pax_global_header00006660000000000000000000000064147573701670014533gustar00rootroot0000000000000052 comment=d477503556a1e39deba984fed4a1b4964318027a proftpd-mod_proxy-0.9.5/000077500000000000000000000000001475737016700152425ustar00rootroot00000000000000proftpd-mod_proxy-0.9.5/.codeql.yml000066400000000000000000000014711475737016700173150ustar00rootroot00000000000000--- query-filters: - exclude: # See: https://codeql.github.com/codeql-query-help/cpp/cpp-commented-out-code/ id: cpp/commented-out-code - exclude: # See: https://codeql.github.com/codeql-query-help/cpp/cpp-long-switch/ id: cpp/long-switch - exclude: # See: https://codeql.github.com/codeql-query-help/cpp/cpp-empty-if/ id: cpp/empty-if - exclude: # See: https://codeql.github.com/codeql-query-help/cpp/cpp-loop-variable-changed/ id: cpp/loop-variable-changed - exclude: # See: https://codeql.github.com/codeql-query-help/cpp/cpp-missing-check-scanf/ id: cpp/missing-check-scanf - exclude: # See: https://codeql.github.com/codeql-query-help/cpp/cpp-poorly-documented-function/ id: cpp/poorly-documented-function paths: - contrib/mod_proxy proftpd-mod_proxy-0.9.5/.gitattributes000066400000000000000000000000621475737016700201330ustar00rootroot00000000000000*.pl linguist-language=C *.pm linguist-language=C proftpd-mod_proxy-0.9.5/.github/000077500000000000000000000000001475737016700166025ustar00rootroot00000000000000proftpd-mod_proxy-0.9.5/.github/workflows/000077500000000000000000000000001475737016700206375ustar00rootroot00000000000000proftpd-mod_proxy-0.9.5/.github/workflows/ci.yml000066400000000000000000000162141475737016700217610ustar00rootroot00000000000000name: CI on: push: branches: - master paths-ignore: - '*.html' - '*.md' - 't/etc/**' - 't/lib/**' - 't/modules/**' pull_request: branches: - master schedule: - cron: '11 1 * * 0' jobs: build: runs-on: ubuntu-latest env: # We need to avoid using NodeJS v20, because it doesn't work with # older glibc versions. See: # https://github.com/actions/checkout/issues/1809. ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true CI: true strategy: matrix: compiler: - clang - gcc container: - almalinux:8 - alpine:3.18 - ubuntu:22.04 container: ${{ matrix.container }} steps: - name: Checkout ProFTPD uses: actions/checkout@v3 with: repository: proftpd/proftpd path: proftpd - name: Checkout module source code uses: actions/checkout@v3 with: path: proftpd/contrib/mod_proxy - name: Whitespace check if: ${{ matrix.container == 'ubuntu:22.04' }} run: | apt-get update -qq apt-get install -y git cd proftpd/contrib/mod_proxy if [[ -n $(git diff --check HEAD^) ]]; then echo "You must remove whitespace before submitting a pull request" echo "" git diff --check HEAD^ exit 1 fi - name: Install Alpine packages if: ${{ matrix.container == 'alpine:3.18' }} run: | apk update # for builds apk add bash build-base clang compiler-rt gcc make zlib-dev # for unit tests apk add check check-dev subunit subunit-dev # for Redis support apk add hiredis-dev # for OpenSSL support apk add openssl openssl-dev # for SQLite support apk add sqlite sqlite-dev # for debugging clang --version gcc --version openssl version -a - name: Install RPM packages if: ${{ matrix.container == 'almalinux:8' }} run: | # Need to add other repos for e.g. libsodium yum install -y dnf-plugins-core epel-release clang gcc make zlib-devel # for unit tests yum install -y check-devel https://cbs.centos.org/kojifiles/packages/subunit/1.4.0/1.el8/x86_64/subunit-1.4.0-1.el8.x86_64.rpm https://cbs.centos.org/kojifiles/packages/subunit/1.4.0/1.el8/x86_64/subunit-devel-1.4.0-1.el8.x86_64.rpm # for Redis support yum install -y hiredis-devel # for OpenSSL support yum install -y openssl openssl-devel # for SQLite support yum install -y sqlite-devel # for debugging clang --version gcc --version openssl version -a - name: Install Ubuntu packages if: ${{ matrix.container == 'ubuntu:22.04' }} run: | apt-get update -qq # for builds apt-get install -y clang gcc make # for unit tests apt-get install -y check libsubunit-dev # for Redis support apt-get install -y libhiredis-dev # for OpenSSL support apt-get install -y libssl-dev # for SQLite support apt-get install -y libsqlite3-dev sqlite3 # for test code coverage apt-get install -y lcov ruby gem install coveralls-lcov # for HTML validation apt-get install -y tidy # for debugging clang --version gcc --version openssl version -a - name: Prepare code coverage if: ${{ matrix.container == 'ubuntu:22.04' }} run: | lcov --directory proftpd --zerocounters - name: Build without Redis, SSL support env: CC: ${{ matrix.compiler }} run: | cd proftpd ./configure LIBS="-lm -lsubunit -lrt -pthread" --enable-devel=coverage --enable-tests --with-modules=mod_proxy make - name: Build with Redis, without SSL support env: CC: ${{ matrix.compiler }} run: | cd proftpd make clean ./configure LIBS="-lm -lsubunit -lrt -pthread" --enable-devel=coverage --enable-redis --enable-tests --with-modules=mod_proxy make - name: Build with Redis, SSL support as shared module env: CC: ${{ matrix.compiler }} run: | cd proftpd make clean ./configure LIBS="-lm -lsubunit -lrt -pthread" --enable-devel=coverage --enable-dso --enable-redis --enable-tests --with-shared=mod_tls:mod_proxy make - name: Build with Redis, SSL support as static module env: CC: ${{ matrix.compiler }} run: | cd proftpd make clean ./configure LIBS="-lm -lsubunit -lrt -pthread" --enable-devel=coverage --enable-redis --enable-tests --with-modules=mod_tls:mod_proxy make - name: Run unit tests env: CC: ${{ matrix.compiler }} # Note: Skip the unit tests on Alpine if: ${{ matrix.container != 'alpine:3.18' }} run: | cd proftpd/contrib/mod_proxy make TEST_VERBOSE=1 check - name: Install with static modules run: | cd proftpd make install - name: Build with shared modules env: CC: ${{ matrix.compiler }} run: | cd proftpd make clean ./configure LIBS="-lm -lsubunit -lrt -pthread" --enable-devel --enable-dso --with-shared=mod_proxy make - name: Install with shared modules run: | cd proftpd make install # https://github.com/google/sanitizers/wiki/AddressSanitizer # https://github.com/google/sanitizers/wiki/AddressSanitizerLeakSanitizer # https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html # # NOTE: Using MemorySanitizer is desirable, but currently unusable since # libcheck is not instrumented, resulting in unsuppressible false # positives. - name: Run unit tests under asan+lsan+ubsan env: ASAN_OPTIONS: abort_on_error=1,check_initialization_order=true,debug=true,detect_invalid_pointer_pairs=2,detect_leaks=1,detect_stack_use_after_return=true,strict_string_checks=true,verbosity=0 CC: ${{ matrix.compiler }} CFLAGS: -fsanitize=address,undefined LDFLAGS: -fsanitize=address,undefined if: ${{ matrix.compiler == 'clang' && matrix.container == 'ubuntu:22.04' }} run: | cd proftpd make clean ./configure LIBS="-lm -lsubunit -lrt -pthread" --enable-devel --enable-redis --enable-tests --with-modules=mod_tls:mod_proxy make cd contrib/mod_proxy export ASAN_SYMBOLIZER_PATH=$(readlink -f $(which llvm-symbolizer-10)) make TEST_VERBOSE=1 check - name: Check HTML docs if: ${{ matrix.container == 'ubuntu:22.04' }} run: | cd proftpd/contrib/mod_proxy for f in $(/bin/ls *.html); do echo "Processing $f"; tidy -errors -omit -q $f; done || exit 0 proftpd-mod_proxy-0.9.5/.github/workflows/codeql.yml000066400000000000000000000050521475737016700226330ustar00rootroot00000000000000name: CodeQL on: push: branches: - master paths-ignore: - '*.html' - '**/*.md' - '**/doc/*' - 't/etc/**' - 't/lib/**' - 't/modules/**' pull_request: branches: - master paths-ignore: - '**/*.md' - '**/doc/*' schedule: - cron: "37 17 * * 2" jobs: analyze: name: CodeQL Analysis runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: true matrix: language: - cpp steps: - name: Checkout ProFTPD uses: actions/checkout@v4 with: repository: proftpd/proftpd - name: Checkout mod_proxy uses: actions/checkout@v4 with: path: contrib/mod_proxy - name: Install Packages run: | sudo apt-get update sudo apt-get install --yes libhiredis-dev libsqlite3-dev libssl-dev libsodium-dev zlib1g-dev - name: Configure run: | ./configure --enable-redis --with-modules=mod_sftp:mod_tls:mod_proxy - name: Initialize CodeQL uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} config-file: contrib/mod_proxy/.codeql.yml queries: +security-and-quality source-root: contrib/mod_proxy - name: Build run: | make - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 with: category: "/language:${{ matrix.language }}" checkout_path: contrib/mod_proxy output: sarif-results upload: false - name: Filter CodeQL SARIF uses: advanced-security/filter-sarif@v1 with: patterns: | -**/lib/proxy/dns.c:cpp/large-parameter -**/lib/proxy/ssh.c:cpp/stack-address-escape -**/lib/proxy/ssh/compress.c:cpp/stack-address-escape -**/lib/proxy/ssh/compress.c:cpp/uncontrolled-allocation-size -**/lib/proxy/ssh/packet.c:cpp/stack-address-escape -**/lib/proxy/ssh/packet.c:cpp/uncontrolled-allocation-size -**/lib/proxy/ssh/umac.c -**/lib/proxy/ssh/umac128.c input: "sarif-results/${{ matrix.language }}.sarif" output: "sarif-results/${{ matrix.language }}.sarif" - name: Upload CodeQL SARIF uses: github/codeql-action/upload-sarif@v2 with: checkout_path: contrib/mod_proxy sarif_file: "sarif-results/${{ matrix.language }}.sarif" proftpd-mod_proxy-0.9.5/.github/workflows/regressions.yml000066400000000000000000000070111475737016700237240ustar00rootroot00000000000000name: Regression Tests on: push: branches: - master paths-ignore: - '*.html' - '*.md' pull_request: branches: - master jobs: build: runs-on: ubuntu-latest env: # We need to avoid using NodeJS v20, because it doesn't work with # older glibc versions. See: # https://github.com/actions/checkout/issues/1809. ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true DEBIAN_FRONTEND: noninteractive REDIS_HOST: redis TZ: America/Los_Angeles services: redis: # Docker Hub image image: redis:6-alpine # Set health checks to wait until redis has started options: >- --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 strategy: matrix: compiler: - gcc container: - ubuntu:22.04 container: ${{ matrix.container }} steps: - name: Checkout ProFTPD uses: actions/checkout@v3 with: repository: proftpd/proftpd path: proftpd - name: Checkout mod_proxy_protocol source code uses: actions/checkout@v3 with: repository: Castaglia/proftpd-mod_proxy_protocol path: mod_proxy_protocol - name: Checkout module source code uses: actions/checkout@v3 with: path: proftpd/contrib/mod_proxy - name: Install Ubuntu packages run: | apt-get update -qq # for builds apt-get install -y gcc git make tzdata # for Redis support apt-get install -y libhiredis-dev # for OpenSSL support apt-get install -y libssl-dev # for SQLite support apt-get install -y libsqlite3-dev sqlite3 # for Sodium support apt-get install -y --force-yes libsodium-dev # for integration/regression tests apt-get install -y \ libauthen-oath-perl \ libcompress-raw-zlib-perl \ libdata-dumper-simple-perl \ libdatetime-perl \ libfile-copy-recursive-perl \ libfile-path-tiny-perl \ libfile-spec-native-perl \ libmime-base32-perl \ libnet-address-ip-local-perl \ libnet-inet6glue-perl \ libnet-ssh2-perl \ libnet-ssleay-perl \ libnet-telnet-perl \ libposix-2008-perl \ libtest-unit-perl \ libtime-hr-perl \ libwww-perl PERL_MM_USE_DEFAULT=1 perl -MCPAN -e 'install Net::FTPSSL' # for debugging gcc --version openssl version -a - name: Prepare source code run: | cp mod_proxy_protocol/mod_proxy_protocol.c proftpd/contrib/mod_proxy_protocol.c - name: Install with static modules # NOTE: Docker does not have good IPv6 support, hence we disable it. run: | cd proftpd ./configure --enable-ctrls --disable-ipv6 --enable-redis --with-modules=mod_ban:mod_rewrite:mod_sftp:mod_auth_otp:mod_sql:mod_sql_sqlite:mod_tls:mod_tls_shmcache:mod_proxy:mod_unique_id:mod_proxy_protocol make ./proftpd -V make install - name: Run integration tests env: PROFTPD_TEST_BIN: /usr/local/sbin/proftpd PROFTPD_TEST_CI: github PROFTPD_TEST_DIR: ${{ github.workspace }}/proftpd/tests run: | cd proftpd/contrib/mod_proxy perl tests.pl proftpd-mod_proxy-0.9.5/.gitignore000066400000000000000000000002321475737016700172270ustar00rootroot00000000000000configure Makefile config.log config.status autom4te.cache mod_proxy.h tests.log t/api-tests t/api-tests.log .libs *.a *.sw? *.la *.lo *Tests*.log *.o *~ proftpd-mod_proxy-0.9.5/Makefile.in000066400000000000000000000102421475737016700173060ustar00rootroot00000000000000top_builddir=../.. top_srcdir=../.. srcdir=@srcdir@ include $(top_srcdir)/Make.rules .SUFFIXES: .la .lo SHARED_CFLAGS=-DPR_SHARED_MODULE SHARED_LDFLAGS=-avoid-version -export-dynamic -module VPATH=@srcdir@ MODULE_LIBS=@MODULE_LIBS@ MODULE_NAME=mod_proxy MODULE_OBJS=mod_proxy.o \ lib/proxy/random.o \ lib/proxy/db.o \ lib/proxy/dns.o \ lib/proxy/session.o \ lib/proxy/conn.o \ lib/proxy/netio.o \ lib/proxy/inet.o \ lib/proxy/str.o \ lib/proxy/ssh.o \ lib/proxy/ssh/db.o \ lib/proxy/ssh/redis.o \ lib/proxy/tls.o \ lib/proxy/tls/db.o \ lib/proxy/tls/redis.o \ lib/proxy/uri.o \ lib/proxy/forward.o \ lib/proxy/reverse.o \ lib/proxy/reverse/db.o \ lib/proxy/reverse/redis.o \ lib/proxy/ftp/conn.o \ lib/proxy/ftp/ctrl.o \ lib/proxy/ftp/data.o \ lib/proxy/ftp/dirlist.o \ lib/proxy/ftp/facts.o \ lib/proxy/ftp/msg.o \ lib/proxy/ftp/sess.o \ lib/proxy/ftp/xfer.o \ lib/proxy/ssh/agent.o \ lib/proxy/ssh/auth.o \ lib/proxy/ssh/bcrypt.o \ lib/proxy/ssh/cipher.o \ lib/proxy/ssh/compress.o \ lib/proxy/ssh/crypto.o \ lib/proxy/ssh/disconnect.o \ lib/proxy/ssh/interop.o \ lib/proxy/ssh/kex.o \ lib/proxy/ssh/keys.o \ lib/proxy/ssh/mac.o \ lib/proxy/ssh/misc.o \ lib/proxy/ssh/msg.o \ lib/proxy/ssh/packet.o \ lib/proxy/ssh/service.o \ lib/proxy/ssh/session.o \ lib/proxy/ssh/umac.o \ lib/proxy/ssh/umac128.o \ lib/proxy/ssh/utf8.o SHARED_MODULE_OBJS=mod_proxy.lo \ lib/proxy/random.lo \ lib/proxy/db.lo \ lib/proxy/dns.lo \ lib/proxy/session.lo \ lib/proxy/conn.lo \ lib/proxy/netio.lo \ lib/proxy/inet.lo \ lib/proxy/str.lo \ lib/proxy/ssh.lo \ lib/proxy/ssh/db.lo \ lib/proxy/ssh/redis.lo \ lib/proxy/tls.lo \ lib/proxy/tls/db.lo \ lib/proxy/tls/redis.lo \ lib/proxy/uri.lo \ lib/proxy/forward.lo \ lib/proxy/reverse.lo \ lib/proxy/reverse/db.lo \ lib/proxy/reverse/redis.lo \ lib/proxy/ftp/conn.lo \ lib/proxy/ftp/ctrl.lo \ lib/proxy/ftp/data.lo \ lib/proxy/ftp/dirlist.lo \ lib/proxy/ftp/facts.lo \ lib/proxy/ftp/msg.lo \ lib/proxy/ftp/sess.lo \ lib/proxy/ftp/xfer.lo \ lib/proxy/ssh/agent.lo \ lib/proxy/ssh/auth.lo \ lib/proxy/ssh/bcrypt.lo \ lib/proxy/ssh/cipher.lo \ lib/proxy/ssh/compress.lo \ lib/proxy/ssh/crypto.lo \ lib/proxy/ssh/disconnect.lo \ lib/proxy/ssh/interop.lo \ lib/proxy/ssh/kex.lo \ lib/proxy/ssh/keys.lo \ lib/proxy/ssh/mac.lo \ lib/proxy/ssh/misc.lo \ lib/proxy/ssh/msg.lo \ lib/proxy/ssh/packet.lo \ lib/proxy/ssh/service.lo \ lib/proxy/ssh/session.lo \ lib/proxy/ssh/umac.lo \ lib/proxy/ssh/umac128.lo \ lib/proxy/ssh/utf8.lo # Necessary redefinitions INCLUDES=-I. -I./include -I../.. -I../../include @INCLUDES@ CPPFLAGS= $(ADDL_CPPFLAGS) -DHAVE_CONFIG_H $(DEFAULT_PATHS) $(PLATFORM) $(INCLUDES) LDFLAGS=-L../../lib @LIBDIRS@ .c.o: $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ .c.lo: $(LIBTOOL) --mode=compile --tag=CC $(CC) $(CPPFLAGS) $(CFLAGS) $(SHARED_CFLAGS) -c $< -o $@ shared: $(SHARED_MODULE_OBJS) $(LIBTOOL) --mode=link --tag=CC $(CC) -o $(MODULE_NAME).la $(SHARED_MODULE_OBJS) -rpath $(LIBEXECDIR) $(LDFLAGS) $(SHARED_LDFLAGS) $(MODULE_LIBS) $(SHARED_MODULE_LIBS) `cat $(MODULE_NAME).c | grep '$$Libraries:' | sed -e 's/^.*\$$Libraries: \(.*\)\\$$/\1/'` static: $(MODULE_OBJS) test -z "$(MODULE_LIBS)" || echo "$(MODULE_LIBS)" >> $(MODULE_LIBS_FILE) $(AR) rc $(MODULE_NAME).a $(MODULE_OBJS) $(RANLIB) $(MODULE_NAME).a install: install-misc if [ -f $(MODULE_NAME).la ] ; then \ $(LIBTOOL) --mode=install --tag=CC $(INSTALL_BIN) $(MODULE_NAME).la $(DESTDIR)$(LIBEXECDIR) ; \ fi install-misc: $(INSTALL) -o $(INSTALL_USER) -g $(INSTALL_GROUP) -m 0644 cacerts.pem $(DESTDIR)$(sysconfdir)/cacerts.pem clean: $(LIBTOOL) --mode=clean $(RM) $(MODULE_NAME).a $(MODULE_NAME).la *.o *.lo .libs/*.o lib/proxy/*.o lib/proxy/*.lo lib/proxy/ftp/*.o lib/proxy/ftp/*.lo lib/proxy/reverse/*.o lib/proxy/reverse/*.lo lib/proxy/ssh/*.o lib/proxy/ssh/*.lo lib/proxy/tls/*.o lib/proxy/tls/*.lo cd t/ && $(MAKE) clean # Run the API tests check: test -z "$(ENABLE_TESTS)" || (cd t/ && $(MAKE) api-tests) distclean: clean $(RM) Makefile $(MODULE_NAME).h config.status config.cache config.log *.gcda *.gcno -$(RM) -r .libs/ .git/ CVS/ RCS/ proftpd-mod_proxy-0.9.5/README.md000066400000000000000000000015511475737016700165230ustar00rootroot00000000000000proftpd-mod_proxy ================= Status ------ [![GitHub Actions CI Status](https://github.com/Castaglia/proftpd-mod_proxy/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/Castaglia/proftpd-mod_proxy/actions/workflows/ci.yml) [![CodeQL Analysis](https://github.com/Castaglia/proftpd-mod_proxy/actions/workflows/codeql.yml/badge.svg)](https://github.com/Castaglia/proftpd-mod_proxy/actions/workflows/codeql.yml) [![License](https://img.shields.io/badge/license-GPL-brightgreen.svg)](https://img.shields.io/badge/license-GPL-brightgreen.svg) Synopsis -------- The `mod_proxy` module for ProFTPD proxies FTP/FTPS connections, supporting both forward and reverse proxy configurations. See the [mod_proxy.html](https://htmlpreview.github.io/?https://github.com/Castaglia/proftpd-mod_proxy/blob/master/mod_proxy.html) documentation for more details. proftpd-mod_proxy-0.9.5/cacerts.pem000066400000000000000000007176061475737016700174120ustar00rootroot00000000000000## ## Bundle of CA Root Certificates ## ## Certificate data from Mozilla as of: Sun Dec 12 01:50:20 2021 GMT ## ## This is a bundle of X.509 certificates of public Certificate Authorities ## (CA). These were automatically extracted from Mozilla's root certificates ## file (certdata.txt). This file can be found in the mozilla source tree: ## https://hg.mozilla.org/releases/mozilla-release/raw-file/default/security/nss/lib/ckfw/builtins/certdata.txt ## ## It contains the certificates in PEM format and therefore ## can be directly used with curl / libcurl / php_curl, or with ## an Apache+mod_ssl webserver for SSL client authentication. ## Just configure this file as the SSLCACertificateFile. ## ## Conversion done with mk-ca-bundle.pl version 1.28. ## mk-ca-bundle.pl -v -f -p ALL:TRUSTED_DELEGATOR ## SHA256: bb36818a81feaa4cca61101e6d6276cd09e972efcb08112dfed846918ca41d7f ## GlobalSign Root CA ================== -----BEGIN CERTIFICATE----- MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkGA1UEBhMCQkUx GTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jvb3QgQ0ExGzAZBgNVBAMTEkds b2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAwMDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNV BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYD VQQDExJHbG9iYWxTaWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDa DuaZjc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavpxy0Sy6sc THAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp1Wrjsok6Vjk4bwY8iGlb Kk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdGsnUOhugZitVtbNV4FpWi6cgKOOvyJBNP c1STE4U6G7weNLWLBYy5d4ux2x8gkasJU26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrX gzT/LCrBbBlDSgeF59N89iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUF AAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOzyj1hTdNGCbM+w6Dj Y1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE38NflNUVyRRBnMRddWQVDf9VMOyG j/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymPAbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhH hm4qxFYxldBniYUr+WymXUadDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveC X4XSQRjbgbMEHMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== -----END CERTIFICATE----- GlobalSign Root CA - R2 ======================= -----BEGIN CERTIFICATE----- MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4GA1UECxMXR2xv YmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh bFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT aWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6 ErPLv4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8eoLrvozp s6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklqtTleiDTsvHgMCJiEbKjN S7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzdC9XZzPnqJworc5HGnRusyMvo4KD0L5CL TfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pazq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6C ygPCm48CAwEAAaOBnDCBmTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E FgQUm+IHV2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5nbG9i YWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG3lm0mi3f3BmGLjAN BgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4GsJ0/WwbgcQ3izDJr86iw8bmEbTUsp 9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu 01yiPqFbQfXf5WRDLenVOavSot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG7 9G+dwfCMNYxdAfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7 TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== -----END CERTIFICATE----- Verisign Class 1 Public Primary Certification Authority - G3 ============================================================ -----BEGIN CERTIFICATE----- MIIEGjCCAwICEQCLW3VWhFSFCwDPrzhIzrGkMA0GCSqGSIb3DQEBBQUAMIHKMQswCQYDVQQGEwJV UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv cmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl IG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNh dGlvbiBBdXRob3JpdHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQsw CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy dXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhv cml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDEgUHVibGljIFByaW1hcnkg Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC ggEBAN2E1Lm0+afY8wR4nN493GwTFtl63SRRZsDHJlkNrAYIwpTRMx/wgzUfbhvI3qpuFU5UJ+/E bRrsC+MO8ESlV8dAWB6jRx9x7GD2bZTIGDnt/kIYVt/kTEkQeE4BdjVjEjbdZrwBBDajVWjVojYJ rKshJlQGrT/KFOCsyq0GHZXi+J3x4GD/wn91K0zM2v6HmSHquv4+VNfSWXjbPG7PoBMAGrgnoeS+ Z5bKoMWznN3JdZ7rMJpfo83ZrngZPyPpXNspva1VyBtUjGP26KbqxzcSXKMpHgLZ2x87tNcPVkeB FQRKr4Mn0cVYiMHd9qqnoxjaaKptEVHhv2Vrn5Z20T0CAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA q2aN17O6x5q25lXQBfGfMY1aqtmqRiYPce2lrVNWYgFHKkTp/j90CxObufRNG7LRX7K20ohcs5/N y9Sn2WCVhDr4wTcdYcrnsMXlkdpUpqwxga6X3s0IrLjAl4B/bnKk52kTlWUfxJM8/XmPBNQ+T+r3 ns7NZ3xPZQL/kYVUc8f/NveGLezQXk//EZ9yBta4GvFMDSZl4kSAHsef493oCtrspSCAaWihT37h a88HQfqDjrw43bAuEbFrskLMmrz5SCJ5ShkPshw+IHTZasO+8ih4E1Z5T21Q6huwtVexN2ZYI/Pc D98Kh8TvhgXVOBRgmaNL3gaWcSzy27YfpO8/7g== -----END CERTIFICATE----- Verisign Class 2 Public Primary Certification Authority - G3 ============================================================ -----BEGIN CERTIFICATE----- MIIEGTCCAwECEGFwy0mMX5hFKeewptlQW3owDQYJKoZIhvcNAQEFBQAwgcoxCzAJBgNVBAYTAlVT MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29y azE6MDgGA1UECxMxKGMpIDE5OTkgVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ug b25seTFFMEMGA1UEAxM8VmVyaVNpZ24gQ2xhc3MgMiBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0 aW9uIEF1dGhvcml0eSAtIEczMB4XDTk5MTAwMTAwMDAwMFoXDTM2MDcxNjIzNTk1OVowgcoxCzAJ BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1 c3QgTmV0d29yazE6MDgGA1UECxMxKGMpIDE5OTkgVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9y aXplZCB1c2Ugb25seTFFMEMGA1UEAxM8VmVyaVNpZ24gQ2xhc3MgMiBQdWJsaWMgUHJpbWFyeSBD ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEczMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC AQEArwoNwtUs22e5LeWUJ92lvuCwTY+zYVY81nzD9M0+hsuiiOLh2KRpxbXiv8GmR1BeRjmL1Za6 tW8UvxDOJxOeBUebMXoT2B/Z0wI3i60sR/COgQanDTAM6/c8DyAd3HJG7qUCyFvDyVZpTMUYwZF7 C9UTAJu878NIPkZgIIUq1ZC2zYugzDLdt/1AVbJQHFauzI13TccgTacxdu9okoqQHgiBVrKtaaNS 0MscxCM9H5n+TOgWY47GCI72MfbS+uV23bUckqNJzc0BzWjNqWm6o+sdDZykIKbBoMXRRkwXbdKs Zj+WjOCE1Db/IlnF+RFgqF8EffIa9iVCYQ/ESrg+iQIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQA0 JhU8wI1NQ0kdvekhktdmnLfexbjQ5F1fdiLAJvmEOjr5jLX77GDx6M4EsMjdpwOPMPOY36TmpDHf 0xwLRtxyID+u7gU8pDM/CzmscHhzS5kr3zDCVLCoO1Wh/hYozUK9dG6A2ydEp85EXdQbkJgNHkKU sQAsBNB0owIFImNjzYO1+8FtYmtpdf1dcEG59b98377BMnMiIYtYgXsVkXq642RIsH/7NiXaldDx JBQX3RiAa0YjOVT1jmIJBB2UkKab5iXiQkWquJCtvgiPqQtCGJTPcjnhsUPgKM+351psE2tJs//j GHyJizNdrDPXp/naOlXJWBD5qu9ats9LS98q -----END CERTIFICATE----- Entrust.net Premium 2048 Secure Server CA ========================================= -----BEGIN CERTIFICATE----- MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChMLRW50cnVzdC5u ZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBpbmNvcnAuIGJ5IHJlZi4gKGxp bWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNV BAMTKkVudHJ1c3QubmV0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQx NzUwNTFaFw0yOTA3MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3 d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTEl MCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5u ZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A MIIBCgKCAQEArU1LqRKGsuqjIAcVFmQqK0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOL Gp18EzoOH1u3Hs/lJBQesYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSr hRSGlVuXMlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVTXTzW nLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/HoZdenoVve8AjhUi VBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH4QIDAQABo0IwQDAOBgNVHQ8BAf8E BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJ KoZIhvcNAQEFBQADggEBADubj1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPy T/4xmf3IDExoU8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5bu/8j72gZyxKT J1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+bYQLCIt+jerXmCHG8+c8eS9e nNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/ErfF6adulZkMV8gzURZVE= -----END CERTIFICATE----- Baltimore CyberTrust Root ========================= -----BEGIN CERTIFICATE----- MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJRTESMBAGA1UE ChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYDVQQDExlCYWx0aW1vcmUgQ3li ZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoXDTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMC SUUxEjAQBgNVBAoTCUJhbHRpbW9yZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFs dGltb3JlIEN5YmVyVHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKME uyKrmD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjrIZ3AQSsB UnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeKmpYcqWe4PwzV9/lSEy/C G9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSuXmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9 XbIGevOF6uvUA65ehD5f/xXtabz5OTZydc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjpr l3RjM71oGDHweI12v/yejl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoI VDaGezq1BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEB BQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT929hkTI7gQCvlYpNRh cL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3WgxjkzSswF07r51XgdIGn9w/xZchMB5 hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsa Y71k5h+3zvDyny67G7fyUIhzksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9H RCwBXbsdtTLSR9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp -----END CERTIFICATE----- Entrust Root Certification Authority ==================================== -----BEGIN CERTIFICATE----- MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMCVVMxFjAUBgNV BAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0Lm5ldC9DUFMgaXMgaW5jb3Jw b3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMWKGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsG A1UEAxMkRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0 MloXDTI2MTEyNzIwNTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMu MTkwNwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSByZWZlcmVu Y2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNVBAMTJEVudHJ1c3QgUm9v dCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB ALaVtkNC+sZtKm9I35RMOVcF7sN5EUFoNu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYsz A9u3g3s+IIRe7bJWKKf44LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOww Cj0Yzfv9KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGIrb68 j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi94DkZfs0Nw4pgHBN rziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOBsDCBrTAOBgNVHQ8BAf8EBAMCAQYw DwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAigA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1 MzQyWjAfBgNVHSMEGDAWgBRokORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DH hmak8fdLQ/uEvW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9tO1KzKtvn1ISM Y/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6ZuaAGAT/3B+XxFNSRuzFVJ7yVTa v52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTS W3iDVuycNsMm4hH2Z0kdkquM++v/eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0 tHuu2guQOHXvgR1m0vdXcDazv/wor3ElhVsT/h5/WrQ8 -----END CERTIFICATE----- Certum Root CA ============== -----BEGIN CERTIFICATE----- MIIDDDCCAfSgAwIBAgIDAQAgMA0GCSqGSIb3DQEBBQUAMD4xCzAJBgNVBAYTAlBMMRswGQYDVQQK ExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBDQTAeFw0wMjA2MTExMDQ2Mzla Fw0yNzA2MTExMDQ2MzlaMD4xCzAJBgNVBAYTAlBMMRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8u by4xEjAQBgNVBAMTCUNlcnR1bSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6x wS7TT3zNJc4YPk/EjG+AanPIW1H4m9LcuwBcsaD8dQPugfCI7iNS6eYVM42sLQnFdvkrOYCJ5JdL kKWoePhzQ3ukYbDYWMzhbGZ+nPMJXlVjhNWo7/OxLjBos8Q82KxujZlakE403Daaj4GIULdtlkIJ 89eVgw1BS7Bqa/j8D35in2fE7SZfECYPCE/wpFcozo+47UX2bu4lXapuOb7kky/ZR6By6/qmW6/K Uz/iDsaWVhFu9+lmqSbYf5VT7QqFiLpPKaVCjF62/IUgAKpoC6EahQGcxEZjgoi2IrHu/qpGWX7P NSzVttpd90gzFFS269lvzs2I1qsb2pY7HVkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkq hkiG9w0BAQUFAAOCAQEAuI3O7+cUus/usESSbLQ5PqKEbq24IXfS1HeCh+YgQYHu4vgRt2PRFze+ GXYkHAQaTOs9qmdvLdTN/mUxcMUbpgIKumB7bVjCmkn+YzILa+M6wKyrO7Do0wlRjBCDxjTgxSvg GrZgFCdsMneMvLJymM/NzD+5yCRCFNZX/OYmQ6kd5YCQzgNUKD73P9P4Te1qCjqTE5s7FCMTY5w/ 0YcneeVMUeMBrYVdGjux1XMQpNPyvG5k9VpWkKjHDkx0Dy5xO/fIR/RpbxXyEV6DHpx8Uq79AtoS qFlnGNu8cN2bsWntgM6JQEhqDjXKKWYVIZQs6GAqm4VKQPNriiTsBhYscw== -----END CERTIFICATE----- Comodo AAA Services root ======================== -----BEGIN CERTIFICATE----- MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg TGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAw MFoXDTI4MTIzMTIzNTk1OVowezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hl c3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC ggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQuaBtDFcCLNSS1UY8y2bmhG C1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe3M/vg4aijJRPn2jymJBGhCfHdr/jzDUs i14HZGWCwEiwqJH5YZ92IFCokcdmtet4YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszW Y19zjNoFmag4qMsXeDZRrOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjH Ypy+g8cmez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQUoBEK Iz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wewYDVR0f BHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNl cy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29tb2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2Vz LmNybDANBgkqhkiG9w0BAQUFAAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm 7l3sAg9g1o1QGE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2G9w84FoVxp7Z 8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsil2D4kF501KKaU73yqWjgom7C 12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== -----END CERTIFICATE----- QuoVadis Root CA 2 ================== -----BEGIN CERTIFICATE----- MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMjAeFw0wNjExMjQx ODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4IC DwAwggIKAoICAQCaGMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6 XJxgFyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55JWpzmM+Yk lvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bBrrcCaoF6qUWD4gXmuVbB lDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp+ARz8un+XJiM9XOva7R+zdRcAitMOeGy lZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt 66/3FsvbzSUr5R/7mp/iUcw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1Jdxn wQ5hYIizPtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og/zOh D7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UHoycR7hYQe7xFSkyy BNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuIyV77zGHcizN300QyNQliBJIWENie J0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1Ud DgQWBBQahGK8SEwzJQTU7tD2A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGU a6FJpEcwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2fBluornFdLwUv Z+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzng/iN/Ae42l9NLmeyhP3ZRPx3 UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2BlfF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodm VjB3pjd4M1IQWK4/YY7yarHvGH5KWWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK +JDSV6IZUaUtl0HaB0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrW IozchLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPRTUIZ3Ph1 WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWDmbA4CD/pXvk1B+TJYm5X f6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0ZohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II 4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8 VCLAAVBpQ570su9t+Oza8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u -----END CERTIFICATE----- QuoVadis Root CA 3 ================== -----BEGIN CERTIFICATE----- MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMzAeFw0wNjExMjQx OTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4IC DwAwggIKAoICAQDMV0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNgg DhoB4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUrH556VOij KTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd8lyyBTNvijbO0BNO/79K DDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9CabwvvWhDFlaJKjdhkf2mrk7AyxRllDdLkgbv BNDInIjbC3uBr7E9KsRlOni27tyAsdLTmZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwp p5ijJUMv7/FfJuGITfhebtfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8 nT8KKdjcT5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDtWAEX MJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZc6tsgLjoC2SToJyM Gf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A4iLItLRkT9a6fUg+qGkM17uGcclz uD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYDVR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHT BgkrBgEEAb5YAAMwgcUwgZMGCCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmlj YXRlIGNvbnN0aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0 aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVudC4wLQYIKwYB BQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2NwczALBgNVHQ8EBAMCAQYwHQYD VR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4GA1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4 ywLQoUmkRzBFMQswCQYDVQQGEwJCTTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UE AxMSUXVvVmFkaXMgUm9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZV qyM07ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSemd1o417+s hvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd+LJ2w/w4E6oM3kJpK27z POuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2 Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadNt54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp 8kokUvd0/bpO5qgdAm6xDYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBC bjPsMZ57k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6szHXu g/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0jWy10QJLZYxkNc91p vGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeTmJlglFwjz1onl14LBQaTNx47aTbr qZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK4SVhM7JZG+Ju1zdXtg2pEto= -----END CERTIFICATE----- Security Communication Root CA ============================== -----BEGIN CERTIFICATE----- MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw HhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw 8yl89f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJDKaVv0uM DPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9Ms+k2Y7CI9eNqPPYJayX 5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/NQV3Is00qVUarH9oe4kA92819uZKAnDfd DJZkndwi92SL32HeFZRSFaB9UslLqCHJxrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2 JChzAgMBAAGjPzA9MB0GA1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYw DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vGkl3g 0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfrUj94nK9NrvjVT8+a mCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5Bw+SUEmK3TGXX8npN6o7WWWXlDLJ s58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJUJRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ 6rBK+1YWc26sTfcioU+tHXotRSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAi FL39vmwLAw== -----END CERTIFICATE----- Camerfirma Chambers of Commerce Root ==================================== -----BEGIN CERTIFICATE----- MIIEvTCCA6WgAwIBAgIBADANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJFVTEnMCUGA1UEChMe QUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1i ZXJzaWduLm9yZzEiMCAGA1UEAxMZQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdDAeFw0wMzA5MzAx NjEzNDNaFw0zNzA5MzAxNjEzNDRaMH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZp cm1hIFNBIENJRiBBODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3Jn MSIwIAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290MIIBIDANBgkqhkiG9w0BAQEFAAOC AQ0AMIIBCAKCAQEAtzZV5aVdGDDg2olUkfzIx1L4L1DZ77F1c2VHfRtbunXF/KGIJPov7coISjlU xFF6tdpg6jg8gbLL8bvZkSM/SAFwdakFKq0fcfPJVD0dBmpAPrMMhe5cG3nCYsS4No41XQEMIwRH NaqbYE6gZj3LJgqcQKH0XZi/caulAGgq7YN6D6IUtdQis4CwPAxaUWktWBiP7Zme8a7ileb2R6jW DA+wWFjbw2Y3npuRVDM30pQcakjJyfKl2qUMI/cjDpwyVV5xnIQFUZot/eZOKjRa3spAN2cMVCFV d9oKDMyXroDclDZK9D7ONhMeU+SsTjoF7Nuucpw4i9A5O4kKPnf+dQIBA6OCAUQwggFAMBIGA1Ud EwEB/wQIMAYBAf8CAQwwPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC5jaGFtYmVyc2lnbi5v cmcvY2hhbWJlcnNyb290LmNybDAdBgNVHQ4EFgQU45T1sU3p26EpW1eLTXYGduHRooowDgYDVR0P AQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzAnBgNVHREEIDAegRxjaGFtYmVyc3Jvb3RAY2hh bWJlcnNpZ24ub3JnMCcGA1UdEgQgMB6BHGNoYW1iZXJzcm9vdEBjaGFtYmVyc2lnbi5vcmcwWAYD VR0gBFEwTzBNBgsrBgEEAYGHLgoDATA+MDwGCCsGAQUFBwIBFjBodHRwOi8vY3BzLmNoYW1iZXJz aWduLm9yZy9jcHMvY2hhbWJlcnNyb290Lmh0bWwwDQYJKoZIhvcNAQEFBQADggEBAAxBl8IahsAi fJ/7kPMa0QOx7xP5IV8EnNrJpY0nbJaHkb5BkAFyk+cefV/2icZdp0AJPaxJRUXcLo0waLIJuvvD L8y6C98/d3tGfToSJI6WjzwFCm/SlCgdbQzALogi1djPHRPH8EjX1wWnz8dHnjs8NMiAT9QUu/wN UPf6s+xCX6ndbcj0dc97wXImsQEcXCz9ek60AcUFV7nnPKoF2YjpB0ZBzu9Bga5Y34OirsrXdx/n ADydb47kMgkdTXg0eDQ8lJsm7U9xxhl6vSAiSFr+S30Dt+dYvsYyTnQeaN2oaFuzPu5ifdmA6Ap1 erfutGWaIZDgqtCYvDi1czyL+Nw= -----END CERTIFICATE----- Camerfirma Global Chambersign Root ================================== -----BEGIN CERTIFICATE----- MIIExTCCA62gAwIBAgIBADANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJFVTEnMCUGA1UEChMe QUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1i ZXJzaWduLm9yZzEgMB4GA1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwHhcNMDMwOTMwMTYx NDE4WhcNMzcwOTMwMTYxNDE4WjB9MQswCQYDVQQGEwJFVTEnMCUGA1UEChMeQUMgQ2FtZXJmaXJt YSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEg MB4GA1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwggEgMA0GCSqGSIb3DQEBAQUAA4IBDQAw ggEIAoIBAQCicKLQn0KuWxfH2H3PFIP8T8mhtxOviteePgQKkotgVvq0Mi+ITaFgCPS3CU6gSS9J 1tPfnZdan5QEcOw/Wdm3zGaLmFIoCQLfxS+EjXqXd7/sQJ0lcqu1PzKY+7e3/HKE5TWH+VX6ox8O by4o3Wmg2UIQxvi1RMLQQ3/bvOSiPGpVeAp3qdjqGTK3L/5cPxvusZjsyq16aUXjlg9V9ubtdepl 6DJWk0aJqCWKZQbua795B9Dxt6/tLE2Su8CoX6dnfQTyFQhwrJLWfQTSM/tMtgsL+xrJxI0DqX5c 8lCrEqWhz0hQpe/SyBoT+rB/sYIcd2oPX9wLlY/vQ37mRQklAgEDo4IBUDCCAUwwEgYDVR0TAQH/ BAgwBgEB/wIBDDA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vY3JsLmNoYW1iZXJzaWduLm9yZy9j aGFtYmVyc2lnbnJvb3QuY3JsMB0GA1UdDgQWBBRDnDafsJ4wTcbOX60Qq+UDpfqpFDAOBgNVHQ8B Af8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgAHMCoGA1UdEQQjMCGBH2NoYW1iZXJzaWducm9vdEBj aGFtYmVyc2lnbi5vcmcwKgYDVR0SBCMwIYEfY2hhbWJlcnNpZ25yb290QGNoYW1iZXJzaWduLm9y ZzBbBgNVHSAEVDBSMFAGCysGAQQBgYcuCgEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly9jcHMuY2hh bWJlcnNpZ24ub3JnL2Nwcy9jaGFtYmVyc2lnbnJvb3QuaHRtbDANBgkqhkiG9w0BAQUFAAOCAQEA PDtwkfkEVCeR4e3t/mh/YV3lQWVPMvEYBZRqHN4fcNs+ezICNLUMbKGKfKX0j//U2K0X1S0E0T9Y gOKBWYi+wONGkyT+kL0mojAt6JcmVzWJdJYY9hXiryQZVgICsroPFOrGimbBhkVVi76SvpykBMdJ PJ7oKXqJ1/6v/2j1pReQvayZzKWGVwlnRtvWFsJG8eSpUPWP0ZIV018+xgBJOm5YstHRJw0lyDL4 IBHNfTIzSJRUTN3cecQwn+uOuFW114hcxWokPbLTBQNRxgfvzBRydD1ucs4YKIxKoHflCStFREes t2d/AYoFWpO+ocH/+OcOZ6RHSXZddZAa9SaP8A== -----END CERTIFICATE----- XRamp Global CA Root ==================== -----BEGIN CERTIFICATE----- MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UE BhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2Vj dXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB dXRob3JpdHkwHhcNMDQxMTAxMTcxNDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMx HjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkg U2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3Jp dHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS638eMpSe2OAtp87ZOqCwu IR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCPKZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMx foArtYzAQDsRhtDLooY2YKTVMIJt2W7QDxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FE zG+gSqmUsE3a56k0enI4qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqs AxcZZPRaJSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNViPvry xS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud EwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASsjVy16bYbMDYGA1UdHwQvMC0wK6Ap oCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMC AQEwDQYJKoZIhvcNAQEFBQADggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc /Kh4ZzXxHfARvbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLaIR9NmXmd4c8n nxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSyi6mx5O+aGtA9aZnuqCij4Tyz 8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQO+7ETPTsJ3xCwnR8gooJybQDJbw= -----END CERTIFICATE----- Go Daddy Class 2 CA =================== -----BEGIN CERTIFICATE----- MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMY VGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRp ZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkG A1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQAD ggENADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv 2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+qN1j3hybX2C32 qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6j YGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmY vLEHZ6IVDd2gWMZEewo+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0O BBYEFNLEsNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h/t2o atTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMu MTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwG A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wim PQoZ+YeAEW5p5JYXMP80kWNyOO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKt I3lpjbi2Tc7PTMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mERdEr/VxqHD3VI Ls9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5CufReYNnyicsbkqWletNw+vHX/b vZ8= -----END CERTIFICATE----- Starfield Class 2 CA ==================== -----BEGIN CERTIFICATE----- MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzElMCMGA1UEChMc U3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZpZWxkIENsYXNzIDIg Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQwNjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBo MQswCQYDVQQGEwJVUzElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAG A1UECxMpU3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqG SIb3DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf8MOh2tTY bitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN+lq2cwQlZut3f+dZxkqZ JRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVm epsZGD3/cVE8MC5fvj13c7JdBmzDI1aaK4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSN F4Azbl5KXZnJHoe0nRrA1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HF MIHCMB0GA1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fRzt0f hvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNo bm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBDbGFzcyAyIENlcnRpZmljYXRpb24g QXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGs afPzWdqbAYcaT1epoXkJKtv3L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLM PUxA2IGvd56Deruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynpVSJYACPq4xJD KVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEYWQPJIrSPnNVeKtelttQKbfi3 QBFGmh95DmK/D5fs4C8fF5Q= -----END CERTIFICATE----- DigiCert Assured ID Root CA =========================== -----BEGIN CERTIFICATE----- MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQG EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzEx MTEwMDAwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0Ew ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7cJpSIqvTO 9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYPmDI2dsze3Tyoou9q+yHy UmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW /lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpy oeb6pNnVFzF1roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRF 66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzANBgkq hkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2Bc EkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38Fn SbNd67IJKusm7Xi+fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i 8b5QZ7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe +o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== -----END CERTIFICATE----- DigiCert Global Root CA ======================= -----BEGIN CERTIFICATE----- MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQG EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAw MDAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkq hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsBCSDMAZOn TjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97nh6Vfe63SKMI2tavegw5 BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt43C/dxC//AH2hdmoRBBYMql1GNXRor5H 4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7PT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y 7vrTC0LUq7dBMtoM1O/4gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQAB o2MwYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbRTLtm 8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDQYJKoZIhvcNAQEF BQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/EsrhMAtudXH/vTBH1jLuG2cenTnmCmr EbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIt tep3Sp+dWOIrWcBAI+0tKIJFPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886 UAb3LujEV0lsYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= -----END CERTIFICATE----- DigiCert High Assurance EV Root CA ================================== -----BEGIN CERTIFICATE----- MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQG EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSsw KQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAw MFoXDTMxMTExMDAwMDAwMFowbDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFu Y2UgRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm+9S75S0t Mqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTWPNt0OKRKzE0lgvdKpVMS OO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEMxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3 MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFBIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQ NAQTXKFx01p8VdteZOE3hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUe h10aUAsgEsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMB Af8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSY JhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3NecnzyIZgYIVyHbIUf4KmeqvxgydkAQ V8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6zeM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFp myPInngiK3BD41VHMWEZ71jFhS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkK mNEVX58Svnw2Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep+OkuE6N36B9K -----END CERTIFICATE----- SwissSign Platinum CA - G2 ========================== -----BEGIN CERTIFICATE----- MIIFwTCCA6mgAwIBAgIITrIAZwwDXU8wDQYJKoZIhvcNAQEFBQAwSTELMAkGA1UEBhMCQ0gxFTAT BgNVBAoTDFN3aXNzU2lnbiBBRzEjMCEGA1UEAxMaU3dpc3NTaWduIFBsYXRpbnVtIENBIC0gRzIw HhcNMDYxMDI1MDgzNjAwWhcNMzYxMDI1MDgzNjAwWjBJMQswCQYDVQQGEwJDSDEVMBMGA1UEChMM U3dpc3NTaWduIEFHMSMwIQYDVQQDExpTd2lzc1NpZ24gUGxhdGludW0gQ0EgLSBHMjCCAiIwDQYJ KoZIhvcNAQEBBQADggIPADCCAgoCggIBAMrfogLi2vj8Bxax3mCq3pZcZB/HL37PZ/pEQtZ2Y5Wu 669yIIpFR4ZieIbWIDkm9K6j/SPnpZy1IiEZtzeTIsBQnIJ71NUERFzLtMKfkr4k2HtnIuJpX+UF eNSH2XFwMyVTtIc7KZAoNppVRDBopIOXfw0enHb/FZ1glwCNioUD7IC+6ixuEFGSzH7VozPY1kne WCqv9hbrS3uQMpe5up1Y8fhXSQQeol0GcN1x2/ndi5objM89o03Oy3z2u5yg+gnOI2Ky6Q0f4nIo j5+saCB9bzuohTEJfwvH6GXp43gOCWcwizSC+13gzJ2BbWLuCB4ELE6b7P6pT1/9aXjvCR+htL/6 8++QHkwFix7qepF6w9fl+zC8bBsQWJj3Gl/QKTIDE0ZNYWqFTFJ0LwYfexHihJfGmfNtf9dng34T aNhxKFrYzt3oEBSa/m0jh26OWnA81Y0JAKeqvLAxN23IhBQeW71FYyBrS3SMvds6DsHPWhaPpZjy domyExI7C3d3rLvlPClKknLKYRorXkzig3R3+jVIeoVNjZpTxN94ypeRSCtFKwH3HBqi7Ri6Cr2D +m+8jVeTO9TUps4e8aCxzqv9KyiaTxvXw3LbpMS/XUz13XuWae5ogObnmLo2t/5u7Su9IPhlGdpV CX4l3P5hYnL5fhgC72O00Puv5TtjjGePAgMBAAGjgawwgakwDgYDVR0PAQH/BAQDAgEGMA8GA1Ud EwEB/wQFMAMBAf8wHQYDVR0OBBYEFFCvzAeHFUdvOMW0ZdHelarp35zMMB8GA1UdIwQYMBaAFFCv zAeHFUdvOMW0ZdHelarp35zMMEYGA1UdIAQ/MD0wOwYJYIV0AVkBAQEBMC4wLAYIKwYBBQUHAgEW IGh0dHA6Ly9yZXBvc2l0b3J5LnN3aXNzc2lnbi5jb20vMA0GCSqGSIb3DQEBBQUAA4ICAQAIhab1 Fgz8RBrBY+D5VUYI/HAcQiiWjrfFwUF1TglxeeVtlspLpYhg0DB0uMoI3LQwnkAHFmtllXcBrqS3 NQuB2nEVqXQXOHtYyvkv+8Bldo1bAbl93oI9ZLi+FHSjClTTLJUYFzX1UWs/j6KWYTl4a0vlpqD4 U99REJNi54Av4tHgvI42Rncz7Lj7jposiU0xEQ8mngS7twSNC/K5/FqdOxa3L8iYq/6KUFkuozv8 KV2LwUvJ4ooTHbG/u0IdUt1O2BReEMYxB+9xJ/cbOQncguqLs5WGXv312l0xpuAxtpTmREl0xRbl 9x8DYSjFyMsSoEJL+WuICI20MhjzdZ/EfwBPBZWcoxcCw7NTm6ogOSkrZvqdr16zktK1puEa+S1B aYEUtLS17Yk9zvupnTVCRLEcFHOBzyoBNZox1S2PbYTfgE1X4z/FhHXaicYwu+uPyyIIoK6q8QNs OktNCaUOcsZWayFCTiMlFGiudgp8DAdwZPmaL/YFOSbGDI8Zf0NebvRbFS/bYV3mZy8/CJT5YLSY Mdp08YSTcU1f+2BY0fvEwW2JorsgH51xkcsymxM9Pn2SUjWskpSi0xjCfMfqr3YFFt1nJ8J+HAci IfNAChs0B0QTwoRqjt8ZWr9/6x3iGjjRXK9HkmuAtTClyY3YqzGBH9/CZjfTk6mFhnll0g== -----END CERTIFICATE----- SwissSign Gold CA - G2 ====================== -----BEGIN CERTIFICATE----- MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkNIMRUw EwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzIwHhcN MDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBFMQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dp c3NTaWduIEFHMR8wHQYDVQQDExZTd2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0B AQEFAAOCAg8AMIICCgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUq t2/876LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+bbqBHH5C jCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c6bM8K8vzARO/Ws/BtQpg vd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqEemA8atufK+ze3gE/bk3lUIbLtK/tREDF ylqM2tIrfKjuvqblCqoOpd8FUrdVxyJdMmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvR AiTysybUa9oEVeXBCsdtMDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuend jIj3o02yMszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69yFGkO peUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPiaG59je883WX0XaxR 7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxMgI93e2CaHt+28kgeDrpOVG2Y4OGi GqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw AwEB/zAdBgNVHQ4EFgQUWyV7lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64 OfPAeGZe6Drn8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe645R88a7A3hfm 5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczOUYrHUDFu4Up+GC9pWbY9ZIEr 44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOf Mke6UiI0HTJ6CVanfCU2qT1L2sCCbwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6m Gu6uLftIdxf+u+yvGPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxp mo/a77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCChdiDyyJk vC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid392qgQmwLOM7XdVAyksLf KzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEppLd6leNcG2mqeSz53OiATIgHQv2ieY2Br NU0LbbqhPcCT4H8js1WtciVORvnSFu+wZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6Lqj viOvrv1vA+ACOzB2+httQc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ -----END CERTIFICATE----- SwissSign Silver CA - G2 ======================== -----BEGIN CERTIFICATE----- MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCQ0gxFTAT BgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMB4X DTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0NlowRzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3 aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG 9w0BAQEFAAOCAg8AMIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644 N0MvFz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7brYT7QbNHm +/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieFnbAVlDLaYQ1HTWBCrpJH 6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH6ATK72oxh9TAtvmUcXtnZLi2kUpCe2Uu MGoM9ZDulebyzYLs2aFK7PayS+VFheZteJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5h qAaEuSh6XzjZG6k4sIN/c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5 FZGkECwJMoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRHHTBs ROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTfjNFusB3hB48IHpmc celM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb65i/4z3GcRm25xBWNOHkDRUjvxF3X CO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ BAUwAwEB/zAdBgNVHQ4EFgQUF6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRB tjpbO8tFnb0cwpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0 cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBAHPGgeAn0i0P 4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShpWJHckRE1qTodvBqlYJ7YH39F kWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L 3XWgwF15kIwb4FDm3jH+mHtwX6WQ2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx /uNncqCxv1yL5PqZIseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFa DGi8aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2Xem1ZqSqP e97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQRdAtq/gsD/KNVV4n+Ssuu WxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJ DIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ub DgEj8Z+7fNzcbBGXJbLytGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u -----END CERTIFICATE----- SecureTrust CA ============== -----BEGIN CERTIFICATE----- MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBIMQswCQYDVQQG EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xFzAVBgNVBAMTDlNlY3VyZVRy dXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIzMTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAe BgNVBAoTF1NlY3VyZVRydXN0IENvcnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCC ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQX OZEzZum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO0gMdA+9t DWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIaowW8xQmxSPmjL8xk037uH GFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b 01k/unK8RCSc43Oz969XL0Imnal0ugBS8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmH ursCAwEAAaOBnTCBmjATBgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/ BAUwAwEB/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCegJYYj aHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ KoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt36Z3q059c4EVlew3KW+JwULKUBRSu SceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHf mbx8IVQr5Fiiu1cprp6poxkmD5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZ nMUFdAvnZyPSCPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR 3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= -----END CERTIFICATE----- Secure Global CA ================ -----BEGIN CERTIFICATE----- MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQG EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBH bG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkxMjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEg MB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwg Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jx YDiJiQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa/FHtaMbQ bqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJjnIFHovdRIWCQtBJwB1g 8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnIHmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYV HDGA76oYa8J719rO+TMg1fW9ajMtgQT7sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi 0XPnj3pDAgMBAAGjgZ0wgZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud EwEB/wQFMAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCswKaAn oCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsGAQQBgjcVAQQDAgEA MA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0LURYD7xh8yOOvaliTFGCRsoTciE6+ OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXOH0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cn CDpOGR86p1hcF895P4vkp9MmI50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/5 3CYNv6ZHdAbYiNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW -----END CERTIFICATE----- COMODO Certification Authority ============================== -----BEGIN CERTIFICATE----- MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCBgTELMAkGA1UE BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNVBAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1 dGhvcml0eTAeFw0wNjEyMDEwMDAwMDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEb MBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFD T01PRE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3UcEbVASY06m/weaKXTuH +7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI2GqGd0S7WWaXUF601CxwRM/aN5VCaTww xHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV 4EajcNxo2f8ESIl33rXp+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA 1KGzqSX+DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5OnKVI rLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW/zAOBgNVHQ8BAf8E BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLmNvbW9k b2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOC AQEAPpiem/Yb6dc5t3iuHXIYSdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CP OGEIqB6BCsAvIC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/ RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4zJVSk/BwJVmc IGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5ddBA6+C4OmF4O5MBKgxTMVBbkN +8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IBZQ== -----END CERTIFICATE----- Network Solutions Certificate Authority ======================================= -----BEGIN CERTIFICATE----- MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQG EwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydOZXR3b3Jr IFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMx MjM1OTU5WjBiMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0G CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwzc7MEL7xx jOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPPOCwGJgl6cvf6UDL4wpPT aaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rlmGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXT crA/vGp97Eh/jcOrqnErU2lBUzS1sLnFBgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc /Qzpf14Dl847ABSHJ3A4qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMB AAGjgZcwgZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIBBjAP BgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwubmV0c29sc3NsLmNv bS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3JpdHkuY3JsMA0GCSqGSIb3DQEBBQUA A4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc86fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q 4LqILPxFzBiwmZVRDuwduIj/h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/ GGUsyfJj4akH/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHNpGxlaKFJdlxD ydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey -----END CERTIFICATE----- COMODO ECC Certification Authority ================================== -----BEGIN CERTIFICATE----- MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTELMAkGA1UEBhMC R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBB dXRob3JpdHkwHhcNMDgwMzA2MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0Ix GzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRo b3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSRFtSrYpn1PlILBs5BAH+X 4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0JcfRK9ChQtP6IHG4/bC8vCVlbpVsLM5ni wz2J+Wos77LTBumjQjBAMB0GA1UdDgQWBBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8E BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VG FAkK+qDmfQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdvGDeA U/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= -----END CERTIFICATE----- OISTE WISeKey Global Root GA CA =============================== -----BEGIN CERTIFICATE----- MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCBijELMAkGA1UE BhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHlyaWdodCAoYykgMjAwNTEiMCAG A1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBH bG9iYWwgUm9vdCBHQSBDQTAeFw0wNTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYD VQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIw IAYDVQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5 IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy0+zAJs9 Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxRVVuuk+g3/ytr6dTqvirdqFEr12bDYVxg Asj1znJ7O7jyTmUIms2kahnBAbtzptf2w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbD d50kc3vkDIzh2TbhmYsFmQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ /yxViJGg4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t94B3R LoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw AwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ KoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOxSPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vIm MMkQyh2I+3QZH4VFvbBsUfk2ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4 +vg1YFkCExh8vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZiFj4A4xylNoEY okxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ/L7fCg0= -----END CERTIFICATE----- Certigna ======== -----BEGIN CERTIFICATE----- MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNVBAYTAkZSMRIw EAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4XDTA3MDYyOTE1MTMwNVoXDTI3 MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwI Q2VydGlnbmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7q XOEm7RFHYeGifBZ4QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyH GxnygQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbwzBfsV1/p ogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q130yGLMLLGq/jj8UEYkg DncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKf Irjxwo1p3Po6WAbfAgMBAAGjgbwwgbkwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQ tCRZvgHyUtVF9lo53BEwZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJ BgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzjAQ/J SP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG9w0BAQUFAAOCAQEA hQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8hbV6lUmPOEvjvKtpv6zf+EwLHyzs+ ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFncfca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1klu PBS1xp81HlDQwY9qcEQCYsuuHWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY 1gkIl2PlwS6wt0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== -----END CERTIFICATE----- Cybertrust Global Root ====================== -----BEGIN CERTIFICATE----- MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYGA1UEChMPQ3li ZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBSb290MB4XDTA2MTIxNTA4 MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQD ExZDeWJlcnRydXN0IEdsb2JhbCBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +Mi8vRRQZhP/8NN57CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW 0ozSJ8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2yHLtgwEZL AfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iPt3sMpTjr3kfb1V05/Iin 89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNzFtApD0mpSPCzqrdsxacwOUBdrsTiXSZT 8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAYXSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAP BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2 MDSgMqAwhi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3JsMB8G A1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUAA4IBAQBW7wojoFRO lZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMjWqd8BfP9IjsO0QbE2zZMcwSO5bAi 5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUxXOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2 hO0j9n0Hq0V+09+zv+mKts2oomcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+T X3EJIrduPuocA06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW WL1WMRJOEcgh4LMRkWXbtKaIOM5V -----END CERTIFICATE----- ePKI Root Certification Authority ================================= -----BEGIN CERTIFICATE----- MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBeMQswCQYDVQQG EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xKjAoBgNVBAsMIWVQS0kg Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMx MjdaMF4xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEq MCgGA1UECwwhZVBLSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0B AQEFAAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAHSyZbCUNs IZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAhijHyl3SJCRImHJ7K2RKi lTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3XDZoTM1PRYfl61dd4s5oz9wCGzh1NlDiv qOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX 12ruOzjjK9SXDrkb5wdJfzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0O WQqraffAsgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uUWH1+ ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLSnT0IFaUQAS2zMnao lQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pHdmX2Os+PYhcZewoozRrSgx4hxyy/ vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJipNiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXi Zo1jDiVN1Rmy5nk3pyKdVDECAwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/Qkqi MAwGA1UdEwQFMAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGBuvl2ICO1J2B0 1GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6YlPwZpVnPDimZI+ymBV3QGypzq KOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkPJXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdV xrsStZf0X4OFunHB2WyBEXYKCrC/gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEP NXubrjlpC2JgQCA2j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+r GNm65ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUBo2M3IUxE xJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS/jQ6fbjpKdx2qcgw+BRx gMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2zGp1iro2C6pSe3VkQw63d4k3jMdXH7Ojy sP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTEW9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmOD BCEIZ43ygknQW/2xzQ+DhNQ+IIX3Sj0rnP0qCglN6oH4EZw= -----END CERTIFICATE----- certSIGN ROOT CA ================ -----BEGIN CERTIFICATE----- MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYTAlJPMREwDwYD VQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTAeFw0wNjA3MDQxNzIwMDRa Fw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UE CxMQY2VydFNJR04gUk9PVCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7I JUqOtdu0KBuqV5Do0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHH rfAQUySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5dRdY4zTW2 ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQOA7+j0xbm0bqQfWwCHTD 0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwvJoIQ4uNllAoEwF73XVv4EOLQunpL+943 AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B Af8EBAMCAcYwHQYDVR0OBBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IB AQA+0hyJLjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecYMnQ8 SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ44gx+FkagQnIl6Z0 x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6IJd1hJyMctTEHBDa0GpC9oHRxUIlt vBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNwi/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7Nz TogVZ96edhBiIL5VaZVDADlN9u6wWk5JRFRYX0KD -----END CERTIFICATE----- NetLock Arany (Class Gold) Főtanúsítvány ======================================== -----BEGIN CERTIFICATE----- MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQGEwJIVTERMA8G A1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610 dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBB cmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgx MjA2MTUwODIxWjCBpzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxO ZXRMb2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlmaWNhdGlv biBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNzIEdvbGQpIEbFkXRhbsO6 c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCRec75LbRTDofTjl5Bu 0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrTlF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw /HpYzY6b7cNGbIRwXdrzAZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAk H3B5r9s5VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRGILdw fzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2BJtr+UBdADTHLpl1 neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIB BjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2MU9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwW qZw8UQCgwBEIBaeZ5m8BiFRhbvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTta YtOUZcTh5m2C+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2FuLjbvrW5Kfna NwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2XjG4Kvte9nHfRCaexOYNkbQu dZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= -----END CERTIFICATE----- Hongkong Post Root CA 1 ======================= -----BEGIN CERTIFICATE----- MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoT DUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMB4XDTAzMDUx NTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25n IFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEF AAOCAQ8AMIIBCgKCAQEArP84tulmAknjorThkPlAj3n54r15/gK97iSSHSL22oVyaf7XPwnU3ZG1 ApzQjVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEnPzlTCeqr auh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjhZY4bXSNmO7ilMlHIhqqh qZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9nnV0ttgCXjqQesBCNnLsak3c78QA3xMY V18meMjWCnl3v/evt3a5pQuEF10Q6m/hq5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNV HRMBAf8ECDAGAQH/AgEDMA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7i h9legYsCmEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI37pio l7Yutmcn1KZJ/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clBoiMBdDhViw+5Lmei IAQ32pwL0xch4I+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJsEhTkYY2sEJCehFC78JZvRZ+K88ps T/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpOfMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilT c4afU9hDDl3WY4JxHYB0yvbiAmvZWg== -----END CERTIFICATE----- SecureSign RootCA11 =================== -----BEGIN CERTIFICATE----- MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDErMCkGA1UEChMi SmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoGA1UEAxMTU2VjdXJlU2lnbiBS b290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSsw KQYDVQQKEyJKYXBhbiBDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1 cmVTaWduIFJvb3RDQTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvL TJszi1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8h9uuywGO wvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOVMdrAG/LuYpmGYz+/3ZMq g6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rP O7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitA bpSACW22s293bzUIUPsCh8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZX t94wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAKCh OBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xmKbabfSVSSUOrTC4r bnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQX5Ucv+2rIrVls4W6ng+4reV6G4pQ Oh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWrQbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01 y8hSyn+B/tlr0/cR7SXf+Of5pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061 lgeLKBObjBmNQSdJQO7e5iNEOdyhIta6A/I= -----END CERTIFICATE----- Microsec e-Szigno Root CA 2009 ============================== -----BEGIN CERTIFICATE----- MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYDVQQGEwJIVTER MA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jv c2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o dTAeFw0wOTA2MTYxMTMwMThaFw0yOTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UE BwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUt U3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTCCASIw DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvPkd6mJviZpWNwrZuuyjNA fW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tccbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG 0IMZfcChEhyVbUr02MelTTMuhTlAdX4UfIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKA pxn1ntxVUwOXewdI/5n7N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm 1HxdrtbCxkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1+rUC AwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTLD8bf QkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAbBgNVHREE FDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqGSIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0o lZMEyL/azXm4Q5DwpL7v8u8hmLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfX I/OMn74dseGkddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c2Pm2G2JwCz02 yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5tHMN1Rq41Bab2XD0h7lbwyYIi LXpUq3DDfSJlgnCW -----END CERTIFICATE----- GlobalSign Root CA - R3 ======================= -----BEGIN CERTIFICATE----- MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4GA1UECxMXR2xv YmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh bFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT aWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWt iHL8RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsTgHeMCOFJ 0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmmKPZpO/bLyCiR5Z2KYVc3 rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zdQQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjl OCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZXriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2 xmmFghcCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE FI/wS3+oLkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZURUm7 lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMpjjM5RcOO5LlXbKr8 EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK6fBdRoyV3XpYKBovHd7NADdBj+1E bddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQXmcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18 YIvDQVETI53O9zJrlAGomecsMx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7r kpeDMdmztcpHWD9f -----END CERTIFICATE----- Autoridad de Certificacion Firmaprofesional CIF A62634068 ========================================================= -----BEGIN CERTIFICATE----- MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UEBhMCRVMxQjBA BgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2 MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEyMzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIw QAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBB NjI2MzQwNjgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDD Utd9thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQMcas9UX4P B99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefGL9ItWY16Ck6WaVICqjaY 7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15iNA9wBj4gGFrO93IbJWyTdBSTo3OxDqqH ECNZXyAFGUftaI6SEspd/NYrspI8IM/hX68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyI plD9amML9ZMWGxmPsu2bm8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctX MbScyJCyZ/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirjaEbsX LZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/TKI8xWVvTyQKmtFLK bpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF6NkBiDkal4ZkQdU7hwxu+g/GvUgU vzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVhOSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1Ud EwEB/wQIMAYBAf8CAQEwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNH DhpkLzCBpgYDVR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBvACAAZABlACAA bABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBlAGwAbwBuAGEAIAAwADgAMAAx ADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx 51tkljYyGOylMnfX40S2wBEqgLk9am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qk R71kMrv2JYSiJ0L1ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaP T481PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS3a/DTg4f Jl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5kSeTy36LssUzAKh3ntLFl osS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF3dvd6qJ2gHN99ZwExEWN57kci57q13XR crHedUTnQn3iV2t93Jm8PYMo6oCTjcVMZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoR saS8I8nkvof/uZS2+F0gStRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTD KCOM/iczQ0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQBjLMi 6Et8Vcad+qMUu2WFbm5PEn4KPJ2V -----END CERTIFICATE----- Izenpe.com ========== -----BEGIN CERTIFICATE----- MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4MQswCQYDVQQG EwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wHhcNMDcxMjEz MTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMu QS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ 03rKDx6sp4boFmVqscIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAK ClaOxdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6HLmYRY2xU +zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFXuaOKmMPsOzTFlUFpfnXC PCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQDyCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxT OTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbK F7jJeodWLBoBHmy+E60QrLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK 0GqfvEyNBjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8Lhij+ 0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIBQFqNeb+Lz0vPqhbB leStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+HMh3/1uaD7euBUbl8agW7EekFwID AQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2luZm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+ SVpFTlBFIFMuQS4gLSBDSUYgQTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBG NjIgUzgxQzBBBgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O BBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUAA4ICAQB4pgwWSp9MiDrAyw6l Fn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWblaQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbga kEyrkgPH7UIBzg/YsfqikuFgba56awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8q hT/AQKM6WfxZSzwoJNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Cs g1lwLDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCTVyvehQP5 aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGkLhObNA5me0mrZJfQRsN5 nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJbUjWumDqtujWTI6cfSN01RpiyEGjkpTHC ClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZo Q0iy2+tzJOeRf1SktoA+naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1Z WrOZyGlsQyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== -----END CERTIFICATE----- Chambers of Commerce Root - 2008 ================================ -----BEGIN CERTIFICATE----- MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYDVQQGEwJFVTFD MEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNv bS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMu QS4xKTAnBgNVBAMTIENoYW1iZXJzIG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4MB4XDTA4MDgwMTEy Mjk1MFoXDTM4MDczMTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNl ZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIwEAYDVQQF EwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJl cnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC AQCvAMtwNyuAWko6bHiUfaN/Gh/2NdW928sNRHI+JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKA XuFixrYp4YFs8r/lfTJqVKAyGVn+H4vXPWCGhSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorj h40G072QDuKZoRuGDtqaCrsLYVAGUvGef3bsyw/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR5gN/ ikilTWh1uem8nk4ZcfUyS5xtYBkL+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfLZEFHcpOrUMPrCXZk NNI5t3YRCQ12RcSprj1qr7V9ZS+UWBDsXHyvfuK2GNnQm05aSd+pZgvMPMZ4fKecHePOjlO+Bd5g D2vlGts/4+EhySnB8esHnFIbAURRPHsl18TlUlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331 lubKgdaX8ZSD6e2wsWsSaR6s+12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ 0wlf2eOKNcx5Wk0ZN5K3xMGtr/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhj ya6BXBg14JC7vjxPNyK5fuvPnnchpj04gftI2jE9K+OJ9dC1vX7gUMQSibMjmhAxhduub+84Mxh2 EQIDAQABo4IBbDCCAWgwEgYDVR0TAQH/BAgwBgEB/wIBDDAdBgNVHQ4EFgQU+SSsD7K1+HnA+mCI G8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU+SSsD7K1+HnA+mCIG8TZTQKeFxmhgbSkgbEwga4xCzAJ BgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNh bWVyZmlybWEuY29tL2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENh bWVyZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDiC CQCj2kJ+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUH AgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZIhvcNAQEFBQADggIBAJASryI1 wqM58C7e6bXpeHxIvj99RZJe6dqxGfwWPJ+0W2aeaufDuV2I6A+tzyMP3iU6XsxPpcG1Lawk0lgH 3qLPaYRgM+gQDROpI9CF5Y57pp49chNyM/WqfcZjHwj0/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbU RWpGqOt1glanq6B8aBMz9p0w8G8nOSQjKpD9kCk18pPfNKXG9/jvjA9iSnyu0/VU+I22mlaHFoI6 M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO+rcf+g769HsJtg1pDDFOqxXnrN2pSB7+R5KBWIBpih1 YJeSDW4+TTdDDZIVnBgizVGZoCkaPF+KMjNbMMeJL0eYD6MDxvbxrN8y8NmBGuScvfaAFPDRLLmF 9dijscilIeUcE5fuDr3fKanvNFNb0+RqE4QGtjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcK zBIKinmwPQN/aUv0NCB9szTqjktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvG nrDQWzilm1DefhiYtUU79nm06PcaewaD+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRg OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZd0jQ -----END CERTIFICATE----- Global Chambersign Root - 2008 ============================== -----BEGIN CERTIFICATE----- MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYDVQQGEwJFVTFD MEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNv bS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMu QS4xJzAlBgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODAeFw0wODA4MDExMjMx NDBaFw0zODA3MzExMjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUg Y3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJ QTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBD aGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDf VtPkOpt2RbQT2//BthmLN0EYlVJH6xedKYiONWwGMi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXf XjaOcNFccUMd2drvXNL7G706tcuto8xEpw2uIRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0 ZJJ0YPP2zxhPYLIj0Mc7zmFLmY/CDNBAspjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4ddPB /gBVsIdU6CeQNR1MM62X/JcumIS/LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyGHoiMvvKRhI9lNNgA TH23MRdaKXoKGCQwoze1eqkBfSbW+Q6OWfH9GzO1KTsXO0G2Id3UwD2ln58fQ1DJu7xsepeY7s2M H/ucUa6LcL0nn3HAa6x9kGbo1106DbDVwo3VyJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfe Ox2YItaswTXbo6Al/3K1dh3ebeksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSF HTynyQbehP9r6GsaPMWis0L7iwk+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9Jh wZG7SMA0j0GMS0zbaRL/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsogzCtLkykPAgMB AAGjggFqMIIBZjASBgNVHRMBAf8ECDAGAQH/AgEMMB0GA1UdDgQWBBS5CcqcHtvTbDprru1U8VuT BjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDprru1U8VuTBjUuXqGBsqSBrzCBrDELMAkGA1UE BhMCRVUxQzBBBgNVBAcTOk1hZHJpZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cuY2FtZXJm aXJtYS5jb20vYWRkcmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMSQUMgQ2FtZXJm aXJtYSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAtIDIwMDiCCQDJzdPp 1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0 dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZIhvcNAQEFBQADggIBAICIf3DekijZBZRG /5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZUohwEurdPfWbU1Rv4WCiqAm57OtZfMY18dwY6fFn5a+6 ReAJ3spED8IXDneRRXozX1+WLGiLwUePmJs9wOzL9dWCkoQ10b42OFZyMVtHLaoXpGNR6woBrX/s dZ7LoR/xfxKxueRkf2fWIyr0uDldmOghp+G9PUIadJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVza2Mg 9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJjUhjxsYjV5KTXjXBjfkK9yydYhz2rXzdpjEetrHH foUm+qRqtdpjMNHvkzeyZi99Bffnt0uYlDXA2TopwZ2yUDMdSqlapskD7+3056huirRXhOukP9Du qqqHW2Pok+JrqNS4cnhrG+055F3Lm6qH1U9OAP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETr P3iZ8ntxPjzxmKfFGBI/5rsoM0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVq c5iJWzouE4gev8CSlDQb4ye3ix5vQv/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z 09gwzxMNTxXJhLynSC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp/B -----END CERTIFICATE----- Go Daddy Root Certificate Authority - G2 ======================================== -----BEGIN CERTIFICATE----- MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoTEUdvRGFkZHkuY29tLCBJbmMu MTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8G A1UEAxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI hvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKDE6bFIEMBO4Tx5oVJnyfq 9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD +qK+ihVqf94Lw7YZFAXK6sOoBJQ7RnwyDfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutd fMh8+7ArU6SSYmlRJQVhGkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMl NAJWJwGRtDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEAAaNC MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFDqahQcQZyi27/a9 BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmXWWcDYfF+OwYxdS2hII5PZYe096ac vNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r 5N9ss4UXnT3ZJE95kTXWXwTrgIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYV N8Gb5DKj7Tjo2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI4uJEvlz36hz1 -----END CERTIFICATE----- Starfield Root Certificate Authority - G2 ========================================= -----BEGIN CERTIFICATE----- MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s b2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVsZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0 eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAw DgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQg VGVjaG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZpY2F0ZSBB dXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL3twQP89o/8ArFv W59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMgnLRJdzIpVv257IzdIvpy3Cdhl+72WoTs bhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNk N3mSwOxGXn/hbVNMYq/NHwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7Nf ZTD4p7dNdloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0HZbU JtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC AQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0GCSqGSIb3DQEBCwUAA4IBAQARWfol TwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjUsHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx 4mcujJUDJi5DnUox9g61DLu34jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUw F5okxBDgBPfg8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1mMpYjn0q7pBZ c2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 -----END CERTIFICATE----- Starfield Services Root Certificate Authority - G2 ================================================== -----BEGIN CERTIFICATE----- MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMxEDAOBgNVBAgT B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s b2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVsZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRl IEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNV BAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxT dGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2VydmljZXMg Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC AQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20pOsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2 h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm28xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4Pa hHQUw2eeBGg6345AWh1KTs9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLP LJGmpufehRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk6mFB rMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAwDwYDVR0TAQH/BAUw AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+qAdcwKziIorhtSpzyEZGDMA0GCSqG SIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMIbw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPP E95Dz+I0swSdHynVv/heyNXBve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTy xQGjhdByPq1zqwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn0q23KXB56jza YyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCNsSi6 -----END CERTIFICATE----- AffirmTrust Commercial ====================== -----BEGIN CERTIFICATE----- MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UEBhMCVVMxFDAS BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMB4XDTEw MDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEF AAOCAQ8AMIIBCgKCAQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6Eqdb DuKPHx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yrba0F8PrV C8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPALMeIrJmqbTFeurCA+ukV6 BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1yHp52UKqK39c/s4mT6NmgTWvRLpUHhww MmWd5jyTXlBOeuM61G7MGvv50jeuJCqrVwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNV HQ4EFgQUnZPGU4teyq8/nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC AQYwDQYJKoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYGXUPG hi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNjvbz4YYCanrHOQnDi qX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivtZ8SOyUOyXGsViQK8YvxO8rUzqrJv 0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9gN53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0kh sUlHRUe072o0EclNmsxZt9YCnlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8= -----END CERTIFICATE----- AffirmTrust Networking ====================== -----BEGIN CERTIFICATE----- MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UEBhMCVVMxFDAS BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMB4XDTEw MDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEF AAOCAQ8AMIIBCgKCAQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SE Hi3yYJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbuakCNrmreI dIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRLQESxG9fhwoXA3hA/Pe24 /PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gb h+0t+nvujArjqWaJGctB+d1ENmHP4ndGyH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNV HQ4EFgQUBx/S55zawm6iQLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC AQYwDQYJKoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfOtDIu UFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzuQY0x2+c06lkh1QF6 12S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZLgo/bNjR9eUJtGxUAArgFU2HdW23 WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4uolu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9 /ZFvgrG+CJPbFEfxojfHRZ48x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s= -----END CERTIFICATE----- AffirmTrust Premium =================== -----BEGIN CERTIFICATE----- MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UEBhMCVVMxFDAS BgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMB4XDTEwMDEy OTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRy dXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A MIICCgKCAgEAxBLfqV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtn BKAQJG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ+jjeRFcV 5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrSs8PhaJyJ+HoAVt70VZVs +7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmd GPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d770O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5R p9EixAqnOEhss/n/fauGV+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NI S+LI+H+SqHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S5u04 6uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4IaC1nEWTJ3s7xgaVY5 /bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TXOwF0lkLgAOIua+rF7nKsu7/+6qqo +Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYEFJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB /wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByv MiPIs0laUZx2KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B8OWycvpEgjNC 6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQMKSOyARiqcTtNd56l+0OOF6S L5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK +4w1IX2COPKpVJEZNZOUbWo6xbLQu4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmV BtWVyuEklut89pMFu+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFg IxpHYoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8GKa1qF60 g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaORtGdFNrHF+QFlozEJLUb zxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6eKeC2uAloGRwYQw== -----END CERTIFICATE----- AffirmTrust Premium ECC ======================= -----BEGIN CERTIFICATE----- MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMCVVMxFDASBgNV BAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQcmVtaXVtIEVDQzAeFw0xMDAx MjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1U cnVzdDEgMB4GA1UEAwwXQWZmaXJtVHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQA IgNiAAQNMF4bFZ0D0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQ N8O9ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0GA1UdDgQW BBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAK BggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/VsaobgxCd05DhT1wV/GzTjxi+zygk8N53X 57hG8f2h4nECMEJZh0PUUd+60wkyWs6Iflc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKM eQ== -----END CERTIFICATE----- Certum Trusted Network CA ========================= -----BEGIN CERTIFICATE----- MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlv biBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIy MTIwNzM3WhcNMjkxMjMxMTIwNzM3WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBU ZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 MSIwIAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC AQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rHUV+rpDKmYYe2bg+G0jAC l/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LMTXPb865Px1bVWqeWifrzq2jUI4ZZJ88J J7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVUBBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4 fOQtf/WsX+sWn7Et0brMkUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0 cvW0QM8xAcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNVHRMB Af8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNVHQ8BAf8EBAMCAQYw DQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15ysHhE49wcrwn9I0j6vSrEuVUEtRCj jSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfLI9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1 mS1FhIrlQgnXdAIv94nYmem8J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5aj Zt3hrvJBW8qYVoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI 03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= -----END CERTIFICATE----- TWCA Root Certification Authority ================================= -----BEGIN CERTIFICATE----- MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJ VEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlmaWNh dGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMzWhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQG EwJUVzESMBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NB IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK AoIBAQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFEAcK0HMMx QhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HHK3XLfJ+utdGdIzdjp9xC oi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeXRfwZVzsrb+RH9JlF/h3x+JejiB03HFyP 4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/zrX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1r y+UPizgN7gr8/g+YnzAx3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIB BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkqhkiG 9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeCMErJk/9q56YAf4lC mtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdlsXebQ79NqZp4VKIV66IIArB6nCWlW QtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62Dlhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVY T0bf+215WfKEIlKuD8z7fDvnaspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocny Yh0igzyXxfkZYiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== -----END CERTIFICATE----- Security Communication RootCA2 ============================== -----BEGIN CERTIFICATE----- MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDElMCMGA1UEChMc U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMeU2VjdXJpdHkgQ29tbXVuaWNh dGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoXDTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMC SlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3Vy aXR5IENvbW11bmljYXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB ANAVOVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGrzbl+dp++ +T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVMVAX3NuRFg3sUZdbcDE3R 3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQhNBqyjoGADdH5H5XTz+L62e4iKrFvlNV spHEfbmwhRkGeC7bYRr6hfVKkaHnFtWOojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1K EOtOghY6rCcMU/Gt1SSwawNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8 QIH4D5csOPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB CwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpFcoJxDjrSzG+ntKEj u/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXcokgfGT+Ok+vx+hfuzU7jBBJV1uXk 3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6q tnRGEmyR7jTV7JqR50S+kDFy1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29 mvVXIwAHIRc/SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 -----END CERTIFICATE----- EC-ACC ====== -----BEGIN CERTIFICATE----- MIIFVjCCBD6gAwIBAgIQ7is969Qh3hSoYqwE893EATANBgkqhkiG9w0BAQUFADCB8zELMAkGA1UE BhMCRVMxOzA5BgNVBAoTMkFnZW5jaWEgQ2F0YWxhbmEgZGUgQ2VydGlmaWNhY2lvIChOSUYgUS0w ODAxMTc2LUkpMSgwJgYDVQQLEx9TZXJ2ZWlzIFB1YmxpY3MgZGUgQ2VydGlmaWNhY2lvMTUwMwYD VQQLEyxWZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAoYykwMzE1MDMGA1UE CxMsSmVyYXJxdWlhIEVudGl0YXRzIGRlIENlcnRpZmljYWNpbyBDYXRhbGFuZXMxDzANBgNVBAMT BkVDLUFDQzAeFw0wMzAxMDcyMzAwMDBaFw0zMTAxMDcyMjU5NTlaMIHzMQswCQYDVQQGEwJFUzE7 MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8gKE5JRiBRLTA4MDExNzYt SSkxKDAmBgNVBAsTH1NlcnZlaXMgUHVibGljcyBkZSBDZXJ0aWZpY2FjaW8xNTAzBgNVBAsTLFZl Z2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQubmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLEyxKZXJh cnF1aWEgRW50aXRhdHMgZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAxMGRUMtQUND MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsyLHT+KXQpWIR4NA9h0X84NzJB5R85iK w5K4/0CQBXCHYMkAqbWUZRkiFRfCQ2xmRJoNBD45b6VLeqpjt4pEndljkYRm4CgPukLjbo73FCeT ae6RDqNfDrHrZqJyTxIThmV6PttPB/SnCWDaOkKZx7J/sxaVHMf5NLWUhdWZXqBIoH7nF2W4onW4 HvPlQn2v7fOKSGRdghST2MDk/7NQcvJ29rNdQlB50JQ+awwAvthrDk4q7D7SzIKiGGUzE3eeml0a E9jD2z3Il3rucO2n5nzbcc8tlGLfbdb1OL4/pYUKGbio2Al1QnDE6u/LDsg0qBIimAy4E5S2S+zw 0JDnJwIDAQABo4HjMIHgMB0GA1UdEQQWMBSBEmVjX2FjY0BjYXRjZXJ0Lm5ldDAPBgNVHRMBAf8E BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUoMOLRKo3pUW/l4Ba0fF4opvpXY0wfwYD VR0gBHgwdjB0BgsrBgEEAfV4AQMBCjBlMCwGCCsGAQUFBwIBFiBodHRwczovL3d3dy5jYXRjZXJ0 Lm5ldC92ZXJhcnJlbDA1BggrBgEFBQcCAjApGidWZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5l dC92ZXJhcnJlbCAwDQYJKoZIhvcNAQEFBQADggEBAKBIW4IB9k1IuDlVNZyAelOZ1Vr/sXE7zDkJ lF7W2u++AVtd0x7Y/X1PzaBB4DSTv8vihpw3kpBWHNzrKQXlxJ7HNd+KDM3FIUPpqojlNcAZQmNa Al6kSBg6hW/cnbw/nZzBh7h6YQjpdwt/cKt63dmXLGQehb+8dJahw3oS7AwaboMMPOhyRp/7SNVe l+axofjk70YllJyJ22k4vuxcDlbHZVHlUIiIv0LVKz3l+bqeLrPK9HOSAgu+TGbrIP65y7WZf+a2 E/rKS03Z7lNGBjvGTq2TWoF+bCpLagVFjPIhpDGQh2xlnJ2lYJU6Un/10asIbvPuW/mIPX64b24D 5EI= -----END CERTIFICATE----- Hellenic Academic and Research Institutions RootCA 2011 ======================================================= -----BEGIN CERTIFICATE----- MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1IxRDBCBgNVBAoT O0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9y aXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25z IFJvb3RDQSAyMDExMB4XDTExMTIwNjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYT AkdSMUQwQgYDVQQKEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25z IENlcnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNo IEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB AKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPzdYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI 1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJfel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa 71HFK9+WXesyHgLacEnsbgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u 8yBRQlqD75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSPFEDH 3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNVHRMBAf8EBTADAQH/ MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp5dgTBCPuQSUwRwYDVR0eBEAwPqA8 MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQub3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQu b3JnMA0GCSqGSIb3DQEBBQUAA4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVt XdMiKahsog2p6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8 TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7dIsXRSZMFpGD /md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8AcysNnq/onN694/BtZqhFLKPM58N 7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXIl7WdmplNsDz4SgCbZN2fOUvRJ9e4 -----END CERTIFICATE----- Actalis Authentication Root CA ============================== -----BEGIN CERTIFICATE----- MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCSVQxDjAM BgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UE AwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDky MjExMjIwMlowazELMAkGA1UEBhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlz IFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNvUTufClrJ wkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX4ay8IMKx4INRimlNAJZa by/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9KK3giq0itFZljoZUj5NDKd45RnijMCO6 zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1f YVEiVRvjRuPjPdA1YprbrxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2 oxgkg4YQ51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2Fbe8l EfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxeKF+w6D9Fz8+vm2/7 hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4Fv6MGn8i1zeQf1xcGDXqVdFUNaBr8 EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbnfpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5 jF66CyCU3nuDuP/jVo23Eek7jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLY iDrIn3hm7YnzezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQALe3KHwGCmSUyI WOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70jsNjLiNmsGe+b7bAEzlgqqI0 JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDzWochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKx K3JCaKygvU5a2hi/a5iB0P2avl4VSM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+ Xlff1ANATIGk0k9jpwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC 4yyXX04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+OkfcvHlXHo 2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7RK4X9p2jIugErsWx0Hbhz lefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btUZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXem OR/qnuOf0GZvBeyqdn6/axag67XH/JJULysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9 vwGYT7JZVEc+NHt4bVaTLnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== -----END CERTIFICATE----- Buypass Class 2 Root CA ======================= -----BEGIN CERTIFICATE----- MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMiBSb290IENBMB4X DTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1owTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1 eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIw DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1 g1Lr6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPVL4O2fuPn 9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC911K2GScuVr1QGbNgGE41b /+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHxMlAQTn/0hpPshNOOvEu/XAFOBz3cFIqU CqTqc/sLUegTBxj6DvEr0VQVfTzh97QZQmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeff awrbD02TTqigzXsu8lkBarcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgI zRFo1clrUs3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLiFRhn Bkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRSP/TizPJhk9H9Z2vX Uq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN9SG9dKpN6nIDSdvHXx1iY8f93ZHs M+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxPAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD VR0OBBYEFMmAd+BikoL1RpzzuvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF AAOCAgEAU18h9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3tOluwlN5E40EI osHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo+fsicdl9sz1Gv7SEr5AcD48S aq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYd DnkM/crqJIByw5c/8nerQyIKx+u2DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWD LfJ6v9r9jv6ly0UsH8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0 oyLQI+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK75t98biGC wWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h3PFaTWwyI0PurKju7koS CTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPzY11aWOIv4x3kqdbQCtCev9eBCfHJxyYN rJgWVqA= -----END CERTIFICATE----- Buypass Class 3 Root CA ======================= -----BEGIN CERTIFICATE----- MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMyBSb290IENBMB4X DTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFowTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1 eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIw DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRH sJ8YZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3EN3coTRiR 5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9tznDDgFHmV0ST9tD+leh 7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX0DJq1l1sDPGzbjniazEuOQAnFN44wOwZ ZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH 2xc519woe2v1n/MuwU8XKhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV /afmiSTYzIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvSO1UQ RwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D34xFMFbG02SrZvPA Xpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgPK9Dx2hzLabjKSWJtyNBjYt1gD1iq j6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD VR0OBBYEFEe4zf/lb+74suwvTg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF AAOCAgEAACAjQTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXSIGrs/CIBKM+G uIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2HJLw5QY33KbmkJs4j1xrG0aG Q0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsaO5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8 ZORK15FTAaggiG6cX0S5y2CBNOxv033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2 KSb12tjE8nVhz36udmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz 6MkEkbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg413OEMXbug UZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvDu79leNKGef9JOxqDDPDe eOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq4/g7u9xN12TyUb7mqqta6THuBrxzvxNi Cp/HuZc= -----END CERTIFICATE----- T-TeleSec GlobalRoot Class 3 ============================ -----BEGIN CERTIFICATE----- MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgx MDAxMTAyOTU2WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0GCSqGSIb3 DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN8ELg63iIVl6bmlQdTQyK 9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/RLyTPWGrTs0NvvAgJ1gORH8EGoel15YU NpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZF iP0Zf3WHHx+xGwpzJFu5ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W 0eDrXltMEnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGjQjBA MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1A/d2O2GCahKqGFPr AyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOyWL6ukK2YJ5f+AbGwUgC4TeQbIXQb fsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzT ucpH9sry9uetuUg/vBa3wW306gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7h P0HHRwA11fXT91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4pTpPDpFQUWw== -----END CERTIFICATE----- D-TRUST Root Class 3 CA 2 2009 ============================== -----BEGIN CERTIFICATE----- MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQK DAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTAe Fw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NThaME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxE LVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIw DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOAD ER03UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42tSHKXzlA BF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9RySPocq60vFYJfxLLHLGv KZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsMlFqVlNpQmvH/pStmMaTJOKDfHR+4CS7z p+hnUquVH+BGPtikw8paxTGA6Eian5Rp/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUC AwEAAaOCARowggEWMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ 4PGEMA4GA1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVjdG9y eS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUyMENBJTIwMiUyMDIw MDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRlcmV2b2NhdGlvbmxpc3QwQ6BBoD+G PWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAw OS5jcmwwDQYJKoZIhvcNAQELBQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm 2H6NMLVwMeniacfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4KzCUqNQT4YJEV dT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8PIWmawomDeCTmGCufsYkl4ph X5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3YJohw1+qRzT65ysCQblrGXnRl11z+o+I= -----END CERTIFICATE----- D-TRUST Root Class 3 CA 2 EV 2009 ================================= -----BEGIN CERTIFICATE----- MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw OTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUwNDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw OTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfS egpnljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM03TP1YtHh zRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6ZqQTMFexgaDbtCHu39b+T 7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lRp75mpoo6Kr3HGrHhFPC+Oh25z1uxav60 sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure35 11H3a6UCAwEAAaOCASQwggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyv cop9NteaHNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFwOi8v ZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xhc3MlMjAzJTIwQ0El MjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1ERT9jZXJ0aWZpY2F0ZXJldm9jYXRp b25saXN0MEagRKBChkBodHRwOi8vd3d3LmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xh c3NfM19jYV8yX2V2XzIwMDkuY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+ PPoeUSbrh/Yp3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNFCSuGdXzfX2lX ANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7naxpeG0ILD5EJt/rDiZE4OJudA NCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqXKVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVv w9y4AyHqnxbxLFS1 -----END CERTIFICATE----- CA Disig Root R2 ================ -----BEGIN CERTIFICATE----- MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNVBAYTAlNLMRMw EQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNp ZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQyMDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sx EzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERp c2lnIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbC w3OeNcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNHPWSb6Wia xswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3Ix2ymrdMxp7zo5eFm1tL7 A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbeQTg06ov80egEFGEtQX6sx3dOy1FU+16S GBsEWmjGycT6txOgmLcRK7fWV8x8nhfRyyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqV g8NTEQxzHQuyRpDRQjrOQG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa 5Beny912H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJQfYE koopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUDi/ZnWejBBhG93c+A Ak9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORsnLMOPReisjQS1n6yqEm70XooQL6i Fh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNV HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5u Qu0wDQYJKoZIhvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqfGopTpti72TVV sRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkblvdhuDvEK7Z4bLQjb/D907Je dR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka+elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W8 1k/BfDxujRNt+3vrMNDcTa/F1balTFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjx mHHEt38OFdAlab0inSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01 utI3gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18DrG5gPcFw0 sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3OszMOl6W8KjptlwlCFtaOg UxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8xL4ysEr3vQCj8KWefshNPZiTEUxnpHikV 7+ZtsH8tZ/3zbBt1RqPlShfppNcL -----END CERTIFICATE----- ACCVRAIZ1 ========= -----BEGIN CERTIFICATE----- MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UEAwwJQUNDVlJB SVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQswCQYDVQQGEwJFUzAeFw0xMTA1 MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQBgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwH UEtJQUNDVjENMAsGA1UECgwEQUNDVjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4IC DwAwggIKAoICAQCbqau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gM jmoYHtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWoG2ioPej0 RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpAlHPrzg5XPAOBOp0KoVdD aaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhrIA8wKFSVf+DuzgpmndFALW4ir50awQUZ 0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDG WuzndN9wrqODJerWx5eHk6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs7 8yM2x/474KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMOm3WR 5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpacXpkatcnYGMN285J 9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPluUsXQA+xtrn13k/c4LOsOxFwYIRK Q26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYIKwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRw Oi8vd3d3LmFjY3YuZXMvZmlsZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEu Y3J0MB8GCCsGAQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeTVfZW6oHlNsyM Hj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIGCCsGAQUFBwICMIIBFB6CARAA QQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUAcgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBh AO0AegAgAGQAZQAgAGwAYQAgAEEAQwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUA YwBuAG8AbABvAGcA7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBj AHQAcgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAAQwBQAFMA IABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUAczAwBggrBgEFBQcCARYk aHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2MuaHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0 dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRtaW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2 MV9kZXIuY3JsMA4GA1UdDwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZI hvcNAQEFBQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdpD70E R9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gUJyCpZET/LtZ1qmxN YEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+mAM/EKXMRNt6GGT6d7hmKG9Ww7Y49 nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepDvV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJ TS+xJlsndQAJxGJ3KQhfnlmstn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3 sCPdK6jT2iWH7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szAh1xA2syVP1Xg Nce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xFd3+YJ5oyXSrjhO7FmGYvliAd 3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2HpPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3p EfbRD0tVNEYqi4Y7 -----END CERTIFICATE----- TWCA Global Root CA =================== -----BEGIN CERTIFICATE----- MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcxEjAQBgNVBAoT CVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMTVFdDQSBHbG9iYWwgUm9vdCBD QTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQK EwlUQUlXQU4tQ0ExEDAOBgNVBAsTB1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3Qg Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2C nJfF10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz0ALfUPZV r2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfChMBwqoJimFb3u/Rk28OKR Q4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbHzIh1HrtsBv+baz4X7GGqcXzGHaL3SekV tTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1W KKD+u4ZqyPpcC1jcxkt2yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99 sy2sbZCilaLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYPoA/p yJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQABDzfuBSO6N+pjWxn kjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcEqYSjMq+u7msXi7Kx/mzhkIyIqJdI zshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMC AQYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6g cFGn90xHNcgL1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WFH6vPNOw/KP4M 8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNoRI2T9GRwoD2dKAXDOXC4Ynsg /eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlg lPx4mI88k1HtQJAH32RjJMtOcQWh15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryP A9gK8kxkRr05YuWW6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3m i4TWnsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5jwa19hAM8 EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWzaGHQRiapIVJpLesux+t3 zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmyKwbQBM0= -----END CERTIFICATE----- TeliaSonera Root CA v1 ====================== -----BEGIN CERTIFICATE----- MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAwNzEUMBIGA1UE CgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJvb3QgQ0EgdjEwHhcNMDcxMDE4 MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYDVQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwW VGVsaWFTb25lcmEgUm9vdCBDQSB2MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+ 6yfwIaPzaSZVfp3FVRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA 3GV17CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+XZ75Ljo1k B1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+/jXh7VB7qTCNGdMJjmhn Xb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxH oLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkmdtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3 F0fUTPHSiXk+TT2YqGHeOh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJ oWjiUIMusDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4pgd7 gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fsslESl1MpWtTwEhDc TwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQarMCpgKIv7NHfirZ1fpoeDVNAgMB AAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qW DNXr+nuqF+gTEjANBgkqhkiG9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNm zqjMDfz1mgbldxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx 0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1TjTQpgcmLNkQfW pb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBedY2gea+zDTYa4EzAvXUYNR0PV G6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpc c41teyWRyu5FrgZLAMzTsVlQ2jqIOylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOT JsjrDNYmiLbAJM+7vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2 qReWt88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcnHL/EVlP6 Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVxSK236thZiNSQvxaz2ems WWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= -----END CERTIFICATE----- E-Tugra Certification Authority =============================== -----BEGIN CERTIFICATE----- MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNVBAYTAlRSMQ8w DQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamls ZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMw NTEyMDk0OFoXDTIzMDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmEx QDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxl cmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQD DB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A MIICCgKCAgEA4vU/kwVRHoViVF56C/UYB4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vd hQd2h8y/L5VMzH2nPbxHD5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5K CKpbknSFQ9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEoq1+g ElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3Dk14opz8n8Y4e0ypQ BaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcHfC425lAcP9tDJMW/hkd5s3kc91r0 E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsutdEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gz rt48Ue7LE3wBf4QOXVGUnhMMti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAq jqFGOjGY5RH8zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUXU8u3Zg5mTPj5 dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6Jyr+zE7S6E5UMA8GA1UdEwEB /wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEG MA0GCSqGSIb3DQEBCwUAA4ICAQAFNzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAK kEh47U6YA5n+KGCRHTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jO XKqYGwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c77NCR807 VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3+GbHeJAAFS6LrVE1Uweo a2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WKvJUawSg5TB9D0pH0clmKuVb8P7Sd2nCc dlqMQ1DujjByTd//SffGqWfZbawCEeI6FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEV KV0jq9BgoRJP3vQXzTLlyb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gT Dx4JnW2PAJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpDy4Q0 8ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8dNL/+I5c30jn6PQ0G C7TbO6Orb1wdtn7os4I07QZcJA== -----END CERTIFICATE----- T-TeleSec GlobalRoot Class 2 ============================ -----BEGIN CERTIFICATE----- MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgx MDAxMTA0MDE0WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0GCSqGSIb3 DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUdAqSzm1nzHoqvNK38DcLZ SBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiCFoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/F vudocP05l03Sx5iRUKrERLMjfTlH6VJi1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx970 2cu+fjOlbpSD8DT6IavqjnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGV WOHAD3bZwI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGjQjBA MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/WSA2AHmgoCJrjNXy YdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhyNsZt+U2e+iKo4YFWz827n+qrkRk4 r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPACuvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNf vNoBYimipidx5joifsFvHZVwIEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR 3p1m0IvVVGb6g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN 9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlPBSeOE6Fuwg== -----END CERTIFICATE----- Atos TrustedRoot 2011 ===================== -----BEGIN CERTIFICATE----- MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UEAwwVQXRvcyBU cnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0xMTA3MDcxNDU4 MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMMFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsG A1UECgwEQXRvczELMAkGA1UEBhMCREUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCV hTuXbyo7LjvPpvMpNb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr 54rMVD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+SZFhyBH+ DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ4J7sVaE3IqKHBAUsR320 HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0Lcp2AMBYHlT8oDv3FdU9T1nSatCQujgKR z3bFmx5VdJx4IbHwLfELn8LVlhgf8FQieowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7R l+lwrrw7GWzbITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZ bNshMBgGA1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB CwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8jvZfza1zv7v1Apt+h k6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kPDpFrdRbhIfzYJsdHt6bPWHJxfrrh TZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pcmaHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a9 61qn8FYiqTxlVMYVqL2Gns2Dlmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G 3mB/ufNPRJLvKrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed -----END CERTIFICATE----- QuoVadis Root CA 1 G3 ===================== -----BEGIN CERTIFICATE----- MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQELBQAwSDELMAkG A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv b3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJN MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEg RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakE PBtVwedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWerNrwU8lm PNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF34168Xfuw6cwI2H44g4hWf6 Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh4Pw5qlPafX7PGglTvF0FBM+hSo+LdoIN ofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXpUhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/l g6AnhF4EwfWQvTA9xO+oabw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV 7qJZjqlc3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/GKubX 9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSthfbZxbGL0eUQMk1f iyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KOTk0k+17kBL5yG6YnLUlamXrXXAkg t3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOtzCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZI hvcNAQELBQADggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2cDMT/uFPpiN3 GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUNqXsCHKnQO18LwIE6PWThv6ct Tr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP +V04ikkwj+3x6xn0dxoxGE1nVGwvb2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh 3jRJjehZrJ3ydlo28hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fa wx/kNSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNjZgKAvQU6 O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhpq1467HxpvMc7hU6eFbm0 FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFtnh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOV hMJKzRwuJIczYOXD -----END CERTIFICATE----- QuoVadis Root CA 2 G3 ===================== -----BEGIN CERTIFICATE----- MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQELBQAwSDELMAkG A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv b3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJN MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIg RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFh ZiFfqq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMWn4rjyduY NM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ymc5GQYaYDFCDy54ejiK2t oIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+O7q414AB+6XrW7PFXmAqMaCvN+ggOp+o MiwMzAkd056OXbxMmO7FGmh77FOm6RQ1o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+l V0POKa2Mq1W/xPtbAd0jIaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZo L1NesNKqIcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz8eQQ sSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43ehvNURG3YBZwjgQQvD 6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l7ZizlWNof/k19N+IxWA1ksB8aRxh lRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALGcC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTAD AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZI hvcNAQELBQADggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RCroijQ1h5fq7K pVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0GaW/ZZGYjeVYg3UQt4XAoeo0L9 x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4nlv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgz dWqTHBLmYF5vHX/JHyPLhGGfHoJE+V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6X U/IyAgkwo1jwDQHVcsaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+Nw mNtddbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNgKCLjsZWD zYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeMHVOyToV7BjjHLPj4sHKN JeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4WSr2Rz0ZiC3oheGe7IUIarFsNMkd7Egr O3jtZsSOeWmD3n+M -----END CERTIFICATE----- QuoVadis Root CA 3 G3 ===================== -----BEGIN CERTIFICATE----- MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQELBQAwSDELMAkG A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv b3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJN MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMg RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286 IxSR/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNuFoM7pmRL Mon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXRU7Ox7sWTaYI+FrUoRqHe 6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+cra1AdHkrAj80//ogaX3T7mH1urPnMNA3 I4ZyYUUpSFlob3emLoG+B01vr87ERRORFHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3U VDmrJqMz6nWB2i3ND0/kA9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f7 5li59wzweyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634RylsSqi Md5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBpVzgeAVuNVejH38DM dyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0QA4XN8f+MFrXBsj6IbGB/kE+V9/Yt rQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZI hvcNAQELBQADggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnIFUBhynLWcKzS t/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5WvvoxXqA/4Ti2Tk08HS6IT7SdEQ TXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFgu/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9Du DcpmvJRPpq3t/O5jrFc/ZSXPsoaP0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGib Ih6BJpsQBJFxwAYf3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmD hPbl8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+DhcI00iX 0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HNPlopNLk9hM6xZdRZkZFW dSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ywaZWWDYWGWVjUTR939+J399roD1B0y2 PpxxVJkES/1Y+Zj0 -----END CERTIFICATE----- DigiCert Assured ID Root G2 =========================== -----BEGIN CERTIFICATE----- MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQG EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgw MTE1MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIw ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSAn61UQbVH 35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4HteccbiJVMWWXvdMX0h5i89vq bFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9HpEgjAALAcKxHad3A2m67OeYfcgnDmCXRw VWmvo2ifv922ebPynXApVfSr/5Vh88lAbx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OP YLfykqGxvYmJHzDNw6YuYjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+Rn lTGNAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTO w0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPIQW5pJ6d1Ee88hjZv 0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I0jJmwYrA8y8678Dj1JGG0VDjA9tz d29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4GnilmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAW hsI6yLETcDbYz+70CjTVW0z9B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0M jomZmWzwPDCvON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo IhNzbM8m9Yop5w== -----END CERTIFICATE----- DigiCert Assured ID Root G3 =========================== -----BEGIN CERTIFICATE----- MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYD VQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1 MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQ BgcqhkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJfZn4f5dwb RXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17QRSAPWXYQ1qAk8C3eNvJs KTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgF UaFNN6KDec6NHSrkhDAKBggqhkjOPQQDAwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5Fy YZ5eEJJZVrmDxxDnOOlYJjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy 1vUhZscv6pZjamVFkpUBtA== -----END CERTIFICATE----- DigiCert Global Root G2 ======================= -----BEGIN CERTIFICATE----- MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQG EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUx MjAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkq hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI2/Ou8jqJ kTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx1x7e/dfgy5SDN67sH0NO 3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQq2EGnI/yuum06ZIya7XzV+hdG82MHauV BJVJ8zUtluNJbd134/tJS7SsVQepj5WztCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyM UNGPHgm+F6HmIcr9g+UQvIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQAB o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV5uNu 5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY1Yl9PMWLSn/pvtsr F9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4NeF22d+mQrvHRAiGfzZ0JFrabA0U WTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NGFdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBH QRFXGU7Aj64GxJUTFy8bJZ918rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/ iyK5S9kJRaTepLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl MrY= -----END CERTIFICATE----- DigiCert Global Root G3 ======================= -----BEGIN CERTIFICATE----- MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQswCQYDVQQGEwJV UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYD VQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAw MDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5k aWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0C AQYFK4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FGfp4tn+6O YwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPOZ9wj/wMco+I+o0IwQDAP BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNp Yim8S8YwCgYIKoZIzj0EAwMDaAAwZQIxAK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y 3maTD/HMsQmP3Wyr+mt/oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34 VOKa5Vt8sycX -----END CERTIFICATE----- DigiCert Trusted Root G4 ======================== -----BEGIN CERTIFICATE----- MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQG EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEw HwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1 MTIwMDAwWjBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0G CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3yithZwuEp pz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1Ifxp4VpX6+n6lXFllVcq9o k3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7Fsa vOvJz82sNEBfsXpm7nfISKhmV1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGY QJB5w3jHtrHEtWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6 MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCiEhtm mnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADMfRyVw4/3IbKyEbe7 f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFH dL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8 oR7FwI+isX4KJpn15GkvmB0t9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud DwEB/wQEAwIBhjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2SV1EY+CtnJYY ZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd+SeuMIW59mdNOj6PWTkiU0Tr yF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWcfFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy 7zBZLq7gcfJW5GqXb5JQbZaNaHqasjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iah ixTXTBmyUEFxPT9NcCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN 5r5N0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie4u1Ki7wb /UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mIr/OSmbaz5mEP0oUA51Aa 5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tK G48BtieVU+i2iW1bvGjUI+iLUaJW+fCmgKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP 82Z+ -----END CERTIFICATE----- COMODO RSA Certification Authority ================================== -----BEGIN CERTIFICATE----- MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCBhTELMAkGA1UE BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlv biBBdXRob3JpdHkwHhcNMTAwMTE5MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMC R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBB dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR6FSS0gpWsawNJN3Fz0Rn dJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8Xpz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZ FGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+ 5eNu/Nio5JIk2kNrYrhV/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pG x8cgoLEfZd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z+pUX 2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7wqP/0uK3pN/u6uPQL OvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZahSL0896+1DSJMwBGB7FY79tOi4lu3 sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVICu9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+C GCe01a60y1Dma/RMhnEw6abfFobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5 WdYgGq/yapiqcrxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w DQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvlwFTPoCWOAvn9sKIN9SCYPBMt rFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+ nq6PK7o9mfjYcwlYRm6mnPTXJ9OV2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSg tZx8jb8uk2IntznaFxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwW sRqZCuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiKboHGhfKp pC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmckejkk9u+UJueBPSZI9FoJA zMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yLS0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHq ZJx64SIDqZxubw5lT2yHh17zbqD5daWbQOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk52 7RH89elWsn2/x20Kk4yl0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7I LaZRfyHBNVOFBkpdn627G190 -----END CERTIFICATE----- USERTrust RSA Certification Authority ===================================== -----BEGIN CERTIFICATE----- MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCBiDELMAkGA1UE BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh dGlvbiBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UE BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCAEmUXNg7D2wiz 0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2j Y0K2dvKpOyuR+OJv0OwWIJAJPuLodMkYtJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFn RghRy4YUVD+8M/5+bJz/Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O +T23LLb2VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT79uq /nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6c0Plfg6lZrEpfDKE Y1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmTYo61Zs8liM2EuLE/pDkP2QKe6xJM lXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97lc6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8 yexDJtC/QV9AqURE9JnnV4eeUB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+ eLf8ZxXhyVeEHg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF MAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPFUp/L+M+ZBn8b2kMVn54CVVeW FPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KOVWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ 7l8wXEskEVX/JJpuXior7gtNn3/3ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQ Eg9zKC7F4iRO/Fjs8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM 8WcRiQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYzeSf7dNXGi FSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZXHlKYC6SQK5MNyosycdi yA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9c J2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRBVXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGw sAvgnEzDHNb842m1R0aBL6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gx Q+6IHdfGjjxDah2nGN59PRbxYvnKkKj9 -----END CERTIFICATE----- USERTrust ECC Certification Authority ===================================== -----BEGIN CERTIFICATE----- MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDELMAkGA1UEBhMC VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv biBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMC VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqfloI+d61SRvU8Za2EurxtW2 0eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinngo4N+LZfQYcTxmdwlkWOrfzCjtHDix6Ez nPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0GA1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNV HQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBB HU6+4WMBzzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbWRNZu 9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= -----END CERTIFICATE----- GlobalSign ECC Root CA - R4 =========================== -----BEGIN CERTIFICATE----- MIIB4TCCAYegAwIBAgIRKjikHJYKBN5CsiilC+g0mAIwCgYIKoZIzj0EAwIwUDEkMCIGA1UECxMb R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD EwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoXDTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMb R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD EwpHbG9iYWxTaWduMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMZ5049sJQ6fLjkZHAOkrprl OQcJFspjsbmG+IpXwVfOQvpzofdlQv8ewQCybnMO/8ch5RikqtlxP6jUuc6MHaNCMEAwDgYDVR0P AQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFSwe61FuOJAf/sKbvu+M8k8o4TV MAoGCCqGSM49BAMCA0gAMEUCIQDckqGgE6bPA7DmxCGXkPoUVy0D7O48027KqGx2vKLeuwIgJ6iF JzWbVsaj8kfSt24bAgAXqmemFZHe+pTsewv4n4Q= -----END CERTIFICATE----- GlobalSign ECC Root CA - R5 =========================== -----BEGIN CERTIFICATE----- MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEkMCIGA1UECxMb R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD EwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoXDTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMb R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD EwpHbG9iYWxTaWduMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6 SFkc8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8kehOvRnkmS h5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd BgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYIKoZIzj0EAwMDaAAwZQIxAOVpEslu28Yx uglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7 yFz9SO8NdCKoCOJuxUnOxwy8p2Fp8fc74SrL+SvzZpA3 -----END CERTIFICATE----- Staat der Nederlanden Root CA - G3 ================================== -----BEGIN CERTIFICATE----- MIIFdDCCA1ygAwIBAgIEAJiiOTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJOTDEeMBwGA1UE CgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFhdCBkZXIgTmVkZXJsYW5kZW4g Um9vdCBDQSAtIEczMB4XDTEzMTExNDExMjg0MloXDTI4MTExMzIzMDAwMFowWjELMAkGA1UEBhMC TkwxHjAcBgNVBAoMFVN0YWF0IGRlciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5l ZGVybGFuZGVuIFJvb3QgQ0EgLSBHMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL4y olQPcPssXFnrbMSkUeiFKrPMSjTysF/zDsccPVMeiAho2G89rcKezIJnByeHaHE6n3WWIkYFsO2t x1ueKt6c/DrGlaf1F2cY5y9JCAxcz+bMNO14+1Cx3Gsy8KL+tjzk7FqXxz8ecAgwoNzFs21v0IJy EavSgWhZghe3eJJg+szeP4TrjTgzkApyI/o1zCZxMdFyKJLZWyNtZrVtB0LrpjPOktvA9mxjeM3K Tj215VKb8b475lRgsGYeCasH/lSJEULR9yS6YHgamPfJEf0WwTUaVHXvQ9Plrk7O53vDxk5hUUur mkVLoR9BvUhTFXFkC4az5S6+zqQbwSmEorXLCCN2QyIkHxcE1G6cxvx/K2Ya7Irl1s9N9WMJtxU5 1nus6+N86U78dULI7ViVDAZCopz35HCz33JvWjdAidiFpNfxC95DGdRKWCyMijmev4SH8RY7Ngzp 07TKbBlBUgmhHbBqv4LvcFEhMtwFdozL92TkA1CvjJFnq8Xy7ljY3r735zHPbMk7ccHViLVlvMDo FxcHErVc0qsgk7TmgoNwNsXNo42ti+yjwUOH5kPiNL6VizXtBznaqB16nzaeErAMZRKQFWDZJkBE 41ZgpRDUajz9QdwOWke275dhdU/Z/seyHdTtXUmzqWrLZoQT1Vyg3N9udwbRcXXIV2+vD3dbAgMB AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRUrfrHkleu yjWcLhL75LpdINyUVzANBgkqhkiG9w0BAQsFAAOCAgEAMJmdBTLIXg47mAE6iqTnB/d6+Oea31BD U5cqPco8R5gu4RV78ZLzYdqQJRZlwJ9UXQ4DO1t3ApyEtg2YXzTdO2PCwyiBwpwpLiniyMMB8jPq KqrMCQj3ZWfGzd/TtiunvczRDnBfuCPRy5FOCvTIeuXZYzbB1N/8Ipf3YF3qKS9Ysr1YvY2WTxB1 v0h7PVGHoTx0IsL8B3+A3MSs/mrBcDCw6Y5p4ixpgZQJut3+TcCDjJRYwEYgr5wfAvg1VUkvRtTA 8KCWAg8zxXHzniN9lLf9OtMJgwYh/WA9rjLA0u6NpvDntIJ8CsxwyXmA+P5M9zWEGYox+wrZ13+b 8KKaa8MFSu1BYBQw0aoRQm7TIwIEC8Zl3d1Sd9qBa7Ko+gE4uZbqKmxnl4mUnrzhVNXkanjvSr0r mj1AfsbAddJu+2gw7OyLnflJNZoaLNmzlTnVHpL3prllL+U9bTpITAjc5CgSKL59NVzq4BZ+Extq 1z7XnvwtdbLBFNUjA9tbbws+eC8N3jONFrdI54OagQ97wUNNVQQXOEpR1VmiiXTTn74eS9fGbbeI JG9gkaSChVtWQbzQRKtqE77RLFi3EjNYsjdj3BP1lB0/QFH1T/U67cjF68IeHRaVesd+QnGTbksV tzDfqu1XhUisHWrdOWnk4Xl4vs4Fv6EM94B7IWcnMFk= -----END CERTIFICATE----- Staat der Nederlanden EV Root CA ================================ -----BEGIN CERTIFICATE----- MIIFcDCCA1igAwIBAgIEAJiWjTANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJOTDEeMBwGA1UE CgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSkwJwYDVQQDDCBTdGFhdCBkZXIgTmVkZXJsYW5kZW4g RVYgUm9vdCBDQTAeFw0xMDEyMDgxMTE5MjlaFw0yMjEyMDgxMTEwMjhaMFgxCzAJBgNVBAYTAk5M MR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xKTAnBgNVBAMMIFN0YWF0IGRlciBOZWRl cmxhbmRlbiBFViBSb290IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA48d+ifkk SzrSM4M1LGns3Amk41GoJSt5uAg94JG6hIXGhaTK5skuU6TJJB79VWZxXSzFYGgEt9nCUiY4iKTW O0Cmws0/zZiTs1QUWJZV1VD+hq2kY39ch/aO5ieSZxeSAgMs3NZmdO3dZ//BYY1jTw+bbRcwJu+r 0h8QoPnFfxZpgQNH7R5ojXKhTbImxrpsX23Wr9GxE46prfNeaXUmGD5BKyF/7otdBwadQ8QpCiv8 Kj6GyzyDOvnJDdrFmeK8eEEzduG/L13lpJhQDBXd4Pqcfzho0LKmeqfRMb1+ilgnQ7O6M5HTp5gV XJrm0w912fxBmJc+qiXbj5IusHsMX/FjqTf5m3VpTCgmJdrV8hJwRVXj33NeN/UhbJCONVrJ0yPr 08C+eKxCKFhmpUZtcALXEPlLVPxdhkqHz3/KRawRWrUgUY0viEeXOcDPusBCAUCZSCELa6fS/ZbV 0b5GnUngC6agIk440ME8MLxwjyx1zNDFjFE7PZQIZCZhfbnDZY8UnCHQqv0XcgOPvZuM5l5Tnrmd 74K74bzickFbIZTTRTeU0d8JOV3nI6qaHcptqAqGhYqCvkIH1vI4gnPah1vlPNOePqc7nvQDs/nx fRN0Av+7oeX6AHkcpmZBiFxgV6YuCcS6/ZrPpx9Aw7vMWgpVSzs4dlG4Y4uElBbmVvMCAwEAAaNC MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFP6rAJCYniT8qcwa ivsnuL8wbqg7MA0GCSqGSIb3DQEBCwUAA4ICAQDPdyxuVr5Os7aEAJSrR8kN0nbHhp8dB9O2tLsI eK9p0gtJ3jPFrK3CiAJ9Brc1AsFgyb/E6JTe1NOpEyVa/m6irn0F3H3zbPB+po3u2dfOWBfoqSmu c0iH55vKbimhZF8ZE/euBhD/UcabTVUlT5OZEAFTdfETzsemQUHSv4ilf0X8rLiltTMMgsT7B/Zq 5SWEXwbKwYY5EdtYzXc7LMJMD16a4/CrPmEbUCTCwPTxGfARKbalGAKb12NMcIxHowNDXLldRqAN b/9Zjr7dn3LDWyvfjFvO5QxGbJKyCqNMVEIYFRIYvdr8unRu/8G2oGTYqV9Vrp9canaW2HNnh/tN f1zuacpzEPuKqf2evTY4SUmH9A4U8OmHuD+nT3pajnnUk+S7aFKErGzp85hwVXIy+TSrK0m1zSBi 5Dp6Z2Orltxtrpfs/J92VoguZs9btsmksNcFuuEnL5O7Jiqik7Ab846+HUCjuTaPPoIaGl6I6lD4 WeKDRikL40Rc4ZW2aZCaFG+XroHPaO+Zmr615+F/+PoTRxZMzG0IQOeLeG9QgkRQP2YGiqtDhFZK DyAthg710tvSeopLzaXoTvFeJiUBWSOgftL2fiFX1ye8FVdMpEbB4IMeDExNH08GGeL5qPQ6gqGy eUN51q1veieQA6TqJIc/2b3Z6fJfUEkc7uzXLg== -----END CERTIFICATE----- IdenTrust Commercial Root CA 1 ============================== -----BEGIN CERTIFICATE----- MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBKMQswCQYDVQQG EwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBS b290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQwMTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzES MBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENB IDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ld hNlT3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU+ehcCuz/ mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gpS0l4PJNgiCL8mdo2yMKi 1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1bVoE/c40yiTcdCMbXTMTEl3EASX2MN0C XZ/g1Ue9tOsbobtJSdifWwLziuQkkORiT0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl 3ZBWzvurpWCdxJ35UrCLvYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzy NeVJSQjKVsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZKdHzV WYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHTc+XvvqDtMwt0viAg xGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hvl7yTmvmcEpB4eoCHFddydJxVdHix uuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5NiGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMC AQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZI hvcNAQELBQADggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH 6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwtLRvM7Kqas6pg ghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93nAbowacYXVKV7cndJZ5t+qnt ozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3+wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmV YjzlVYA211QC//G5Xc7UI2/YRYRKW2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUX feu+h1sXIFRRk0pTAwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/ro kTLql1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG4iZZRHUe 2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZmUlO+KWA2yUPHGNiiskz Z2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7R cGzM7vRX+Bi6hG6H -----END CERTIFICATE----- IdenTrust Public Sector Root CA 1 ================================= -----BEGIN CERTIFICATE----- MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBNMQswCQYDVQQG EwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3Rv ciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcNMzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJV UzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBS b290IENBIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTy P4o7ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGyRBb06tD6 Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlSbdsHyo+1W/CD80/HLaXI rcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF/YTLNiCBWS2ab21ISGHKTN9T0a9SvESf qy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoS mJxZZoY+rfGwyj4GD3vwEUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFn ol57plzy9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9VGxyh LrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ2fjXctscvG29ZV/v iDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsVWaFHVCkugyhfHMKiq3IXAAaOReyL 4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gDW/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8B Af8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMw DQYJKoZIhvcNAQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHVDRDtfULAj+7A mgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9TaDKQGXSc3z1i9kKlT/YPyNt GtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8GlwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFt m6/n6J91eEyrRjuazr8FGF1NFTwWmhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMx NRF4eKLg6TCMf4DfWN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4 Mhn5+bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJtshquDDI ajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhAGaQdp/lLQzfcaFpPz+vC ZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ 3Wl9af0AVqW3rLatt8o+Ae+c -----END CERTIFICATE----- Entrust Root Certification Authority - G2 ========================================= -----BEGIN CERTIFICATE----- MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMCVVMxFjAUBgNV BAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVy bXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ug b25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIw HhcNMDkwNzA3MTcyNTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoT DUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMx OTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25s eTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwggEi MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP /vaCeb9zYQYKpSfYs1/TRU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXz HHfV1IWNcCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hWwcKU s/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1U1+cPvQXLOZprE4y TGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0jaWvYkxN4FisZDQSA/i2jZRjJKRx AgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ6 0B7vfec7aVHUbI2fkBJmqzANBgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5Z iXMRrEPR9RP/jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v1fN2D807iDgi nWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4RnAuknZoh8/CbCzB428Hch0P+ vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmHVHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xO e4pIb4tF9g== -----END CERTIFICATE----- Entrust Root Certification Authority - EC1 ========================================== -----BEGIN CERTIFICATE----- MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkGA1UEBhMCVVMx FjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVn YWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXpl ZCB1c2Ugb25seTEzMDEGA1UEAxMqRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 IC0gRUMxMB4XDTEyMTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYw FAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2Fs LXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQg dXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAt IEVDMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHy AsWfoPZb1YsGGYZPUxBtByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef 9eNi1KlHBz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE FLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVCR98crlOZF7ZvHH3h vxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nXhTcGtXsI/esni0qU+eH6p44mCOh8 kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G -----END CERTIFICATE----- CFCA EV ROOT ============ -----BEGIN CERTIFICATE----- MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJDTjEwMC4GA1UE CgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNB IEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkxMjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEw MC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQD DAxDRkNBIEVWIFJPT1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnV BU03sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpLTIpTUnrD 7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5/ZOkVIBMUtRSqy5J35DN uF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp7hZZLDRJGqgG16iI0gNyejLi6mhNbiyW ZXvKWfry4t3uMCz7zEasxGPrb382KzRzEpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7 xzbh72fROdOXW3NiGUgthxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9f py25IGvPa931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqotaK8K gWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNgTnYGmE69g60dWIol hdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfVPKPtl8MeNPo4+QgO48BdK4PRVmrJ tqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hvcWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAf BgNVHSMEGDAWgBTj/i39KNALtbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB /wQEAwIBBjAdBgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObTej/tUxPQ4i9q ecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdLjOztUmCypAbqTuv0axn96/Ua 4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBSESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sG E5uPhnEFtC+NiWYzKXZUmhH4J/qyP5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfX BDrDMlI1Dlb4pd19xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjn aH9dCi77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN5mydLIhy PDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe/v5WOaHIz16eGWRGENoX kbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+ZAAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3C ekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su -----END CERTIFICATE----- OISTE WISeKey Global Root GB CA =============================== -----BEGIN CERTIFICATE----- MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBtMQswCQYDVQQG EwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAw MzJaFw0zOTEyMDExNTEwMzFaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYD VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEds b2JhbCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3HEokKtaX scriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGxWuR51jIjK+FTzJlFXHtP rby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk 9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNku7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4o Qnc/nSMbsrY9gBQHTC5P99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvg GUpuuy9rM2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB /zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZI hvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrghcViXfa43FK8+5/ea4n32cZiZBKpD dHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0 VQreUGdNZtGn//3ZwLWoo4rOZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEui HZeeevJuQHHfaPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= -----END CERTIFICATE----- SZAFIR ROOT CA2 =============== -----BEGIN CERTIFICATE----- MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQELBQAwUTELMAkG A1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6ZW5pb3dhIFMuQS4xGDAWBgNV BAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkwNzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJ BgNVBAYTAlBMMSgwJgYDVQQKDB9LcmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYD VQQDDA9TWkFGSVIgUk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5Q qEvNQLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT3PSQ1hNK DJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw3gAeqDRHu5rr/gsUvTaE 2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr63fE9biCloBK0TXC5ztdyO4mTp4CEHCdJ ckm1/zuVnsHMyAHs6A6KCpbns6aH5db5BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwi ieDhZNRnvDF5YTy7ykHNXGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P AQH/BAQDAgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsFAAOC AQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw8PRBEew/R40/cof5 O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOGnXkZ7/e7DDWQw4rtTw/1zBLZpD67 oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCPoky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul 4+vJhaAlIDf7js4MNIThPIGyd05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6 +/NNIxuZMzSgLvWpCz/UXeHPhJ/iGcJfitYgHuNztw== -----END CERTIFICATE----- Certum Trusted Network CA 2 =========================== -----BEGIN CERTIFICATE----- MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCBgDELMAkGA1UE BhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAlBgNVBAsTHkNlcnR1 bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29y ayBDQSAyMCIYDzIwMTExMDA2MDgzOTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQ TDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENl cnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENB IDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWADGSdhhuWZGc/IjoedQF9 7/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+o CgCXhVqqndwpyeI1B+twTUrWwbNWuKFBOJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40b Rr5HMNUuctHFY9rnY3lEfktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2p uTRZCr+ESv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1mo130 GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02isx7QBlrd9pPPV3WZ 9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOWOZV7bIBaTxNyxtd9KXpEulKkKtVB Rgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgezTv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pye hizKV/Ma5ciSixqClnrDvFASadgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vM BhBgu4M1t15n3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZI hvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQF/xlhMcQSZDe28cmk4gmb3DW Al45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTfCVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuA L55MYIR4PSFk1vtBHxgP58l1cb29XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMo clm2q8KMZiYcdywmdjWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tM pkT/WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jbAoJnwTnb w3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksqP/ujmv5zMnHCnsZy4Ypo J/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Kob7a6bINDd82Kkhehnlt4Fj1F4jNy3eFm ypnTycUm/Q1oBEauttmbjL4ZvrHG8hnjXALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLX is7VmFxWlgPF7ncGNf/P5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7 zAYspsbiDrW5viSP -----END CERTIFICATE----- Hellenic Academic and Research Institutions RootCA 2015 ======================================================= -----BEGIN CERTIFICATE----- MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcT BkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0 aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl YXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAx MTIxWjCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMg QWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNV BAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIw MTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDC+Kk/G4n8PDwEXT2QNrCROnk8Zlrv bTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+eh iGsxr/CL0BgzuNtFajT0AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+ 6PAQZe104S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06CojXd FPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV9Cz82XBST3i4vTwr i5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrDgfgXy5I2XdGj2HUb4Ysn6npIQf1F GQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2 fu/Z8VFRfS0myGlZYeCsargqNhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9mu iNX6hME6wGkoLfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVdctA4GGqd83EkVAswDQYJKoZI hvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0IXtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+ D1hYc2Ryx+hFjtyp8iY/xnmMsVMIM4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrM d/K4kPFox/la/vot9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+y d+2VZ5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/eaj8GsGsVn 82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnhX9izjFk0WaSrT2y7Hxjb davYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQl033DlZdwJVqwjbDG2jJ9SrcR5q+ss7F Jej6A7na+RZukYT1HCjI/CbM1xyQVqdfbzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVt J94Cj8rDtSvK6evIIVM4pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGa JI7ZjnHKe7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0vm9q p/UsQu0yrbYhnr68 -----END CERTIFICATE----- Hellenic Academic and Research Institutions ECC RootCA 2015 =========================================================== -----BEGIN CERTIFICATE----- MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0 aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u cyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj aCBJbnN0aXR1dGlvbnMgRUNDIFJvb3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEw MzcxMlowgaoxCzAJBgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmlj IEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUQwQgYD VQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIEVDQyBSb290 Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKgQehLgoRc4vgxEZmGZE4JJS+dQS8KrjVP dJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJajq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoK Vlp8aQuqgAkkbH7BRqNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O BBYEFLQiC4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaeplSTA GiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7SofTUwJCA3sS61kFyjn dc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR -----END CERTIFICATE----- ISRG Root X1 ============ -----BEGIN CERTIFICATE----- MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAwTzELMAkGA1UE BhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNoIEdyb3VwMRUwEwYDVQQD EwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQG EwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMT DElTUkcgUm9vdCBYMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54r Vygch77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+0TM8ukj1 3Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6UA5/TR5d8mUgjU+g4rk8K b4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sWT8KOEUt+zwvo/7V3LvSye0rgTBIlDHCN Aymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyHB5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ 4Q7e2RCOFvu396j3x+UCB5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf 1b0SHzUvKBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWnOlFu hjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTnjh8BCNAw1FtxNrQH usEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbwqHyGO0aoSCqI3Haadr8faqU9GY/r OPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CIrU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4G A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY 9umbbjANBgkqhkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ3BebYhtF8GaV 0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KKNFtY2PwByVS5uCbMiogziUwt hDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJw TdwJx4nLCgdNbOhdjsnvzqvHu7UrTkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nx e5AW0wdeRlN8NwdCjNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZA JzVcoyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq4RgqsahD YVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPAmRGunUHBcnWEvgJBQl9n JEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57demyPxgcYxn/eR44/KJ4EBs+lVDR3veyJ m+kXQ99b21/+jh5Xos1AnX5iItreGCc= -----END CERTIFICATE----- AC RAIZ FNMT-RCM ================ -----BEGIN CERTIFICATE----- MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsxCzAJBgNVBAYT AkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTAeFw0wODEw MjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJD TTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC ggIBALpxgHpMhm5/yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcf qQgfBBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAzWHFctPVr btQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxFtBDXaEAUwED653cXeuYL j2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z374jNUUeAlz+taibmSXaXvMiwzn15Cou 08YfxGyqxRxqAQVKL9LFwag0Jl1mpdICIfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mw WsXmo8RZZUc1g16p6DULmbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnT tOmlcYF7wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peSMKGJ 47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2ZSysV4999AeU14EC ll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMetUqIJ5G+GR4of6ygnXYMgrwTJbFaa i0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE FPd9xf3E6Jobd2Sn9R2gzL+HYJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1o dHRwOi8vd3d3LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1RXxlDPiyN8+s D8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYMLVN0V2Ue1bLdI4E7pWYjJ2cJ j+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrT Qfv6MooqtyuGC2mDOL7Nii4LcK2NJpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW +YJF1DngoABd15jmfZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7 Ixjp6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp1txyM/1d 8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B9kiABdcPUXmsEKvU7ANm 5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wokRqEIr9baRRmW1FMdW4R58MD3R++Lj8UG rp1MYp3/RgT408m2ECVAdf4WqslKYIYvuu8wd+RU4riEmViAqhOLUTpPSPaLtrM= -----END CERTIFICATE----- Amazon Root CA 1 ================ -----BEGIN CERTIFICATE----- MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsFADA5MQswCQYD VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAxMB4XDTE1 MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC ggEBALJ4gHHKeNXjca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgH FzZM9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qwIFAGbHrQ gLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6VOujw5H5SNz/0egwLX0t dHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L93FcXmn/6pUCyziKrlA4b9v7LWIbxcce VOF34GfID5yHI9Y/QCB/IIDEgEw+OyQmjgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB /zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3 DQEBCwUAA4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDIU5PM CCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUsN+gDS63pYaACbvXy 8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vvo/ufQJVtMVT8QtPHRh8jrdkPSHCa 2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2 xJNDd2ZhwLnoQdeXeGADbkpyrqXRfboQnoZsG4q5WTP468SQvvG5 -----END CERTIFICATE----- Amazon Root CA 2 ================ -----BEGIN CERTIFICATE----- MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwFADA5MQswCQYD VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAyMB4XDTE1 MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC ggIBAK2Wny2cSkxKgXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4 kHbZW0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg1dKmSYXp N+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K8nu+NQWpEjTj82R0Yiw9 AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvd fLC6HM783k81ds8P+HgfajZRRidhW+mez/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAEx kv8LV/SasrlX6avvDXbR8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSS btqDT6ZjmUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz7Mt0 Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6+XUyo05f7O0oYtlN c/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI0u1ufm8/0i2BWSlmy5A5lREedCf+ 3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSw DPBMMPQFWAJI/TPlUq9LhONmUjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oA A7CXDpO8Wqj2LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY +gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kSk5Nrp+gvU5LE YFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl7uxMMne0nxrpS10gxdr9HIcW xkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygmbtmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQ gj9sAq+uEjonljYE1x2igGOpm/HlurR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbW aQbLU8uz/mtBzUF+fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoV Yh63n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE76KlXIx3 KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H9jVlpNMKVv/1F2Rs76gi JUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT4PsJYGw= -----END CERTIFICATE----- Amazon Root CA 3 ================ -----BEGIN CERTIFICATE----- MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5MQswCQYDVQQG EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAzMB4XDTE1MDUy NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZB f8ANm+gBG1bG8lKlui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjr Zt6jQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSrttvXBp43 rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkrBqWTrBqYaGFy+uGh0Psc eGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteMYyRIHN8wfdVoOw== -----END CERTIFICATE----- Amazon Root CA 4 ================ -----BEGIN CERTIFICATE----- MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5MQswCQYDVQQG EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSA0MB4XDTE1MDUy NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN /sGKe0uoe0ZLY7Bi9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri 83BkM6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV HQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WBMAoGCCqGSM49BAMDA2gA MGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlwCkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1 AE47xDqUEpHJWEadIRNyp4iciuRMStuW1KyLa2tJElMzrdfkviT8tQp21KW8EA== -----END CERTIFICATE----- Symantec Class 1 Public Primary Certification Authority - G6 ============================================================ -----BEGIN CERTIFICATE----- MIID9jCCAt6gAwIBAgIQJDJ18h0v0gkz97RqytDzmDANBgkqhkiG9w0BAQsFADCBlDELMAkGA1UE BhMCVVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMR8wHQYDVQQLExZTeW1hbnRlYyBU cnVzdCBOZXR3b3JrMUUwQwYDVQQDEzxTeW1hbnRlYyBDbGFzcyAxIFB1YmxpYyBQcmltYXJ5IENl cnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzYwHhcNMTExMDE4MDAwMDAwWhcNMzcxMjAxMjM1OTU5 WjCBlDELMAkGA1UEBhMCVVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMR8wHQYDVQQL ExZTeW1hbnRlYyBUcnVzdCBOZXR3b3JrMUUwQwYDVQQDEzxTeW1hbnRlYyBDbGFzcyAxIFB1Ymxp YyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzYwggEiMA0GCSqGSIb3DQEBAQUA A4IBDwAwggEKAoIBAQDHOddJZKmZgiJM6kXZBxbje/SD6Jlz+muxNuCad6BAwoGNAcfMjL2Pffd5 43pMA03Z+/2HOCgs3ZqLVAjbZ/sbjP4oki++t7JIp4Gh2F6Iw8w5QEFa0dzl2hCfL9oBTf0uRnz5 LicKaTfukaMbasxEvxvHw9QRslBglwm9LiL1QYRmn81ApqkAgMEflZKf3vNI79sdd2H8f9/ulqRy 0LY+/3gnr8uSFWkI22MQ4uaXrG7crPaizh5HmbmJtxLmodTNWRFnw2+F2EJOKL5ZVVkElauPN4C/ DfD8HzpkMViBeNfiNfYgPym4jxZuPkjctUwH4fIa6n4KedaovetdhitNAgMBAAGjQjBAMA4GA1Ud DwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQzQejIORIVk0jyljIuWvXalF9T YDANBgkqhkiG9w0BAQsFAAOCAQEAFeNzV7EXtl9JaUSm9l56Z6zS3nVJq/4lVcc6yUQVEG6/MWvL 2QeTfxyFYwDjMhLgzMv7OWyP4lPiPEAz2aSMR+atWPuJr+PehilWNCxFuBL6RIluLRQlKCQBZdbq UqwFblYSCT3QdPTXvQbKqDqNVkL6jXI+dPEDct+HG14OelWWLDi3mIXNTTNEyZSPWjEwN0ujOhKz 5zbRIWhLLTjmU64cJVYIVgNnhJ3Gw84kYsdMNs+wBkS39V8C3dlU6S+QTnrIToNADJqXPDe/v+z2 8LSFdyjBC8hnghAXOKK3Buqbvzr46SMHv3TgmDgVVXjucgBcGaP00jPg/73RVDkpDw== -----END CERTIFICATE----- Symantec Class 2 Public Primary Certification Authority - G6 ============================================================ -----BEGIN CERTIFICATE----- MIID9jCCAt6gAwIBAgIQZIKe/DcedF38l/+XyLH/QTANBgkqhkiG9w0BAQsFADCBlDELMAkGA1UE BhMCVVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMR8wHQYDVQQLExZTeW1hbnRlYyBU cnVzdCBOZXR3b3JrMUUwQwYDVQQDEzxTeW1hbnRlYyBDbGFzcyAyIFB1YmxpYyBQcmltYXJ5IENl cnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzYwHhcNMTExMDE4MDAwMDAwWhcNMzcxMjAxMjM1OTU5 WjCBlDELMAkGA1UEBhMCVVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMR8wHQYDVQQL ExZTeW1hbnRlYyBUcnVzdCBOZXR3b3JrMUUwQwYDVQQDEzxTeW1hbnRlYyBDbGFzcyAyIFB1Ymxp YyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzYwggEiMA0GCSqGSIb3DQEBAQUA A4IBDwAwggEKAoIBAQDNzOkFyGOFyz9AYxe9GPo15gRnV2WYKaRPyVyPDzTS+NqoE2KquB5QZ3iw FkygOakVeq7t0qLA8JA3KRgmXOgNPLZsST/B4NzZS7YUGQum05bh1gnjGSYc+R9lS/kaQxwAg9bQ qkmi1NvmYji6UBRDbfkx+FYW2TgCkc/rbN27OU6Z4TBnRfHU8I3D3/7yOAchfQBeVkSz5GC9kSuc q1sEcg+yKNlyqwUgQiWpWwNqIBDMMfAr2jUs0Pual07wgksr2F82owstr2MNHSV/oW5cYqGNKD6h /Bwg+AEvulWaEbAZ0shQeWsOagXXqgQ2sqPy4V93p3ec5R7c6d9qwWVdAgMBAAGjQjBAMA4GA1Ud DwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSHjCCVyJhK0daABkqQNETfHE2/ sDANBgkqhkiG9w0BAQsFAAOCAQEAgY6ypWaWtyGltu9vI1pf24HFQqV4wWn99DzX+VxrcHIa/FqX TQCAiIiCisNxDY7FiZss7Y0L0nJU9X3UXENX6fOupQIR9nYrgVfdfdp0MP1UR/bgFm6mtApI5ud1 Bw8pGTnOefS2bMVfmdUfS/rfbSw8DVSAcPCIC4DPxmiiuB1w2XaM/O6lyc+tHc+ZJVdaYkXLFmu9 Sc2lo4xpeSWuuExsi0BmSxY/zwIa3eFsawdhanYVKZl/G92IgMG/tY9zxaaWI4SmKIYkM2oBLldz JbZev4/mHWGoQClnHYebHX+bn5nNMdZUvmK7OaxoEkiRIKXLsd3+b/xa5IJVWa8xqQ== -----END CERTIFICATE----- D-TRUST Root CA 3 2013 ====================== -----BEGIN CERTIFICATE----- MIIEDjCCAvagAwIBAgIDD92sMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNVBAYTAkRFMRUwEwYDVQQK DAxELVRydXN0IEdtYkgxHzAdBgNVBAMMFkQtVFJVU1QgUm9vdCBDQSAzIDIwMTMwHhcNMTMwOTIw MDgyNTUxWhcNMjgwOTIwMDgyNTUxWjBFMQswCQYDVQQGEwJERTEVMBMGA1UECgwMRC1UcnVzdCBH bWJIMR8wHQYDVQQDDBZELVRSVVNUIFJvb3QgQ0EgMyAyMDEzMIIBIjANBgkqhkiG9w0BAQEFAAOC AQ8AMIIBCgKCAQEAxHtCkoIf7O1UmI4SwMoJ35NuOpNcG+QQd55OaYhs9uFp8vabomGxvQcgdJhl 8YwmCM2oNcqANtFjbehEeoLDbF7eu+g20sRoNoyfMr2EIuDcwu4QRjltr5M5rofmw7wJySxrZ1vZ m3Z1TAvgu8XXvD558l++0ZBX+a72Zl8xv9Ntj6e6SvMjZbu376Ml1wrqWLbviPr6ebJSWNXwrIyh UXQplapRO5AyA58ccnSQ3j3tYdLl4/1kR+W5t0qp9x+uloYErC/jpIF3t1oW/9gPP/a3eMykr/pb PBJbqFKJcu+I89VEgYaVI5973bzZNO98lDyqwEHC451QGsDkGSL8swIDAQABo4IBBTCCAQEwDwYD VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUP5DIfccVb/Mkj6nDL0uiDyGyL+cwDgYDVR0PAQH/BAQD AgEGMIG+BgNVHR8EgbYwgbMwdKByoHCGbmxkYXA6Ly9kaXJlY3RvcnkuZC10cnVzdC5uZXQvQ049 RC1UUlVTVCUyMFJvb3QlMjBDQSUyMDMlMjAyMDEzLE89RC1UcnVzdCUyMEdtYkgsQz1ERT9jZXJ0 aWZpY2F0ZXJldm9jYXRpb25saXN0MDugOaA3hjVodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9k LXRydXN0X3Jvb3RfY2FfM18yMDEzLmNybDANBgkqhkiG9w0BAQsFAAOCAQEADlkOWOR0SCNEzzQh tZwUGq2aS7eziG1cqRdw8CqfjXv5e4X6xznoEAiwNStfzwLS05zICx7uBVSuN5MECX1sj8J0vPgc lL4xAUAt8yQgt4RVLFzI9XRKEBmLo8ftNdYJSNMOwLo5qLBGArDbxohZwr78e7Erz35ih1WWzAFv m2chlTWL+BD8cRu3SzdppjvW7IvuwbDzJcmPkn2h6sPKRL8mpXSSnON065102ctNh9j8tGlsi6BD B2B4l+nZk3zCRrybN1Kj7Yo8E6l7U0tJmhEFLAtuVqwfLoJs4GlntQ5tLdnkwBXxP/oYcuEVbSdb LTAoK59ImmQrme/ydUlfXA== -----END CERTIFICATE----- TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 ============================================= -----BEGIN CERTIFICATE----- MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIxGDAWBgNVBAcT D0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxpbXNlbCB2ZSBUZWtub2xvamlr IEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0wKwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24g TWVya2V6aSAtIEthbXUgU00xNjA0BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRp ZmlrYXNpIC0gU3VydW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYD VQQGEwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXllIEJpbGlt c2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklUQUsxLTArBgNVBAsTJEth bXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBTTTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11 IFNNIFNTTCBLb2sgU2VydGlmaWthc2kgLSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A MIIBCgKCAQEAr3UwM6q7a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y8 6Ij5iySrLqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INrN3wc wv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2XYacQuFWQfw4tJzh0 3+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/iSIzL+aFCr2lqBs23tPcLG07xxO9 WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4fAJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQU ZT/HiobGPN08VFw1+DrtUgxHV8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJ KoZIhvcNAQELBQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPfIPP54+M638yc lNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4lzwDGrpDxpa5RXI4s6ehlj2R e37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0j q5Rm+K37DwhuJi1/FwcJsoz7UMCflo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM= -----END CERTIFICATE----- GDCA TrustAUTH R5 ROOT ====================== -----BEGIN CERTIFICATE----- MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UEBhMCQ04xMjAw BgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZIENPLixMVEQuMR8wHQYDVQQD DBZHRENBIFRydXN0QVVUSCBSNSBST09UMB4XDTE0MTEyNjA1MTMxNVoXDTQwMTIzMTE1NTk1OVow YjELMAkGA1UEBhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ IENPLixMVEQuMR8wHQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMIICIjANBgkqhkiG9w0B AQEFAAOCAg8AMIICCgKCAgEA2aMW8Mh0dHeb7zMNOwZ+Vfy1YI92hhJCfVZmPoiC7XJjDp6L3TQs AlFRwxn9WVSEyfFrs0yw6ehGXTjGoqcuEVe6ghWinI9tsJlKCvLriXBjTnnEt1u9ol2x8kECK62p OqPseQrsXzrj/e+APK00mxqriCZ7VqKChh/rNYmDf1+uKU49tm7srsHwJ5uu4/Ts765/94Y9cnrr pftZTqfrlYwiOXnhLQiPzLyRuEH3FMEjqcOtmkVEs7LXLM3GKeJQEK5cy4KOFxg2fZfmiJqwTTQJ 9Cy5WmYqsBebnh52nUpmMUHfP/vFBu8btn4aRjb3ZGM74zkYI+dndRTVdVeSN72+ahsmUPI2JgaQ xXABZG12ZuGR224HwGGALrIuL4xwp9E7PLOR5G62xDtw8mySlwnNR30YwPO7ng/Wi64HtloPzgsM R6flPri9fcebNaBhlzpBdRfMK5Z3KpIhHtmVdiBnaM8Nvd/WHwlqmuLMc3GkL30SgLdTMEZeS1SZ D2fJpcjyIMGC7J0R38IC+xo70e0gmu9lZJIQDSri3nDxGGeCjGHeuLzRL5z7D9Ar7Rt2ueQ5Vfj4 oR24qoAATILnsn8JuLwwoC8N9VKejveSswoAHQBUlwbgsQfZxw9cZX08bVlX5O2ljelAU58VS6Bx 9hoh49pwBiFYFIeFd3mqgnkCAwEAAaNCMEAwHQYDVR0OBBYEFOLJQJ9NzuiaoXzPDj9lxSmIahlR MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQDRSVfg p8xoWLoBDysZzY2wYUWsEe1jUGn4H3++Fo/9nesLqjJHdtJnJO29fDMylyrHBYZmDRd9FBUb1Ov9 H5r2XpdptxolpAqzkT9fNqyL7FeoPueBihhXOYV0GkLH6VsTX4/5COmSdI31R9KrO9b7eGZONn35 6ZLpBN79SWP8bfsUcZNnL0dKt7n/HipzcEYwv1ryL3ml4Y0M2fmyYzeMN2WFcGpcWwlyua1jPLHd +PwyvzeG5LuOmCd+uh8W4XAR8gPfJWIyJyYYMoSf/wA6E7qaTfRPuBRwIrHKK5DOKcFw9C+df/KQ HtZa37dG/OaG+svgIHZ6uqbL9XzeYqWxi+7egmaKTjowHz+Ay60nugxe19CxVsp3cbK1daFQqUBD F8Io2c9Si1vIY9RCPqAzekYu9wogRlR+ak8x8YF+QnQ4ZXMn7sZ8uI7XpTrXmKGcjBBV09tL7ECQ 8s1uV9JiDnxXk7Gnbc2dg7sq5+W2O3FYrf3RRbxake5TFW/TRQl1brqQXR4EzzffHqhmsYzmIGrv /EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrqT8p+ck0LcIymSLumoRT2+1hEmRSuqguT aaApJUqlyyvdimYHFngVV3Eb7PVHhPOeMTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g== -----END CERTIFICATE----- TrustCor RootCert CA-1 ====================== -----BEGIN CERTIFICATE----- MIIEMDCCAxigAwIBAgIJANqb7HHzA7AZMA0GCSqGSIb3DQEBCwUAMIGkMQswCQYDVQQGEwJQQTEP MA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEkMCIGA1UECgwbVHJ1c3RDb3Ig U3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3Jp dHkxHzAdBgNVBAMMFlRydXN0Q29yIFJvb3RDZXJ0IENBLTEwHhcNMTYwMjA0MTIzMjE2WhcNMjkx MjMxMTcyMzE2WjCBpDELMAkGA1UEBhMCUEExDzANBgNVBAgMBlBhbmFtYTEUMBIGA1UEBwwLUGFu YW1hIENpdHkxJDAiBgNVBAoMG1RydXN0Q29yIFN5c3RlbXMgUy4gZGUgUi5MLjEnMCUGA1UECwwe VHJ1c3RDb3IgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MR8wHQYDVQQDDBZUcnVzdENvciBSb290Q2Vy dCBDQS0xMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv463leLCJhJrMxnHQFgKq1mq jQCj/IDHUHuO1CAmujIS2CNUSSUQIpidRtLByZ5OGy4sDjjzGiVoHKZaBeYei0i/mJZ0PmnK6bV4 pQa81QBeCQryJ3pS/C3Vseq0iWEk8xoT26nPUu0MJLq5nux+AHT6k61sKZKuUbS701e/s/OojZz0 JEsq1pme9J7+wH5COucLlVPat2gOkEz7cD+PSiyU8ybdY2mplNgQTsVHCJCZGxdNuWxu72CVEY4h gLW9oHPY0LJ3xEXqWib7ZnZ2+AYfYW0PVcWDtxBWcgYHpfOxGgMFZA6dWorWhnAbJN7+KIor0Gqw /Hqi3LJ5DotlDwIDAQABo2MwYTAdBgNVHQ4EFgQU7mtJPHo/DeOxCbeKyKsZn3MzUOcwHwYDVR0j BBgwFoAU7mtJPHo/DeOxCbeKyKsZn3MzUOcwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC AYYwDQYJKoZIhvcNAQELBQADggEBACUY1JGPE+6PHh0RU9otRCkZoB5rMZ5NDp6tPVxBb5UrJKF5 mDo4Nvu7Zp5I/5CQ7z3UuJu0h3U/IJvOcs+hVcFNZKIZBqEHMwwLKeXx6quj7LUKdJDHfXLy11yf ke+Ri7fc7Waiz45mO7yfOgLgJ90WmMCV1Aqk5IGadZQ1nJBfiDcGrVmVCrDRZ9MZyonnMlo2HD6C qFqTvsbQZJG2z9m2GM/bftJlo6bEjhcxwft+dtvTheNYsnd6djtsL1Ac59v2Z3kf9YKVmgenFK+P 3CghZwnS1k1aHBkcjndcw5QkPTJrS37UeJSDvjdNzl/HHk484IkzlQsPpTLWPFp5LBk= -----END CERTIFICATE----- TrustCor RootCert CA-2 ====================== -----BEGIN CERTIFICATE----- MIIGLzCCBBegAwIBAgIIJaHfyjPLWQIwDQYJKoZIhvcNAQELBQAwgaQxCzAJBgNVBAYTAlBBMQ8w DQYDVQQIDAZQYW5hbWExFDASBgNVBAcMC1BhbmFtYSBDaXR5MSQwIgYDVQQKDBtUcnVzdENvciBT eXN0ZW1zIFMuIGRlIFIuTC4xJzAlBgNVBAsMHlRydXN0Q29yIENlcnRpZmljYXRlIEF1dGhvcml0 eTEfMB0GA1UEAwwWVHJ1c3RDb3IgUm9vdENlcnQgQ0EtMjAeFw0xNjAyMDQxMjMyMjNaFw0zNDEy MzExNzI2MzlaMIGkMQswCQYDVQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5h bWEgQ2l0eTEkMCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5U cnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxHzAdBgNVBAMMFlRydXN0Q29yIFJvb3RDZXJ0 IENBLTIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCnIG7CKqJiJJWQdsg4foDSq8Gb ZQWU9MEKENUCrO2fk8eHyLAnK0IMPQo+QVqedd2NyuCb7GgypGmSaIwLgQ5WoD4a3SwlFIIvl9Nk RvRUqdw6VC0xK5mC8tkq1+9xALgxpL56JAfDQiDyitSSBBtlVkxs1Pu2YVpHI7TYabS3OtB0PAx1 oYxOdqHp2yqlO/rOsP9+aij9JxzIsekp8VduZLTQwRVtDr4uDkbIXvRR/u8OYzo7cbrPb1nKDOOb XUm4TOJXsZiKQlecdu/vvdFoqNL0Cbt3Nb4lggjEFixEIFapRBF37120Hapeaz6LMvYHL1cEksr1 /p3C6eizjkxLAjHZ5DxIgif3GIJ2SDpxsROhOdUuxTTCHWKF3wP+TfSvPd9cW436cOGlfifHhi5q jxLGhF5DUVCcGZt45vz27Ud+ez1m7xMTiF88oWP7+ayHNZ/zgp6kPwqcMWmLmaSISo5uZk3vFsQP eSghYA2FFn3XVDjxklb9tTNMg9zXEJ9L/cb4Qr26fHMC4P99zVvh1Kxhe1fVSntb1IVYJ12/+Ctg rKAmrhQhJ8Z3mjOAPF5GP/fDsaOGM8boXg25NSyqRsGFAnWAoOsk+xWq5Gd/bnc/9ASKL3x74xdh 8N0JqSDIvgmk0H5Ew7IwSjiqqewYmgeCK9u4nBit2uBGF6zPXQIDAQABo2MwYTAdBgNVHQ4EFgQU 2f4hQG6UnrybPZx9mCAZ5YwwYrIwHwYDVR0jBBgwFoAU2f4hQG6UnrybPZx9mCAZ5YwwYrIwDwYD VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggIBAJ5Fngw7tu/h Osh80QA9z+LqBrWyOrsGS2h60COXdKcs8AjYeVrXWoSK2BKaG9l9XE1wxaX5q+WjiYndAfrs3fnp kpfbsEZC89NiqpX+MWcUaViQCqoL7jcjx1BRtPV+nuN79+TMQjItSQzL/0kMmx40/W5ulop5A7Zv 2wnL/V9lFDfhOPXzYRZY5LVtDQsEGz9QLX+zx3oaFoBg+Iof6Rsqxvm6ARppv9JYx1RXCI/hOWB3 S6xZhBqI8d3LT3jX5+EzLfzuQfogsL7L9ziUwOHQhQ+77Sxzq+3+knYaZH9bDTMJBzN7Bj8RpFxw PIXAz+OQqIN3+tvmxYxoZxBnpVIt8MSZj3+/0WvitUfW2dCFmU2Umw9Lje4AWkcdEQOsQRivh7dv DDqPys/cA8GiCcjl/YBeyGBCARsaU1q7N6a3vLqE6R5sGtRk2tRD/pOLS/IseRYQ1JMLiI+h2IYU RpFHmygk71dSTlxCnKr3Sewn6EAes6aJInKc9Q0ztFijMDvd1GpUk74aTfOTlPf8hAs/hCBcNANE xdqtvArBAs8e5ZTZ845b2EzwnexhF7sUMlQMAimTHpKG9n/v55IFDlndmQguLvqcAFLTxWYp5KeX RKQOKIETNcX2b2TmQcTVL8w0RSXPQQCWPUouwpaYT05KnJe32x+SMsj/D1Fu1uwJ -----END CERTIFICATE----- TrustCor ECA-1 ============== -----BEGIN CERTIFICATE----- MIIEIDCCAwigAwIBAgIJAISCLF8cYtBAMA0GCSqGSIb3DQEBCwUAMIGcMQswCQYDVQQGEwJQQTEP MA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEkMCIGA1UECgwbVHJ1c3RDb3Ig U3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3Jp dHkxFzAVBgNVBAMMDlRydXN0Q29yIEVDQS0xMB4XDTE2MDIwNDEyMzIzM1oXDTI5MTIzMTE3Mjgw N1owgZwxCzAJBgNVBAYTAlBBMQ8wDQYDVQQIDAZQYW5hbWExFDASBgNVBAcMC1BhbmFtYSBDaXR5 MSQwIgYDVQQKDBtUcnVzdENvciBTeXN0ZW1zIFMuIGRlIFIuTC4xJzAlBgNVBAsMHlRydXN0Q29y IENlcnRpZmljYXRlIEF1dGhvcml0eTEXMBUGA1UEAwwOVHJ1c3RDb3IgRUNBLTEwggEiMA0GCSqG SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPj+ARtZ+odnbb3w9U73NjKYKtR8aja+3+XzP4Q1HpGjOR MRegdMTUpwHmspI+ap3tDvl0mEDTPwOABoJA6LHip1GnHYMma6ve+heRK9jGrB6xnhkB1Zem6g23 xFUfJ3zSCNV2HykVh0A53ThFEXXQmqc04L/NyFIduUd+Dbi7xgz2c1cWWn5DkR9VOsZtRASqnKmc p0yJF4OuowReUoCLHhIlERnXDH19MURB6tuvsBzvgdAsxZohmz3tQjtQJvLsznFhBmIhVE5/wZ0+ fyCMgMsq2JdiyIMzkX2woloPV+g7zPIlstR8L+xNxqE6FXrntl019fZISjZFZtS6mFjBAgMBAAGj YzBhMB0GA1UdDgQWBBREnkj1zG1I1KBLf/5ZJC+Dl5mahjAfBgNVHSMEGDAWgBREnkj1zG1I1KBL f/5ZJC+Dl5mahjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsF AAOCAQEABT41XBVwm8nHc2FvcivUwo/yQ10CzsSUuZQRg2dd4mdsdXa/uwyqNsatR5Nj3B5+1t4u /ukZMjgDfxT2AHMsWbEhBuH7rBiVDKP/mZb3Kyeb1STMHd3BOuCYRLDE5D53sXOpZCz2HAF8P11F hcCF5yWPldwX8zyfGm6wyuMdKulMY/okYWLW2n62HGz1Ah3UKt1VkOsqEUc8Ll50soIipX1TH0Xs J5F95yIW6MBoNtjG8U+ARDL54dHRHareqKucBK+tIA5kmE2la8BIWJZpTdwHjFGTot+fDz2LYLSC jaoITmJF4PkL0uDgPFveXHEnJcLmA4GLEFPjx1WitJ/X5g== -----END CERTIFICATE----- SSL.com Root Certification Authority RSA ======================================== -----BEGIN CERTIFICATE----- MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxDjAM BgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24x MTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYw MjEyMTczOTM5WhcNNDEwMjEyMTczOTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NM LmNvbSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcNAQEBBQAD ggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2RxFdHaxh3a3by/ZPkPQ/C Fp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aXqhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8 P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcCC52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/ge oeOy3ZExqysdBP+lSgQ36YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkp k8zruFvh/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrFYD3Z fBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93EJNyAKoFBbZQ+yODJ gUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVcUS4cK38acijnALXRdMbX5J+tB5O2 UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi8 1xtZPCvM8hnIk2snYxnP/Okm+Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4s bE6x/c+cCbqiM+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4GA1UdDwEB/wQE AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGVcpNxJK1ok1iOMq8bs3AD/CUr dIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBcHadm47GUBwwyOabqG7B52B2ccETjit3E+ZUf ijhDPwGFpUenPUayvOUiaPd7nNgsPgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAsl u1OJD7OAUN5F7kR/q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjq erQ0cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jra6x+3uxj MxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90IH37hVZkLId6Tngr75qNJ vTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/YK9f1JmzJBjSWFupwWRoyeXkLtoh/D1JI Pb9s2KJELtFOt3JY04kTlf5Eq/jXixtunLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406y wKBjYZC6VWg3dGq2ktufoYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NI WuuA8ShYIc2wBlX7Jz9TkHCpBB5XJ7k= -----END CERTIFICATE----- SSL.com Root Certification Authority ECC ======================================== -----BEGIN CERTIFICATE----- MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMCVVMxDjAMBgNV BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xMTAv BgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEy MTgxNDAzWhcNNDEwMjEyMTgxNDAzWjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAO BgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuBBAAiA2IA BEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI7Z4INcgn64mMU1jrYor+ 8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPgCemB+vNH06NjMGEwHQYDVR0OBBYEFILR hXMw5zUE044CkvvlpNHEIejNMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTT jgKS++Wk0cQh6M0wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCW e+0F+S8Tkdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+gA0z 5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl -----END CERTIFICATE----- SSL.com EV Root Certification Authority RSA R2 ============================================== -----BEGIN CERTIFICATE----- MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNVBAYTAlVTMQ4w DAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9u MTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy MB4XDTE3MDUzMTE4MTQzN1oXDTQyMDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQI DAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYD VQQDDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMIICIjAN BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvqM0fNTPl9fb69LT3w23jh hqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssufOePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7w cXHswxzpY6IXFJ3vG2fThVUCAtZJycxa4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTO Zw+oz12WGQvE43LrrdF9HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+ B6KjBSYRaZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcAb9Zh CBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQGp8hLH94t2S42Oim 9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQVPWKchjgGAGYS5Fl2WlPAApiiECto RHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMOpgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+Slm JuwgUHfbSguPvuUCYHBBXtSuUDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48 +qvWBkofZ6aYMBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa49QaAJadz20Zp qJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBWs47LCp1Jjr+kxJG7ZhcFUZh1 ++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nx Y/hoLVUE0fKNsKTPvDxeH3jnpaAgcLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2G guDKBAdRUNf/ktUM79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDz OFSz/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXtll9ldDz7 CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEmKf7GUmG6sXP/wwyc5Wxq lD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKKQbNmC1r7fSOl8hqw/96bg5Qu0T/fkreR rwU7ZcegbLHNYhLDkBvjJc40vG93drEQw/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1 hlMYegouCRw2n5H9gooiS9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX 9hwJ1C07mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w== -----END CERTIFICATE----- SSL.com EV Root Certification Authority ECC =========================================== -----BEGIN CERTIFICATE----- MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMCVVMxDjAMBgNV BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xNDAy BgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYw MjEyMTgxNTIzWhcNNDEwMjEyMTgxNTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NM LmNvbSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB BAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMAVIbc/R/fALhBYlzccBYy 3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1KthkuWnBaBu2+8KGwytAJKaNjMGEwHQYDVR0O BBYEFFvKXuXe0oGqzagtZFG22XKbl+ZPMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe 5d7SgarNqC1kUbbZcpuX5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJ N+vp1RPZytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZgh5Mm m7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg== -----END CERTIFICATE----- GlobalSign Root CA - R6 ======================= -----BEGIN CERTIFICATE----- MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEgMB4GA1UECxMX R2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds b2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQxMjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9i YWxTaWduIFJvb3QgQ0EgLSBSNjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFs U2lnbjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQss grRIxutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1kZguSgMpE 3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxDaNc9PIrFsmbVkJq3MQbF vuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJwLnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqM PKq0pPbzlUoSB239jLKJz9CgYXfIWHSw1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+ azayOeSsJDa38O+2HBNXk7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05O WgtH8wY2SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/hbguy CLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4nWUx2OVvq+aWh2IMP 0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpYrZxCRXluDocZXFSxZba/jJvcE+kN b7gu3GduyYsRtYQUigAZcIN5kZeR1BonvzceMgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQE AwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNV HSMEGDAWgBSubAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGtIxg93eFyRJa0 lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr6155wsTLxDKZmOMNOsIeDjHfrY BzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLjvUYAGm0CuiVdjaExUd1URhxN25mW7xocBFym Fe944Hn+Xds+qkxV/ZoVqW/hpvvfcDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr 3TsTjxKM4kEaSHpzoHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB1 0jZpnOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfspA9MRf/T uTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+vJJUEeKgDu+6B5dpffItK oZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+t JDfLRVpOoERIyNiwmcUVhAn21klJwGW45hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA= -----END CERTIFICATE----- OISTE WISeKey Global Root GC CA =============================== -----BEGIN CERTIFICATE----- MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQswCQYDVQQGEwJD SDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNlZDEo MCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRa Fw00MjA1MDkwOTU4MzNaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQL ExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh bCBSb290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4nieUqjFqdr VCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4Wp2OQ0jnUsYd4XxiWD1Ab NTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd BgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7TrYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0E AwMDaAAwZQIwJsdpW9zV57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtk AjEA2zQgMgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9 -----END CERTIFICATE----- GTS Root R1 =========== -----BEGIN CERTIFICATE----- MIIFWjCCA0KgAwIBAgIQbkepxUtHDA3sM9CJuRz04TANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQG EwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJv b3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAG A1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIi MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx 9vaMf/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7wCl7r aKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjwTcLCeoiKu7rPWRnW r4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0PfyblqAj+lug8aJRT7oM6iCsVlgmy4HqM LnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly 4cpk9+aCEI3oncKKiPo4Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr 06zqkUspzBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92 wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70paDPvOmbsB4om 3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrNVjzRlwW5y0vtOUucxD/SVRNu JLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEM BQADggIBADiWCu49tJYeX++dnAsznyvgyv3SjgofQXSlfKqE1OXyHuY3UjKcC9FhHb8owbZEKTV1 d5iyfNm9dKyKaOOpMQkpAWBz40d8U6iQSifvS9efk+eCNs6aaAyC58/UEBZvXw6ZXPYfcX3v73sv fuo21pdwCxXu11xWajOl40k4DLh9+42FpLFZXvRq4d2h9mREruZRgyFmxhE+885H7pwoHyXa/6xm ld01D1zvICxi/ZG6qcz8WpyTgYMpl0p8WnK0OdC3d8t5/Wk6kjftbjhlRn7pYL15iJdfOBL07q9b gsiG1eGZbYwE8na6SfZu6W0eX6DvJ4J2QPim01hcDyxC2kLGe4g0x8HYRZvBPsVhHdljUEn2NIVq 4BjFbkerQUIpm/ZgDdIx02OYI5NaAIFItO/Nis3Jz5nu2Z6qNuFoS3FJFDYoOj0dzpqPJeaAcWEr tXvM+SUWgeExX6GjfhaknBZqlxi9dnKlC54dNuYvoS++cJEPqOba+MSSQGwlfnuzCdyyF62ARPBo pY+Udf90WuioAnwMCeKpSwughQtiue+hMZL77/ZRBIls6Kl0obsXs7X9SQ98POyDGCBDTtWTurQ0 sR8WNh8M5mQ5Fkzc4P4dyKliPUDqysU0ArSuiYgzNdwsE3PYJ/HQcu51OyLemGhmW/HGY0dVHLql CFF1pkgl -----END CERTIFICATE----- GTS Root R2 =========== -----BEGIN CERTIFICATE----- MIIFWjCCA0KgAwIBAgIQbkepxlqz5yDFMJo/aFLybzANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQG EwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJv b3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAG A1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIi MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTuk k3LvCvptnfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY6Dlo 7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAuMC6C/Pq8tBcKSOWI m8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7kRXuJVfeKH2JShBKzwkCX44ofR5Gm dFrS+LFjKBC4swm4VndAoiaYecb+3yXuPuWgf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbu ak7MkogwTZq9TwtImoS1mKPV+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscsz cTJGr61K8YzodDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RW Ir9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKaG73Vululycsl aVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCqgc7dGtxRcw1PcOnlthYhGXmy 5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEM BQADggIBALZp8KZ3/p7uC4Gt4cCpx/k1HUCCq+YEtN/L9x0Pg/B+E02NjO7jMyLDOfxA325BS0JT vhaI8dI4XsRomRyYUpOM52jtG2pzegVATX9lO9ZY8c6DR2Dj/5epnGB3GFW1fgiTz9D2PGcDFWEJ +YF59exTpJ/JjwGLc8R3dtyDovUMSRqodt6Sm2T4syzFJ9MHwAiApJiS4wGWAqoC7o87xdFtCjMw c3i5T1QWvwsHoaRc5svJXISPD+AVdyx+Jn7axEvbpxZ3B7DNdehyQtaVhJ2Gg/LkkM0JR9SLA3Da WsYDQvTtN6LwG1BUSw7YhN4ZKJmBR64JGz9I0cNv4rBgF/XuIwKl2gBbbZCr7qLpGzvpx0QnRY5r n/WkhLx3+WuXrD5RRaIRpsyF7gpo8j5QOHokYh4XIDdtak23CZvJ/KRY9bb7nE4Yu5UC56Gtmwfu Nmsk0jmGwZODUNKBRqhfYlcsu2xkiAhu7xNUX90txGdj08+JN7+dIPT7eoOboB6BAFDC5AwiWVIQ 7UNWhwD4FFKnHYuTjKJNRn8nxnGbJN7k2oaLDX5rIMHAnuFl2GqjpuiFizoHCBy69Y9Vmhh1fuXs gWbRIXOhNUQLgD1bnF5vKheW0YMjiGZt5obicDIvUiLnyOd/xCxgXS/Dr55FBcOEArf9LAhST4Ld o/DUhgkC -----END CERTIFICATE----- GTS Root R3 =========== -----BEGIN CERTIFICATE----- MIICDDCCAZGgAwIBAgIQbkepx2ypcyRAiQ8DVd2NHTAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJV UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg UjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcq hkjOPQIBBgUrgQQAIgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUU Rout736GjOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL24Cej QjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTB8Sa6oC2uhYHP 0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEAgFukfCPAlaUs3L6JbyO5o91lAFJekazInXJ0 glMLfalAvWhgxeG4VDvBNhcl2MG9AjEAnjWSdIUlUfUk7GRSJFClH9voy8l27OyCbvWFGFPouOOa KaqW04MjyaR7YbPMAuhd -----END CERTIFICATE----- GTS Root R4 =========== -----BEGIN CERTIFICATE----- MIICCjCCAZGgAwIBAgIQbkepyIuUtui7OyrYorLBmTAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJV UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg UjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcq hkjOPQIBBgUrgQQAIgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa 6zzuhXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvRHYqj QjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSATNbrdP9JNqPV 2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNnADBkAjBqUFJ0CMRw3J5QdCHojXohw0+WbhXRIjVhLfoI N+4Zba3bssx9BzT1YBkstTTZbyACMANxsbqjYAuG7ZoIapVon+Kz4ZNkfF6Tpt95LY2F45TPI11x zPKwTdb+mciUqXWi4w== -----END CERTIFICATE----- UCA Global G2 Root ================== -----BEGIN CERTIFICATE----- MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9MQswCQYDVQQG EwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBHbG9iYWwgRzIgUm9vdDAeFw0x NjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0xCzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlU cnVzdDEbMBkGA1UEAwwSVUNBIEdsb2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A MIICCgKCAgEAxeYrb3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmT oni9kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzmVHqUwCoV 8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/RVogvGjqNO7uCEeBHANBS h6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDcC/Vkw85DvG1xudLeJ1uK6NjGruFZfc8o LTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIjtm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/ R+zvWr9LesGtOxdQXGLYD0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBe KW4bHAyvj5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6DlNaBa 4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6iIis7nCs+dwp4wwc OxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznPO6Q0ibd5Ei9Hxeepl2n8pndntd97 8XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O BBYEFIHEjMz15DD/pQwIX4wVZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo 5sOASD0Ee/ojL3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5 1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl1qnN3e92mI0A Ds0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oUb3n09tDh05S60FdRvScFDcH9 yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LVPtateJLbXDzz2K36uGt/xDYotgIVilQsnLAX c47QN6MUPJiVAAwpBVueSUmxX8fjy88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHo jhJi6IjMtX9Gl8CbEGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZk bxqgDMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI+Vg7RE+x ygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGyYiGqhkCyLmTTX8jjfhFn RR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bXUB+K+wb1whnw0A== -----END CERTIFICATE----- UCA Extended Validation Root ============================ -----BEGIN CERTIFICATE----- MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBHMQswCQYDVQQG EwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9u IFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMxMDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8G A1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIi MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrs iWogD4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvSsPGP2KxF Rv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aopO2z6+I9tTcg1367r3CTu eUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dksHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR 59mzLC52LqGj3n5qiAno8geK+LLNEOfic0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH 0mK1lTnj8/FtDw5lhIpjVMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KR el7sFsLzKuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/TuDv B0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41Gsx2VYVdWf6/wFlth WG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs1+lvK9JKBZP8nm9rZ/+I8U6laUpS NwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQDfwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS 3H5aBZ8eNJr34RQwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQEL BQADggIBADaNl8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQVBcZEhrxH9cM aVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5c6sq1WnIeJEmMX3ixzDx/BR4 dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb +7lsq+KePRXBOy5nAliRn+/4Qh8st2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOW F3sGPjLtx7dCvHaj2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwi GpWOvpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2CxR9GUeOc GMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmxcmtpzyKEC2IPrNkZAJSi djzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbMfjKaiJUINlK73nZfdklJrX+9ZSCyycEr dhh2n1ax -----END CERTIFICATE----- Certigna Root CA ================ -----BEGIN CERTIFICATE----- MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAwWjELMAkGA1UE BhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAwMiA0ODE0NjMwODEwMDAzNjEZ MBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0xMzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjda MFoxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYz MDgxMDAwMzYxGTAXBgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4IC DwAwggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sOty3tRQgX stmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9MCiBtnyN6tMbaLOQdLNyz KNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPuI9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8 JXrJhFwLrN1CTivngqIkicuQstDuI7pmTLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16 XdG+RCYyKfHx9WzMfgIhC59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq 4NYKpkDfePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3YzIoej wpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWTCo/1VTp2lc5ZmIoJ lXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1kJWumIWmbat10TWuXekG9qxf5kBdI jzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp/ /TBt2dzhauH8XwIDAQABo4IBGjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw HQYDVR0OBBYEFBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of 1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczovL3d3d3cuY2Vy dGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilodHRwOi8vY3JsLmNlcnRpZ25h LmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYraHR0cDovL2NybC5kaGlteW90aXMuY29tL2Nl cnRpZ25hcm9vdGNhLmNybDANBgkqhkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOIt OoldaDgvUSILSo3L6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxP TGRGHVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH60BGM+RFq 7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncBlA2c5uk5jR+mUYyZDDl3 4bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdio2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd 8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS 6Cvu5zHbugRqh5jnxV/vfaci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaY tlu3zM63Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayhjWZS aX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw3kAP+HwV96LOPNde E4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0= -----END CERTIFICATE----- emSign Root CA - G1 =================== -----BEGIN CERTIFICATE----- MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYDVQQGEwJJTjET MBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRl ZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBHMTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgx ODMwMDBaMGcxCzAJBgNVBAYTAklOMRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVk aHJhIFRlY2hub2xvZ2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIB IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQzf2N4aLTN LnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO8oG0x5ZOrRkVUkr+PHB1 cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aqd7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHW DV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhMtTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ 6DqS0hdW5TUaQBw+jSztOd9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrH hQIDAQABo0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQDAgEG MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31xPaOfG1vR2vjTnGs2 vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjMwiI/aTvFthUvozXGaCocV685743Q NcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6dGNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q +Mri/Tm3R7nrft8EI6/6nAYH6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeih U80Bv2noWgbyRQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx iN66zB+Afko= -----END CERTIFICATE----- emSign ECC Root CA - G3 ======================= -----BEGIN CERTIFICATE----- MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQGEwJJTjETMBEG A1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEg MB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4 MTgzMDAwWjBrMQswCQYDVQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11 ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g RzMwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0WXTsuwYc 58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xySfvalY8L1X44uT6EYGQIr MgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuBzhccLikenEhjQjAOBgNVHQ8BAf8EBAMC AQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+D CBeQyh+KTOgNG3qxrdWBCUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7 jHvrZQnD+JbNR6iC8hZVdyR+EhCVBCyj -----END CERTIFICATE----- emSign Root CA - C1 =================== -----BEGIN CERTIFICATE----- MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkGA1UEBhMCVVMx EzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNp Z24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UE BhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQD ExNlbVNpZ24gUm9vdCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+up ufGZBczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZHdPIWoU/ Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH3DspVpNqs8FqOp099cGX OFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvHGPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4V I5b2P/AgNBbeCsbEBEV5f6f9vtKppa+cxSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleooms lMuoaJuvimUnzYnu3Yy1aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+ XJGFehiqTbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQAD ggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87/kOXSTKZEhVb3xEp /6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4kqNPEjE2NuLe/gDEo2APJ62gsIq1 NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrGYQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9 wC68AivTxEDkigcxHpvOJpkT+xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQ BmIMMMAVSKeoWXzhriKi4gp6D/piq1JM4fHfyr6DDUI= -----END CERTIFICATE----- emSign ECC Root CA - C3 ======================= -----BEGIN CERTIFICATE----- MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQGEwJVUzETMBEG A1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMxIDAeBgNVBAMTF2VtU2lnbiBF Q0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UE BhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQD ExdlbVNpZ24gRUNDIFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd 6bciMK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4OjavtisIGJAnB9 SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0OBBYEFPtaSNCAIEDyqOkA B2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gA MGUCMQC02C8Cif22TGK6Q04ThHK1rt0c3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwU ZOR8loMRnLDRWmFLpg9J0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ== -----END CERTIFICATE----- Hongkong Post Root CA 3 ======================= -----BEGIN CERTIFICATE----- MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQELBQAwbzELMAkG A1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJSG9uZyBLb25nMRYwFAYDVQQK Ew1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25na29uZyBQb3N0IFJvb3QgQ0EgMzAeFw0xNzA2 MDMwMjI5NDZaFw00MjA2MDMwMjI5NDZaMG8xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtv bmcxEjAQBgNVBAcTCUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEgMB4GA1UEAxMX SG9uZ2tvbmcgUG9zdCBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz iNfqzg8gTr7m1gNt7ln8wlffKWihgw4+aMdoWJwcYEuJQwy51BWy7sFOdem1p+/l6TWZ5Mwc50tf jTMwIDNT2aa71T4Tjukfh0mtUC1Qyhi+AViiE3CWu4mIVoBc+L0sPOFMV4i707mV78vH9toxdCim 5lSJ9UExyuUmGs2C4HDaOym71QP1mbpV9WTRYA6ziUm4ii8F0oRFKHyPaFASePwLtVPLwpgchKOe sL4jpNrcyCse2m5FHomY2vkALgbpDDtw1VAliJnLzXNg99X/NWfFobxeq81KuEXryGgeDQ0URhLj 0mRiikKYvLTGCAj4/ahMZJx2Ab0vqWwzD9g/KLg8aQFChn5pwckGyuV6RmXpwtZQQS4/t+TtbNe/ JgERohYpSms0BpDsE9K2+2p20jzt8NYt3eEV7KObLyzJPivkaTv/ciWxNoZbx39ri1UbSsUgYT2u y1DhCDq+sI9jQVMwCFk8mB13umOResoQUGC/8Ne8lYePl8X+l2oBlKN8W4UdKjk60FSh0Tlxnf0h +bV78OLgAo9uliQlLKAeLKjEiafv7ZkGL7YKTE/bosw3Gq9HhS2KX8Q0NEwA/RiTZxPRN+ZItIsG xVd7GYYKecsAyVKvQv83j+GjHno9UKtjBucVtT+2RTeUN7F+8kjDf8V1/peNRY8apxpyKBpADwID AQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQXnc0e i9Y5K3DTXNSguB+wAPzFYTAdBgNVHQ4EFgQUF53NHovWOStw01zUoLgfsAD8xWEwDQYJKoZIhvcN AQELBQADggIBAFbVe27mIgHSQpsY1Q7XZiNc4/6gx5LS6ZStS6LG7BJ8dNVI0lkUmcDrudHr9Egw W62nV3OZqdPlt9EuWSRY3GguLmLYauRwCy0gUCCkMpXRAJi70/33MvJJrsZ64Ee+bs7Lo3I6LWld y8joRTnU+kLBEUx3XZL7av9YROXrgZ6voJmtvqkBZss4HTzfQx/0TW60uhdG/H39h4F5ag0zD/ov +BS5gLNdTaqX4fnkGMX41TiMJjz98iji7lpJiCzfeT2OnpA8vUFKOt1b9pq0zj8lMH8yfaIDlNDc eqFS3m6TjRgm/VWsvY+b0s+v54Ysyx8Jb6NvqYTUc79NoXQbTiNg8swOqn+knEwlqLJmOzj/2ZQw 9nKEvmhVEA/GcywWaZMH/rFF7buiVWqw2rVKAiUnhde3t4ZEFolsgCs+l6mc1X5VTMbeRRAc6uk7 nwNT7u56AQIWeNTowr5GdogTPyK7SBIdUgC0An4hGh6cJfTzPV4e0hz5sy229zdcxsshTrD3mUcY hcErulWuBurQB7Lcq9CClnXO0lD+mefPL5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB 60PZ2Pierc+xYw5F9KBaLJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fq dBb9HxEGmpv0 -----END CERTIFICATE----- Entrust Root Certification Authority - G4 ========================================= -----BEGIN CERTIFICATE----- MIIGSzCCBDOgAwIBAgIRANm1Q3+vqTkPAAAAAFVlrVgwDQYJKoZIhvcNAQELBQAwgb4xCzAJBgNV BAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3Qu bmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1 dGhvcml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1 dGhvcml0eSAtIEc0MB4XDTE1MDUyNzExMTExNloXDTM3MTIyNzExNDExNlowgb4xCzAJBgNVBAYT AlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0 L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhv cml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhv cml0eSAtIEc0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsewsQu7i0TD/pZJH4i3D umSXbcr3DbVZwbPLqGgZ2K+EbTBwXX7zLtJTmeH+H17ZSK9dE43b/2MzTdMAArzE+NEGCJR5WIoV 3imz/f3ET+iq4qA7ec2/a0My3dl0ELn39GjUu9CH1apLiipvKgS1sqbHoHrmSKvS0VnM1n4j5pds 8ELl3FFLFUHtSUrJ3hCX1nbB76W1NhSXNdh4IjVS70O92yfbYVaCNNzLiGAMC1rlLAHGVK/XqsEQ e9IFWrhAnoanw5CGAlZSCXqc0ieCU0plUmr1POeo8pyvi73TDtTUXm6Hnmo9RR3RXRv06QqsYJn7 ibT/mCzPfB3pAqoEmh643IhuJbNsZvc8kPNXwbMv9W3y+8qh+CmdRouzavbmZwe+LGcKKh9asj5X xNMhIWNlUpEbsZmOeX7m640A2Vqq6nPopIICR5b+W45UYaPrL0swsIsjdXJ8ITzI9vF01Bx7owVV 7rtNOzK+mndmnqxpkCIHH2E6lr7lmk/MBTwoWdPBDFSoWWG9yHJM6Nyfh3+9nEg2XpWjDrk4JFX8 dWbrAuMINClKxuMrLzOg2qOGpRKX/YAr2hRC45K9PvJdXmd0LhyIRyk0X+IyqJwlN4y6mACXi0mW Hv0liqzc2thddG5msP9E36EYxr5ILzeUePiVSj9/E15dWf10hkNjc0kCAwEAAaNCMEAwDwYDVR0T AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJ84xFYjwznooHFs6FRM5Og6sb9n MA0GCSqGSIb3DQEBCwUAA4ICAQAS5UKme4sPDORGpbZgQIeMJX6tuGguW8ZAdjwD+MlZ9POrYs4Q jbRaZIxowLByQzTSGwv2LFPSypBLhmb8qoMi9IsabyZIrHZ3CL/FmFz0Jomee8O5ZDIBf9PD3Vht 7LGrhFV0d4QEJ1JrhkzO3bll/9bGXp+aEJlLdWr+aumXIOTkdnrG0CSqkM0gkLpHZPt/B7NTeLUK YvJzQ85BK4FqLoUWlFPUa19yIqtRLULVAJyZv967lDtX/Zr1hstWO1uIAeV8KEsD+UmDfLJ/fOPt jqF/YFOOVZ1QNBIPt5d7bIdKROf1beyAN/BYGW5KaHbwH5Lk6rWS02FREAutp9lfx1/cH6NcjKF+ m7ee01ZvZl4HliDtC3T7Zk6LERXpgUl+b7DUUH8i119lAg2m9IUe2K4GS0qn0jFmwvjO5QimpAKW RGhXxNUzzxkvFMSUHHuk2fCfDrGA4tGeEWSpiBE6doLlYsKA2KSD7ZPvfC+QsDJMlhVoSFLUmQjA JOgc47OlIQ6SwJAfzyBfyjs4x7dtOvPmRLgOMWuIjnDrnBdSqEGULoe256YSxXXfW8AKbnuk5F6G +TaU33fD6Q3AOfF5u0aOq0NZJ7cguyPpVkAh7DE9ZapD8j3fcEThuk0mEDuYn/PIjhs4ViFqUZPT kcpG2om3PVODLAgfi49T3f+sHw== -----END CERTIFICATE----- Microsoft ECC Root Certificate Authority 2017 ============================================= -----BEGIN CERTIFICATE----- MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQgRUND IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4 MjMxNjA0WjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw NAYDVQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQ BgcqhkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZRogPZnZH6 thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYbhGBKia/teQ87zvH2RPUB eMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTIy5lycFIM +Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlf Xu5gKcs68tvWMoQZP3zVL8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaR eNtUjGUBiudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M= -----END CERTIFICATE----- Microsoft RSA Root Certificate Authority 2017 ============================================= -----BEGIN CERTIFICATE----- MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBlMQswCQYDVQQG EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQg UlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIw NzE4MjMwMDIzWjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u MTYwNAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcw ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZNt9GkMml 7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0ZdDMbRnMlfl7rEqUrQ7e S0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw7 1VdyvD/IybLeS2v4I2wDwAW9lcfNcztmgGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+ dkC0zVJhUXAoP8XFWvLJjEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49F yGcohJUcaDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaGYaRS MLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6W6IYZVcSn2i51BVr lMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4KUGsTuqwPN1q3ErWQgR5WrlcihtnJ 0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH+FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJ ClTUFLkqqNfs+avNJVgyeY+QW5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYw DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZCLgLNFgVZJ8og 6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OCgMNPOsduET/m4xaRhPtthH80 dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk +ONVFT24bcMKpBLBaYVu32TxU5nhSnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex /2kskZGT4d9Mozd2TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDy AmH3pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGRxpl/j8nW ZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiAppGWSZI1b7rCoucL5mxAyE 7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKT c0QWbej09+CVgI+WXTik9KveCjCHk9hNAHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D 5KbvtwEwXlGjefVwaaZBRA+GsCyRxj3qrg+E -----END CERTIFICATE----- e-Szigno Root CA 2017 ===================== -----BEGIN CERTIFICATE----- MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNVBAYTAkhVMREw DwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUt MjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJvb3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZa Fw00MjA4MjIxMjA3MDZaMHExCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UE CgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3pp Z25vIFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtvxie+RJCx s1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+HWyx7xf58etqjYzBhMA8G A1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSHERUI0arBeAyxr87GyZDv vzAEwDAfBgNVHSMEGDAWgBSHERUI0arBeAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEA tVfd14pVCzbhhkT61NlojbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxO svxyqltZ+efcMQ== -----END CERTIFICATE----- certSIGN Root CA G2 =================== -----BEGIN CERTIFICATE----- MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNVBAYTAlJPMRQw EgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjAeFw0xNzAy MDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJBgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lH TiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIP ADCCAgoCggIBAMDFdRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05 N0IwvlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZuIt4Imfk abBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhpn+Sc8CnTXPnGFiWeI8Mg wT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKscpc/I1mbySKEwQdPzH/iV8oScLumZfNp dWO9lfsbl83kqK/20U6o2YpxJM02PbyWxPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91Qqh ngLjYl/rNUssuHLoPj1PrCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732 jcZZroiFDsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fxDTvf 95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgyLcsUDFDYg2WD7rlc z8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6CeWRgKRM+o/1Pcmqr4tTluCRVLERL iohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1Ud DgQWBBSCIS1mxteg4BXrzkwJd8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOB ywaK8SJJ6ejqkX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQlqiCA2ClV9+BB /AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0OJD7uNGzcgbJceaBxXntC6Z5 8hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+cNywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5 BiKDUyUM/FHE5r7iOZULJK2v0ZXkltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklW atKcsWMy5WHgUyIOpwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tU Sxfj03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZkPuXaTH4M NMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE1LlSVHJ7liXMvGnjSG4N 0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MXQRBdJ3NghVdJIgc= -----END CERTIFICATE----- Trustwave Global Certification Authority ======================================== -----BEGIN CERTIFICATE----- MIIF2jCCA8KgAwIBAgIMBfcOhtpJ80Y1LrqyMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJV UzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2 ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9u IEF1dGhvcml0eTAeFw0xNzA4MjMxOTM0MTJaFw00MjA4MjMxOTM0MTJaMIGIMQswCQYDVQQGEwJV UzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2 ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9u IEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALldUShLPDeS0YLOvR29 zd24q88KPuFd5dyqCblXAj7mY2Hf8g+CY66j96xz0XznswuvCAAJWX/NKSqIk4cXGIDtiLK0thAf LdZfVaITXdHG6wZWiYj+rDKd/VzDBcdu7oaJuogDnXIhhpCujwOl3J+IKMujkkkP7NAP4m1ET4Bq stTnoApTAbqOl5F2brz81Ws25kCI1nsvXwXoLG0R8+eyvpJETNKXpP7ScoFDB5zpET71ixpZfR9o WN0EACyW80OzfpgZdNmcc9kYvkHHNHnZ9GLCQ7mzJ7Aiy/k9UscwR7PJPrhq4ufogXBeQotPJqX+ OsIgbrv4Fo7NDKm0G2x2EOFYeUY+VM6AqFcJNykbmROPDMjWLBz7BegIlT1lRtzuzWniTY+HKE40 Cz7PFNm73bZQmq131BnW2hqIyE4bJ3XYsgjxroMwuREOzYfwhI0Vcnyh78zyiGG69Gm7DIwLdVcE uE4qFC49DxweMqZiNu5m4iK4BUBjECLzMx10coos9TkpoNPnG4CELcU9402x/RpvumUHO1jsQkUm +9jaJXLE9gCxInm943xZYkqcBW89zubWR2OZxiRvchLIrH+QtAuRcOi35hYQcRfO3gZPSEF9NUqj ifLJS3tBEW1ntwiYTOURGa5CgNz7kAXU+FDKvuStx8KU1xad5hePrzb7AgMBAAGjQjBAMA8GA1Ud EwEB/wQFMAMBAf8wHQYDVR0OBBYEFJngGWcNYtt2s9o9uFvo/ULSMQ6HMA4GA1UdDwEB/wQEAwIB BjANBgkqhkiG9w0BAQsFAAOCAgEAmHNw4rDT7TnsTGDZqRKGFx6W0OhUKDtkLSGm+J1WE2pIPU/H PinbbViDVD2HfSMF1OQc3Og4ZYbFdada2zUFvXfeuyk3QAUHw5RSn8pk3fEbK9xGChACMf1KaA0H ZJDmHvUqoai7PF35owgLEQzxPy0QlG/+4jSHg9bP5Rs1bdID4bANqKCqRieCNqcVtgimQlRXtpla 4gt5kNdXElE1GYhBaCXUNxeEFfsBctyV3lImIJgm4nb1J2/6ADtKYdkNy1GTKv0WBpanI5ojSP5R vbbEsLFUzt5sQa0WZ37b/TjNuThOssFgy50X31ieemKyJo90lZvkWx3SD92YHJtZuSPTMaCm/zjd zyBP6VhWOmfD0faZmZ26NraAL4hHT4a/RDqA5Dccprrql5gR0IRiR2Qequ5AvzSxnI9O4fKSTx+O 856X3vOmeWqJcU9LJxdI/uz0UA9PSX3MReO9ekDFQdxhVicGaeVyQYHTtgGJoC86cnn+OjC/QezH Yj6RS8fZMXZC+fc8Y+wmjHMMfRod6qh8h6jCJ3zhM0EPz8/8AKAigJ5Kp28AsEFFtyLKaEjFQqKu 3R3y4G5OBVixwJAWKqQ9EEC+j2Jjg6mcgn0tAumDMHzLJ8n9HmYAsC7TIS+OMxZsmO0QqAfWzJPP 29FpHOTKyeC2nOnOcXHebD8WpHk= -----END CERTIFICATE----- Trustwave Global ECC P256 Certification Authority ================================================= -----BEGIN CERTIFICATE----- MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYDVQQGEwJVUzER MA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBI b2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDI1NiBDZXJ0aWZp Y2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMxOTM1MTBaFw00MjA4MjMxOTM1MTBaMIGRMQswCQYD VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRy dXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDI1 NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABH77bOYj 43MyCMpg5lOcunSNGLB4kFKA3TjASh3RqMyTpJcGOMoNFWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqm P62jQzBBMA8GA1UdEwEB/wQFMAMBAf8wDwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt 0UrrdaVKEJmzsaGLSvcwCgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjz RM4q3wghDDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7 -----END CERTIFICATE----- Trustwave Global ECC P384 Certification Authority ================================================= -----BEGIN CERTIFICATE----- MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYDVQQGEwJVUzER MA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBI b2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDM4NCBDZXJ0aWZp Y2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMxOTM2NDNaFw00MjA4MjMxOTM2NDNaMIGRMQswCQYD VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRy dXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDM4 NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTB2MBAGByqGSM49AgEGBSuBBAAiA2IABGvaDXU1CDFH Ba5FmVXxERMuSvgQMSOjfoPTfygIOiYaOs+Xgh+AtycJj9GOMMQKmw6sWASr9zZ9lCOkmwqKi6vr /TklZvFe/oyujUF5nQlgziip04pt89ZF1PKYhDhloKNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNV HQ8BAf8EBQMDBwYAMB0GA1UdDgQWBBRVqYSJ0sEyvRjLbKYHTsjnnb6CkDAKBggqhkjOPQQDAwNn ADBkAjA3AZKXRRJ+oPM+rRk6ct30UJMDEr5E0k9BpIycnR+j9sKS50gU/k6bpZFXrsY3crsCMGcl CrEMXu6pY5Jv5ZAL/mYiykf9ijH3g/56vxC+GCsej/YpHpRZ744hN8tRmKVuSw== -----END CERTIFICATE----- NAVER Global Root Certification Authority ========================================= -----BEGIN CERTIFICATE----- MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEMBQAwaTELMAkG A1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRGT1JNIENvcnAuMTIwMAYDVQQD DClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4 NDJaFw0zNzA4MTgyMzU5NTlaMGkxCzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVT UyBQTEFURk9STSBDb3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlv biBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVAiQqrDZBb UGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH38dq6SZeWYp34+hInDEW +j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lEHoSTGEq0n+USZGnQJoViAbbJAh2+g1G7 XNr4rRVqmfeSVPc0W+m/6imBEtRTkZazkVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2 aacp+yPOiNgSnABIqKYPszuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4 Yb8ObtoqvC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHfnZ3z VHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaGYQ5fG8Ir4ozVu53B A0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo0es+nPxdGoMuK8u180SdOqcXYZai cdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3aCJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejy YhbLgGvtPe31HzClrkvJE+2KAQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNV HQ4EFgQU0p+I36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoNqo0hV4/GPnrK 21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatjcu3cvuzHV+YwIHHW1xDBE1UB jCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm+LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bx hYTeodoS76TiEJd6eN4MUZeoIUCLhr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTg E34h5prCy8VCZLQelHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTH D8z7p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8piKCk5XQ A76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLRLBT/DShycpWbXgnbiUSY qqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oG I/hGoiLtk/bdmuYqh7GYVPEi92tF4+KOdh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmg kpzNNIaRkPpkUZ3+/uul9XXeifdy -----END CERTIFICATE----- AC RAIZ FNMT-RCM SERVIDORES SEGUROS =================================== -----BEGIN CERTIFICATE----- MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQswCQYDVQQGEwJF UzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgwFgYDVQRhDA9WQVRFUy1RMjgy NjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1SQ00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4 MTIyMDA5MzczM1oXDTQzMTIyMDA5MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQt UkNNMQ4wDAYDVQQLDAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNB QyBSQUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuBBAAiA2IA BPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LHsbI6GA60XYyzZl2hNPk2 LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oKUm8BA06Oi6NCMEAwDwYDVR0TAQH/BAUw AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqG SM49BAMDA2kAMGYCMQCuSuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoD zBOQn5ICMQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJyv+c= -----END CERTIFICATE----- GlobalSign Secure Mail Root R45 =============================== -----BEGIN CERTIFICATE----- MIIFcDCCA1igAwIBAgIQdlP+qExQq5+NMrUdA49X3DANBgkqhkiG9w0BAQwFADBSMQswCQYDVQQG EwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEoMCYGA1UEAxMfR2xvYmFsU2lnbiBTZWN1 cmUgTWFpbCBSb290IFI0NTAeFw0yMDAzMTgwMDAwMDBaFw00NTAzMTgwMDAwMDBaMFIxCzAJBgNV BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSgwJgYDVQQDEx9HbG9iYWxTaWduIFNl Y3VyZSBNYWlsIFJvb3QgUjQ1MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA3HnMbQb5 bbvgVgRsf+B1zC0FSehL3FTsW3eVcr9/Yp2FqYokUF9T5dt0b6QpWxMqCa2axS/C93Y7oUVGqkPm JP4rsG8ycBlGWnkmL/w9fV9ky1fMYWGo2ZVu45Wgbn9HEhjW7wPJ+4r6mr2CFalVd0sRT1nga8Nx 8wzYVNWBaD4TuRUuh4o8RCc2YiRu+CwFcjBhvUKRI8SdJafZVJoUozGtgHkMp2NsmKOsV0czH2WW 4dDSNdr5cfehpiW1QV3fPmDY0fafpfK4zBOqj/mybuGDLZPdPoUa3eixXCYBy0mF/PzS1H+FYoZ0 +cvsNSKiDDCPO6t561by+kLz7fkfRYlAKa3qknTqUv1WtCvaou11wm6rzlKQS/be8EmPmkjUiBlt RebMjLndZGBgAkD4uc+8WOs9hbnGCtOcB2aPxxg5I0bhPB6jL1Bhkgs9K2zxo0c4V5GrDY/GnU0E 0iZSXOWl/SotFioBaeepfeE2t7Eqxdmxjb25i87Mi6E+C0jNUJU0xNgIWdhrJvS+9dQiFwBXya6b BDAznwv731aiyW5Udtqxl2InWQ8RiiIbZJY/qPG3JEqNPFN8bYN2PbImSHP1RBYBLQkqjhaWUNBz Bl27IkiCTApGWj+A/1zy8pqsLAjg1urwEjiBT6YQ7UarzBacC89kppkChURnRq39TecCAwEAAaNC MEAwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFKCTFShu7o8IsjXG nmJ5dKexDit7MA0GCSqGSIb3DQEBDAUAA4ICAQBFCvjRXKxigdAE17b/V1GJCwzL3iRlN/urnu1m 9OoMGWmJuBmxMFa02fb3vsaul8tF9hGMOjBkTMGfWcBGQggGR2QXeOCVBwbWjKKsqdk/03tWT/zE hyjftisWI8CfH1vj1kReIk8jBIw1FrV5B4ZcL5fi9ghkptzbqIrjpHt3DdEpkyggtFOjS05f3sH2 dSP8Hzx4T3AxeC+iNVRxBKzIxG3D9pGx/s3uRG6B9kDFPioBv6tMsQM/DRHkD9Ik4yKIm59fRz1R SeAJN34XITF2t2dxSChLJdcQ6J9hWRbFPjJOHwzOo8wP5McRByIvOAjdW5frQmxZmpruetCd38Xb CUMuCqoZPWvoajB6V+a/s2o5qY/j8U9laLa9nyiPoRZaCVA6Mi4dL0QRQqYA5jGY/y2hD+akYFbP edeyTtew+m4MVyPHzh+lsUxtGUmeDn9wj3E/WCifdd1h4Dq3Obbul9Q1UfuLSWDIPGaul+6NJllX u3jwelAwCbBgqp9O3Mk+HjrcYpMzsDpUdG8sMUXRaxEyamh29j32ahNeJJjn6h2az3iCB2D3TRDT gZpFjZ6vm9yAx0OylWikww7oCkcVv1Qz3AHn1aYec9h6sr8vreNVMJ7fDkG84BH1oQyoIuHjAKNO cHyS4wTRekKKdZBZ45vRTKJkvXN5m2/ys8H2PA== -----END CERTIFICATE----- GlobalSign Secure Mail Root E45 =============================== -----BEGIN CERTIFICATE----- MIICITCCAaegAwIBAgIQdlP+qicdlUZd1vGe5biQCjAKBggqhkjOPQQDAzBSMQswCQYDVQQGEwJC RTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEoMCYGA1UEAxMfR2xvYmFsU2lnbiBTZWN1cmUg TWFpbCBSb290IEU0NTAeFw0yMDAzMTgwMDAwMDBaFw00NTAzMTgwMDAwMDBaMFIxCzAJBgNVBAYT AkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSgwJgYDVQQDEx9HbG9iYWxTaWduIFNlY3Vy ZSBNYWlsIFJvb3QgRTQ1MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE+XmLgUc3iZY/RUlQfxomC5My fi7AwKcImsNuj5s+CyLsN1O3b4qwvCc3S22pRjvZH/+loUS7LXO/nkEHXFObUQg6WrtvOMcWkXjC ShNpHYLfWi8AiJaiLhx0+Z1+ZjeKo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB /zAdBgNVHQ4EFgQU3xNei1/CQAL9VreUTLYe1aaxFJYwCgYIKoZIzj0EAwMDaAAwZQIwE7C+13Eg PuSrnM42En1fTB8qtWlFM1/TLVqy5IjH3go2QjJ5naZruuH5RCp7isMSAjEAoGYcToedh8ntmUwb Cu4tYMM3xx3NtXKw2cbvvPL/P/BS3QjnqmR5w+RpV5EvpMt8 -----END CERTIFICATE----- GlobalSign Root R46 =================== -----BEGIN CERTIFICATE----- MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUAMEYxCzAJBgNV BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJv b3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAX BgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIi MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08Es CVeJOaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQGvGIFAha/ r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud316HCkD7rRlr+/fKYIje 2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo0q3v84RLHIf8E6M6cqJaESvWJ3En7YEt bWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSEy132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvj K8Cd+RTyG/FWaha/LIWFzXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD4 12lPFzYE+cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCNI/on ccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzsx2sZy/N78CsHpdls eVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqaByFrgY/bxFn63iLABJzjqls2k+g9 vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYD VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEM BQADggIBAHx47PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti2kM3S+LGteWy gxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIkpnnpHs6i58FZFZ8d4kuaPp92 CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRFFRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZm OUdkLG5NrmJ7v2B0GbhWrJKsFjLtrWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qq JZ4d16GLuc1CLgSkZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwye qiv5u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP4vkYxboz nxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6N3ec592kD3ZDZopD8p/7 DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3vouXsXgxT7PntgMTzlSdriVZzH81Xwj3 QEUxeCp6 -----END CERTIFICATE----- GlobalSign Root E46 =================== -----BEGIN CERTIFICATE----- MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYxCzAJBgNVBAYT AkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3Qg RTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNV BAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcq hkjOPQIBBgUrgQQAIgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkB jtjqR+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGddyXqBPCCj QjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQxCpCPtsad0kRL gLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZk vLtoURMMA/cVi4RguYv/Uo7njLwcAjA8+RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+ CAezNIm8BZ/3Hobui3A= -----END CERTIFICATE----- GLOBALTRUST 2020 ================ -----BEGIN CERTIFICATE----- MIIFgjCCA2qgAwIBAgILWku9WvtPilv6ZeUwDQYJKoZIhvcNAQELBQAwTTELMAkGA1UEBhMCQVQx IzAhBgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVT VCAyMDIwMB4XDTIwMDIxMDAwMDAwMFoXDTQwMDYxMDAwMDAwMFowTTELMAkGA1UEBhMCQVQxIzAh BgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVTVCAy MDIwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAri5WrRsc7/aVj6B3GyvTY4+ETUWi D59bRatZe1E0+eyLinjF3WuvvcTfk0Uev5E4C64OFudBc/jbu9G4UeDLgztzOG53ig9ZYybNpyrO VPu44sB8R85gfD+yc/LAGbaKkoc1DZAoouQVBGM+uq/ufF7MpotQsjj3QWPKzv9pj2gOlTblzLmM CcpL3TGQlsjMH/1WljTbjhzqLL6FLmPdqqmV0/0plRPwyJiT2S0WR5ARg6I6IqIoV6Lr/sCMKKCm fecqQjuCgGOlYx8ZzHyyZqjC0203b+J+BlHZRYQfEs4kUmSFC0iAToexIiIwquuuvuAC4EDosEKA A1GqtH6qRNdDYfOiaxaJSaSjpCuKAsR49GiKweR6NrFvG5Ybd0mN1MkGco/PU+PcF4UgStyYJ9OR JitHHmkHr96i5OTUawuzXnzUJIBHKWk7buis/UDr2O1xcSvy6Fgd60GXIsUf1DnQJ4+H4xj04KlG DfV0OoIu0G4skaMxXDtG6nsEEFZegB31pWXogvziB4xiRfUg3kZwhqG8k9MedKZssCz3AwyIDMvU clOGvGBG85hqwvG/Q/lwIHfKN0F5VVJjjVsSn8VoxIidrPIwq7ejMZdnrY8XD2zHc+0klGvIg5rQ mjdJBKuxFshsSUktq6HQjJLyQUp5ISXbY9e2nKd+Qmn7OmMCAwEAAaNjMGEwDwYDVR0TAQH/BAUw AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFNwuH9FhN3nkq9XVsxJxaD1qaJwiMB8GA1Ud IwQYMBaAFNwuH9FhN3nkq9XVsxJxaD1qaJwiMA0GCSqGSIb3DQEBCwUAA4ICAQCR8EICaEDuw2jA VC/f7GLDw56KoDEoqoOOpFaWEhCGVrqXctJUMHytGdUdaG/7FELYjQ7ztdGl4wJCXtzoRlgHNQIw 4Lx0SsFDKv/bGtCwr2zD/cuz9X9tAy5ZVp0tLTWMstZDFyySCstd6IwPS3BD0IL/qMy/pJTAvoe9 iuOTe8aPmxadJ2W8esVCgmxcB9CpwYhgROmYhRZf+I/KARDOJcP5YBugxZfD0yyIMaK9MOzQ0MAS 8cE54+X1+NZK3TTN+2/BT+MAi1bikvcoskJ3ciNnxz8RFbLEAwW+uxF7Cr+obuf/WEPPm2eggAe2 HcqtbepBEX4tdJP7wry+UUTF72glJ4DjyKDUEuzZpTcdN3y0kcra1LGWge9oXHYQSa9+pTeAsRxS vTOBTI/53WXZFM2KJVj04sWDpQmQ1GwUY7VA3+vA/MRYfg0UFodUJ25W5HCEuGwyEn6CMUO+1918 oa2u1qsgEu8KwxCMSZY13At1XrFP1U80DhEgB3VDRemjEdqso5nCtnkn4rnvyOL2NSl6dPrFf4IF YqYK6miyeUcGbvJXqBUzxvd4Sj1Ce2t+/vdG6tHrju+IaFvowdlxfv1k7/9nR4hYJS8+hge9+6jl gqispdNpQ80xiEmEU5LAsTkbOYMBMMTyqfrQA71yN2BWHzZ8vTmR9W0Nv3vXkg== -----END CERTIFICATE----- ANF Secure Server Root CA ========================= -----BEGIN CERTIFICATE----- MIIF7zCCA9egAwIBAgIIDdPjvGz5a7EwDQYJKoZIhvcNAQELBQAwgYQxEjAQBgNVBAUTCUc2MzI4 NzUxMDELMAkGA1UEBhMCRVMxJzAlBgNVBAoTHkFORiBBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lv bjEUMBIGA1UECxMLQU5GIENBIFJhaXoxIjAgBgNVBAMTGUFORiBTZWN1cmUgU2VydmVyIFJvb3Qg Q0EwHhcNMTkwOTA0MTAwMDM4WhcNMzkwODMwMTAwMDM4WjCBhDESMBAGA1UEBRMJRzYzMjg3NTEw MQswCQYDVQQGEwJFUzEnMCUGA1UEChMeQU5GIEF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uMRQw EgYDVQQLEwtBTkYgQ0EgUmFpejEiMCAGA1UEAxMZQU5GIFNlY3VyZSBTZXJ2ZXIgUm9vdCBDQTCC AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANvrayvmZFSVgpCjcqQZAZ2cC4Ffc0m6p6zz BE57lgvsEeBbphzOG9INgxwruJ4dfkUyYA8H6XdYfp9qyGFOtibBTI3/TO80sh9l2Ll49a2pcbnv T1gdpd50IJeh7WhM3pIXS7yr/2WanvtH2Vdy8wmhrnZEE26cLUQ5vPnHO6RYPUG9tMJJo8gN0pcv B2VSAKduyK9o7PQUlrZXH1bDOZ8rbeTzPvY1ZNoMHKGESy9LS+IsJJ1tk0DrtSOOMspvRdOoiXse zx76W0OLzc2oD2rKDF65nkeP8Nm2CgtYZRczuSPkdxl9y0oukntPLxB3sY0vaJxizOBQ+OyRp1RM VwnVdmPF6GUe7m1qzwmd+nxPrWAI/VaZDxUse6mAq4xhj0oHdkLePfTdsiQzW7i1o0TJrH93PB0j 7IKppuLIBkwC/qxcmZkLLxCKpvR/1Yd0DVlJRfbwcVw5Kda/SiOL9V8BY9KHcyi1Swr1+KuCLH5z JTIdC2MKF4EA/7Z2Xue0sUDKIbvVgFHlSFJnLNJhiQcND85Cd8BEc5xEUKDbEAotlRyBr+Qc5RQe 8TZBAQIvfXOn3kLMTOmJDVb3n5HUA8ZsyY/b2BzgQJhdZpmYgG4t/wHFzstGH6wCxkPmrqKEPMVO Hj1tyRRM4y5Bu8o5vzY8KhmqQYdOpc5LMnndkEl/AgMBAAGjYzBhMB8GA1UdIwQYMBaAFJxf0Gxj o1+TypOYCK2Mh6UsXME3MB0GA1UdDgQWBBScX9BsY6Nfk8qTmAitjIelLFzBNzAOBgNVHQ8BAf8E BAMCAYYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEATh65isagmD9uw2nAalxJ UqzLK114OMHVVISfk/CHGT0sZonrDUL8zPB1hT+L9IBdeeUXZ701guLyPI59WzbLWoAAKfLOKyzx j6ptBZNscsdW699QIyjlRRA96Gejrw5VD5AJYu9LWaL2U/HANeQvwSS9eS9OICI7/RogsKQOLHDt dD+4E5UGUcjohybKpFtqFiGS3XNgnhAY3jyB6ugYw3yJ8otQPr0R4hUDqDZ9MwFsSBXXiJCZBMXM 5gf0vPSQ7RPi6ovDj6MzD8EpTBNO2hVWcXNyglD2mjN8orGoGjR0ZVzO0eurU+AagNjqOknkJjCb 5RyKqKkVMoaZkgoQI1YS4PbOTOK7vtuNknMBZi9iPrJyJ0U27U1W45eZ/zo1PqVUSlJZS2Db7v54 EX9K3BR5YLZrZAPbFYPhor72I5dQ8AkzNqdxliXzuUJ92zg/LFis6ELhDtjTO0wugumDLmsx2d1H hk9tl5EuT+IocTUW0fJz/iUrB0ckYyfI+PbZa/wSMVYIwFNCr5zQM378BvAxRAMU8Vjq8moNqRGy g77FGr8H6lnco4g175x2MjxNBiLOFeXdntiP2t7SxDnlF4HPOEfrf4htWRvfn0IUrn7PqLBmZdo3 r5+qPeoott7VMVgWglvquxl1AnMaykgaIZOQCo6ThKd9OyMYkomgjaw= -----END CERTIFICATE----- Certum EC-384 CA ================ -----BEGIN CERTIFICATE----- MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQswCQYDVQQGEwJQ TDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2Vy dGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2 MDcyNDU0WhcNNDMwMzI2MDcyNDU0WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERh dGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkx GTAXBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATEKI6rGFtq vm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7TmFy8as10CW4kjPMIRBSqn iBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68KjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD VR0OBBYEFI0GZnQkdjrzife81r1HfS+8EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNo ADBlAjADVS2m5hjEfO/JUG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0 QoSZ/6vnnvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k= -----END CERTIFICATE----- Certum Trusted Root CA ====================== -----BEGIN CERTIFICATE----- MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6MQswCQYDVQQG EwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0g Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0Ew HhcNMTgwMzE2MTIxMDEzWhcNNDMwMzE2MTIxMDEzWjB6MQswCQYDVQQGEwJQTDEhMB8GA1UEChMY QXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBB dXRob3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEB AQUAA4ICDwAwggIKAoICAQDRLY67tzbqbTeRn06TpwXkKQMlzhyC93yZn0EGze2jusDbCSzBfN8p fktlL5On1AFrAygYo9idBcEq2EXxkd7fO9CAAozPOA/qp1x4EaTByIVcJdPTsuclzxFUl6s1wB52 HO8AU5853BSlLCIls3Jy/I2z5T4IHhQqNwuIPMqw9MjCoa68wb4pZ1Xi/K1ZXP69VyywkI3C7Te2 fJmItdUDmj0VDT06qKhF8JVOJVkdzZhpu9PMMsmN74H+rX2Ju7pgE8pllWeg8xn2A1bUatMn4qGt g/BKEiJ3HAVz4hlxQsDsdUaakFjgao4rpUYwBI4Zshfjvqm6f1bxJAPXsiEodg42MEx51UGamqi4 NboMOvJEGyCI98Ul1z3G4z5D3Yf+xOr1Uz5MZf87Sst4WmsXXw3Hw09Omiqi7VdNIuJGmj8PkTQk fVXjjJU30xrwCSss0smNtA0Aq2cpKNgB9RkEth2+dv5yXMSFytKAQd8FqKPVhJBPC/PgP5sZ0jeJ P/J7UhyM9uH3PAeXjA6iWYEMspA90+NZRu0PqafegGtaqge2Gcu8V/OXIXoMsSt0Puvap2ctTMSY njYJdmZm/Bo/6khUHL4wvYBQv3y1zgD2DGHZ5yQD4OMBgQ692IU0iL2yNqh7XAjlRICMb/gv1SHK HRzQ+8S1h9E6Tsd2tTVItQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSM+xx1 vALTn04uSNn5YFSqxLNP+jAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQENBQADggIBAEii1QAL LtA/vBzVtVRJHlpr9OTy4EA34MwUe7nJ+jW1dReTagVphZzNTxl4WxmB82M+w85bj/UvXgF2Ez8s ALnNllI5SW0ETsXpD4YN4fqzX4IS8TrOZgYkNCvozMrnadyHncI013nR03e4qllY/p0m+jiGPp2K h2RX5Rc64vmNueMzeMGQ2Ljdt4NR5MTMI9UGfOZR0800McD2RrsLrfw9EAUqO0qRJe6M1ISHgCq8 CYyqOhNf6DR5UMEQGfnTKB7U0VEwKbOukGfWHwpjscWpxkIxYxeU72nLL/qMFH3EQxiJ2fAyQOaA 4kZf5ePBAFmo+eggvIksDkc0C+pXwlM2/KfUrzHN/gLldfq5Jwn58/U7yn2fqSLLiMmq0Uc9Nneo WWRrJ8/vJ8HjJLWG965+Mk2weWjROeiQWMODvA8s1pfrzgzhIMfatz7DP78v3DSk+yshzWePS/Tj 6tQ/50+6uaWTRRxmHyH6ZF5v4HaUMst19W7l9o/HuKTMqJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmT OPQD8rv7gmsHINFSH5pkAnuYZttcTVoP0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZck bxJF0WddCajJFdr60qZfE2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb -----END CERTIFICATE----- TunTrust Root CA ================ -----BEGIN CERTIFICATE----- MIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQELBQAwYTELMAkG A1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUgQ2VydGlmaWNhdGlvbiBFbGVj dHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJvb3QgQ0EwHhcNMTkwNDI2MDg1NzU2WhcNNDQw NDI2MDg1NzU2WjBhMQswCQYDVQQGEwJUTjE3MDUGA1UECgwuQWdlbmNlIE5hdGlvbmFsZSBkZSBD ZXJ0aWZpY2F0aW9uIEVsZWN0cm9uaXF1ZTEZMBcGA1UEAwwQVHVuVHJ1c3QgUm9vdCBDQTCCAiIw DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMPN0/y9BFPdDCA61YguBUtB9YOCfvdZn56eY+hz 2vYGqU8ftPkLHzmMmiDQfgbU7DTZhrx1W4eI8NLZ1KMKsmwb60ksPqxd2JQDoOw05TDENX37Jk0b bjBU2PWARZw5rZzJJQRNmpA+TkBuimvNKWfGzC3gdOgFVwpIUPp6Q9p+7FuaDmJ2/uqdHYVy7BG7 NegfJ7/Boce7SBbdVtfMTqDhuazb1YMZGoXRlJfXyqNlC/M4+QKu3fZnz8k/9YosRxqZbwUN/dAd gjH8KcwAWJeRTIAAHDOFli/LQcKLEITDCSSJH7UP2dl3RxiSlGBcx5kDPP73lad9UKGAwqmDrViW VSHbhlnUr8a83YFuB9tgYv7sEG7aaAH0gxupPqJbI9dkxt/con3YS7qC0lH4Zr8GRuR5KiY2eY8f Tpkdso8MDhz/yV3A/ZAQprE38806JG60hZC/gLkMjNWb1sjxVj8agIl6qeIbMlEsPvLfe/ZdeikZ juXIvTZxi11Mwh0/rViizz1wTaZQmCXcI/m4WEEIcb9PuISgjwBUFfyRbVinljvrS5YnzWuioYas DXxU5mZMZl+QviGaAkYt5IPCgLnPSz7ofzwB7I9ezX/SKEIBlYrilz0QIX32nRzFNKHsLA4KUiwS VXAkPcvCFDVDXSdOvsC9qnyW5/yeYa1E0wCXAgMBAAGjYzBhMB0GA1UdDgQWBBQGmpsfU33x9aTI 04Y+oXNZtPdEITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFAaamx9TffH1pMjThj6hc1m0 90QhMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAqgVutt0Vyb+zxiD2BkewhpMl 0425yAA/l/VSJ4hxyXT968pk21vvHl26v9Hr7lxpuhbI87mP0zYuQEkHDVneixCwSQXi/5E/S7fd Ao74gShczNxtr18UnH1YeA32gAm56Q6XKRm4t+v4FstVEuTGfbvE7Pi1HE4+Z7/FXxttbUcoqgRY YdZ2vyJ/0Adqp2RT8JeNnYA/u8EH22Wv5psymsNUk8QcCMNE+3tjEUPRahphanltkE8pjkcFwRJp adbGNjHh/PqAulxPxOu3Mqz4dWEX1xAZufHSCe96Qp1bWgvUxpVOKs7/B9dPfhgGiPEZtdmYu65x xBzndFlY7wyJz4sfdZMaBBSSSFCp61cpABbjNhzI+L/wM9VBD8TMPN3pM0MBkRArHtG5Xc0yGYuP jCB31yLEQtyEFpslbei0VXF/sHyz03FJuc9SpAQ/3D2gu68zngowYI7bnV2UqL1g52KAdoGDDIzM MEZJ4gzSqK/rYXHv5yJiqfdcZGyfFoxnNidF9Ql7v/YQCvGwjVRDjAS6oz/v4jXH+XTgbzRB0L9z ZVcg+ZtnemZoJE6AZb0QmQZZ8mWvuMZHu/2QeItBcy6vVR/cO5JyboTT0GFMDcx2V+IthSIVNg3r AZ3r2OvEhJn7wAzMMujjd9qDRIueVSjAi1jTkD5OGwDxFa2DK5o= -----END CERTIFICATE----- HARICA TLS RSA Root CA 2021 =========================== -----BEGIN CERTIFICATE----- MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQG EwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u cyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0EgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUz OFoXDTQ1MDIxMzEwNTUzN1owbDELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRl bWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNB IFJvb3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569lmwVnlskN JLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE4VGC/6zStGndLuwRo0Xu a2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uva9of08WRiFukiZLRgeaMOVig1mlDqa2Y Ulhu2wr7a89o+uOkXjpFc5gH6l8Cct4MpbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K 5FrZx40d/JiZ+yykgmvwKh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEv dmn8kN3bLW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcYAuUR 0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqBAGMUuTNe3QvboEUH GjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYqE613TBoYm5EPWNgGVMWX+Ko/IIqm haZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHrW2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQ CPxrvrNQKlr9qEgYRtaQQJKQCoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8G A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAUX15QvWiWkKQU EapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3f5Z2EMVGpdAgS1D0NTsY9FVq QRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxajaH6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxD QpSbIPDRzbLrLFPCU3hKTwSUQZqPJzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcR j88YxeMn/ibvBZ3PzzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5 vZStjBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0/L5H9MG0 qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pTBGIBnfHAT+7hOtSLIBD6 Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79aPib8qXPMThcFarmlwDB31qlpzmq6YR/ PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YWxw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnn kf3/W9b3raYvAwtt41dU63ZTGI0RmLo= -----END CERTIFICATE----- HARICA TLS ECC Root CA 2021 =========================== -----BEGIN CERTIFICATE----- MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQswCQYDVQQGEwJH UjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBD QTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoX DTQ1MDIxMzExMDEwOVowbDELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWlj IGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJv b3QgQ0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7KKrxcm1l AEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9YSTHMmE5gEYd103KUkE+b ECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW 0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAi rcJRQO9gcS3ujwLEXQNwSaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/Qw CZ61IygNnxS2PFOiTAZpffpskcYqSUXm7LcT4Tps -----END CERTIFICATE----- HARICA Client RSA Root CA 2021 ============================== -----BEGIN CERTIFICATE----- MIIFqjCCA5KgAwIBAgIQVVL4HtsbJCyeu5YYzQIoPjANBgkqhkiG9w0BAQsFADBvMQswCQYDVQQG EwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u cyBDQTEnMCUGA1UEAwweSEFSSUNBIENsaWVudCBSU0EgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTEw NTg0NloXDTQ1MDIxMzEwNTg0NVowbzELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFj YWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJzAlBgNVBAMMHkhBUklDQSBDbGll bnQgUlNBIFJvb3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIHbV0KQ LHQ19Pi4dBlNqwlad0WBc2KwNZ/40LczAIcTtparDlQSMAe8m7dI19EZg66O2KnxqQCEsIxenugM j1Rpv/bUCE8mcP4YQWMaszKLQPgHq1cx8MYWdmeatN0v8tFrxdCShJFxbg8uY+kfU6TdUhPMCYMp gQzFU3VEsQ5nUxjQwx+IS5+UJLQpvLvoTv1v0hUdSdyNcPIRGiBRVRG6iG/E91B51qox4oQ9XjLI dypQceULL+m26u+rCjM5Dv2PpWdDgo6YaQkJG0DNOGdH6snsl3ES3iT1cjzR90NMJveQsonpRUtV PTEFekHilbpDwBfFtoU9GY1kcPNbrM2f0yl1h0uVZ2qm+NHdvJCGiUMpqTdb9V2wJlpTQnaQK8+e VmwrVM9cmmXfW4tIYDh8+8ULz3YEYwIzKn31g2fn+sZD/SsP1CYvd6QywSTqZJ2/szhxMUTyR7ii ZkGh+5t7vMdGanW/WqKM6GpEwbiWtcAyCC17dDVzssrG/q8Rchj258jCz6Uq6nvWWeh8oLJqQAlp DqWW29EAufGIbjbwiLKd8VLyw3y/MIk8Cmn5IqRl4ZvgdMaxhZeWLK6Uj1CmORIfvkfygXjTdTae fVogl+JSrpmfxnybZvP+2M/uvZcGHS2F3D42U5Z7ILroyOGtlmI+EXyzAISep0xxq0o3AgMBAAGj QjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFKDWBz1eJPd7oEQuJFINGaorBJGnMA4GA1Ud DwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEADUf5CWYxUux57sKo8mg+7ZZFyzqmmGM/6itN TgPQHILhy9Pl1qtbZyi8nf4MmQqAVafOGyNhDbBX8P7gyr7mkNuDLL6DjvR5tv7QDUKnWB9p6oH1 BaX+RmjrbHjJ4Orn5t4xxdLVLIJjKJ1dqBp+iObnK/Es1dAFntwtvTdm1ASip62/OsKoO63/jZ0z 4LmahKGHH3b0gnTXDvkwSD5biD6qXGvWLwzojnPCGJGDObZmWtAfYCddTeP2Og1mUJx4e6vzExCu Dy+r6GSzGCCdRjVkJXPqmxBcWDWJsUZIp/Ss1B2eW8yppRoTTyRQqtkbbbFA+53dWHTEwm8Ucuzb NZ+4VHVFw6bIGig1Oq5l8qmYzq9byTiMMTt/zNyW/eJb1tBZ9Ha6C8tPgxDHQNAdYOkq5UhYdwxF ab4ZcQQk4uMkH0rIwT6Z9ZaYOEgloRWwG9fihBhb9nE1mmh7QMwYXAwkndSV9ZmqRuqurL/0FBkk 6Izs4/W8BmiKKgwFXwqXdafcfsD913oY3zDROEsfsJhwv8x8c/BuxDGlpJcdrL/ObCFKvicjZ/MG VoEKkY624QMFMyzaNAhNTlAjrR+lxdR6/uoJ7KcoYItGfLXqm91P+edrFcaIz0Pb5SfcBFZub0YV 8VYt6FwMc8MjgTggy8kMac8sqzuEYDMZUv1pFDM= -----END CERTIFICATE----- HARICA Client ECC Root CA 2021 ============================== -----BEGIN CERTIFICATE----- MIICWjCCAeGgAwIBAgIQMWjZ2OFiVx7SGUSI5hB98DAKBggqhkjOPQQDAzBvMQswCQYDVQQGEwJH UjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBD QTEnMCUGA1UEAwweSEFSSUNBIENsaWVudCBFQ0MgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTExMDMz NFoXDTQ1MDIxMzExMDMzM1owbzELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRl bWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJzAlBgNVBAMMHkhBUklDQSBDbGllbnQg RUNDIFJvb3QgQ0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABAcYrZWWlNBcD4L3KkD6AsnJ PTamowRqwW2VAYhgElRsXKIrbhM6iJUMHCaGNkqJGbcY3jvoqFAfyt9bv0mAFdvjMOEdWscqigEH /m0sNO8oKJe8wflXhpWLNc+eWtFolaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUUgjS vjKBJf31GpfsTl8au1PNkK0wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2cAMGQCMEwxRUZP qOa+w3eyGhhLLYh7WOarlGtEA7AX/9+Cc0RRLP2THQZ7FNKJ7EAM7yEBLgIwL8kuWmwsHdmV4J6w uVxSfPb4OMou8dQd8qJJopX4wVheT/5zCu8xsKsjWBOMi947 -----END CERTIFICATE----- proftpd-mod_proxy-0.9.5/config.guess000077500000000000000000001414221475737016700175660ustar00rootroot00000000000000#! /bin/sh # Attempt to guess a canonical system name. # Copyright 1992-2023 Free Software Foundation, Inc. # shellcheck disable=SC2006,SC2268 # see below for rationale timestamp='2023-01-01' # This file is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, see . # # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that # program. This Exception is an additional permission under section 7 # of the GNU General Public License, version 3 ("GPLv3"). # # Originally written by Per Bothner; maintained since 2000 by Ben Elliston. # # You can get the latest version of this script from: # https://git.savannah.gnu.org/cgit/config.git/plain/config.guess # # Please send patches to . # The "shellcheck disable" line above the timestamp inhibits complaints # about features and limitations of the classic Bourne shell that were # superseded or lifted in POSIX. However, this script identifies a wide # variety of pre-POSIX systems that do not have POSIX shells at all, and # even some reasonably current systems (Solaris 10 as case-in-point) still # have a pre-POSIX /bin/sh. me=`echo "$0" | sed -e 's,.*/,,'` usage="\ Usage: $0 [OPTION] Output the configuration name of the system \`$me' is run on. Options: -h, --help print this help, then exit -t, --time-stamp print date of last modification, then exit -v, --version print version number, then exit Report bugs and patches to ." version="\ GNU config.guess ($timestamp) Originally written by Per Bothner. Copyright 1992-2023 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." help=" Try \`$me --help' for more information." # Parse command line while test $# -gt 0 ; do case $1 in --time-stamp | --time* | -t ) echo "$timestamp" ; exit ;; --version | -v ) echo "$version" ; exit ;; --help | --h* | -h ) echo "$usage"; exit ;; -- ) # Stop option processing shift; break ;; - ) # Use stdin as input. break ;; -* ) echo "$me: invalid option $1$help" >&2 exit 1 ;; * ) break ;; esac done if test $# != 0; then echo "$me: too many arguments$help" >&2 exit 1 fi # Just in case it came from the environment. GUESS= # CC_FOR_BUILD -- compiler used by this script. Note that the use of a # compiler to aid in system detection is discouraged as it requires # temporary files to be created and, as you can see below, it is a # headache to deal with in a portable fashion. # Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still # use `HOST_CC' if defined, but it is deprecated. # Portable tmp directory creation inspired by the Autoconf team. tmp= # shellcheck disable=SC2172 trap 'test -z "$tmp" || rm -fr "$tmp"' 0 1 2 13 15 set_cc_for_build() { # prevent multiple calls if $tmp is already set test "$tmp" && return 0 : "${TMPDIR=/tmp}" # shellcheck disable=SC2039,SC3028 { tmp=`(umask 077 && mktemp -d "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } || { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir "$tmp" 2>/dev/null) ; } || { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir "$tmp" 2>/dev/null) && echo "Warning: creating insecure temp directory" >&2 ; } || { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } dummy=$tmp/dummy case ${CC_FOR_BUILD-},${HOST_CC-},${CC-} in ,,) echo "int x;" > "$dummy.c" for driver in cc gcc c89 c99 ; do if ($driver -c -o "$dummy.o" "$dummy.c") >/dev/null 2>&1 ; then CC_FOR_BUILD=$driver break fi done if test x"$CC_FOR_BUILD" = x ; then CC_FOR_BUILD=no_compiler_found fi ;; ,,*) CC_FOR_BUILD=$CC ;; ,*,*) CC_FOR_BUILD=$HOST_CC ;; esac } # This is needed to find uname on a Pyramid OSx when run in the BSD universe. # (ghazi@noc.rutgers.edu 1994-08-24) if test -f /.attbin/uname ; then PATH=$PATH:/.attbin ; export PATH fi UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown case $UNAME_SYSTEM in Linux|GNU|GNU/*) LIBC=unknown set_cc_for_build cat <<-EOF > "$dummy.c" #include #if defined(__UCLIBC__) LIBC=uclibc #elif defined(__dietlibc__) LIBC=dietlibc #elif defined(__GLIBC__) LIBC=gnu #else #include /* First heuristic to detect musl libc. */ #ifdef __DEFINED_va_list LIBC=musl #endif #endif EOF cc_set_libc=`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^LIBC' | sed 's, ,,g'` eval "$cc_set_libc" # Second heuristic to detect musl libc. if [ "$LIBC" = unknown ] && command -v ldd >/dev/null && ldd --version 2>&1 | grep -q ^musl; then LIBC=musl fi # If the system lacks a compiler, then just pick glibc. # We could probably try harder. if [ "$LIBC" = unknown ]; then LIBC=gnu fi ;; esac # Note: order is significant - the case branches are not exclusive. case $UNAME_MACHINE:$UNAME_SYSTEM:$UNAME_RELEASE:$UNAME_VERSION in *:NetBSD:*:*) # NetBSD (nbsd) targets should (where applicable) match one or # more of the tuples: *-*-netbsdelf*, *-*-netbsdaout*, # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently # switched to ELF, *-*-netbsd* would select the old # object file format. This provides both forward # compatibility and a consistent mechanism for selecting the # object file format. # # Note: NetBSD doesn't particularly care about the vendor # portion of the name. We always set it to "unknown". UNAME_MACHINE_ARCH=`(uname -p 2>/dev/null || \ /sbin/sysctl -n hw.machine_arch 2>/dev/null || \ /usr/sbin/sysctl -n hw.machine_arch 2>/dev/null || \ echo unknown)` case $UNAME_MACHINE_ARCH in aarch64eb) machine=aarch64_be-unknown ;; armeb) machine=armeb-unknown ;; arm*) machine=arm-unknown ;; sh3el) machine=shl-unknown ;; sh3eb) machine=sh-unknown ;; sh5el) machine=sh5le-unknown ;; earmv*) arch=`echo "$UNAME_MACHINE_ARCH" | sed -e 's,^e\(armv[0-9]\).*$,\1,'` endian=`echo "$UNAME_MACHINE_ARCH" | sed -ne 's,^.*\(eb\)$,\1,p'` machine=${arch}${endian}-unknown ;; *) machine=$UNAME_MACHINE_ARCH-unknown ;; esac # The Operating System including object format, if it has switched # to ELF recently (or will in the future) and ABI. case $UNAME_MACHINE_ARCH in earm*) os=netbsdelf ;; arm*|i386|m68k|ns32k|sh3*|sparc|vax) set_cc_for_build if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \ | grep -q __ELF__ then # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout). # Return netbsd for either. FIX? os=netbsd else os=netbsdelf fi ;; *) os=netbsd ;; esac # Determine ABI tags. case $UNAME_MACHINE_ARCH in earm*) expr='s/^earmv[0-9]/-eabi/;s/eb$//' abi=`echo "$UNAME_MACHINE_ARCH" | sed -e "$expr"` ;; esac # The OS release # Debian GNU/NetBSD machines have a different userland, and # thus, need a distinct triplet. However, they do not need # kernel version information, so it can be replaced with a # suitable tag, in the style of linux-gnu. case $UNAME_VERSION in Debian*) release='-gnu' ;; *) release=`echo "$UNAME_RELEASE" | sed -e 's/[-_].*//' | cut -d. -f1,2` ;; esac # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: # contains redundant information, the shorter form: # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. GUESS=$machine-${os}${release}${abi-} ;; *:Bitrig:*:*) UNAME_MACHINE_ARCH=`arch | sed 's/Bitrig.//'` GUESS=$UNAME_MACHINE_ARCH-unknown-bitrig$UNAME_RELEASE ;; *:OpenBSD:*:*) UNAME_MACHINE_ARCH=`arch | sed 's/OpenBSD.//'` GUESS=$UNAME_MACHINE_ARCH-unknown-openbsd$UNAME_RELEASE ;; *:SecBSD:*:*) UNAME_MACHINE_ARCH=`arch | sed 's/SecBSD.//'` GUESS=$UNAME_MACHINE_ARCH-unknown-secbsd$UNAME_RELEASE ;; *:LibertyBSD:*:*) UNAME_MACHINE_ARCH=`arch | sed 's/^.*BSD\.//'` GUESS=$UNAME_MACHINE_ARCH-unknown-libertybsd$UNAME_RELEASE ;; *:MidnightBSD:*:*) GUESS=$UNAME_MACHINE-unknown-midnightbsd$UNAME_RELEASE ;; *:ekkoBSD:*:*) GUESS=$UNAME_MACHINE-unknown-ekkobsd$UNAME_RELEASE ;; *:SolidBSD:*:*) GUESS=$UNAME_MACHINE-unknown-solidbsd$UNAME_RELEASE ;; *:OS108:*:*) GUESS=$UNAME_MACHINE-unknown-os108_$UNAME_RELEASE ;; macppc:MirBSD:*:*) GUESS=powerpc-unknown-mirbsd$UNAME_RELEASE ;; *:MirBSD:*:*) GUESS=$UNAME_MACHINE-unknown-mirbsd$UNAME_RELEASE ;; *:Sortix:*:*) GUESS=$UNAME_MACHINE-unknown-sortix ;; *:Twizzler:*:*) GUESS=$UNAME_MACHINE-unknown-twizzler ;; *:Redox:*:*) GUESS=$UNAME_MACHINE-unknown-redox ;; mips:OSF1:*.*) GUESS=mips-dec-osf1 ;; alpha:OSF1:*:*) # Reset EXIT trap before exiting to avoid spurious non-zero exit code. trap '' 0 case $UNAME_RELEASE in *4.0) UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` ;; *5.*) UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'` ;; esac # According to Compaq, /usr/sbin/psrinfo has been available on # OSF/1 and Tru64 systems produced since 1995. I hope that # covers most systems running today. This code pipes the CPU # types through head -n 1, so we only detect the type of CPU 0. ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1` case $ALPHA_CPU_TYPE in "EV4 (21064)") UNAME_MACHINE=alpha ;; "EV4.5 (21064)") UNAME_MACHINE=alpha ;; "LCA4 (21066/21068)") UNAME_MACHINE=alpha ;; "EV5 (21164)") UNAME_MACHINE=alphaev5 ;; "EV5.6 (21164A)") UNAME_MACHINE=alphaev56 ;; "EV5.6 (21164PC)") UNAME_MACHINE=alphapca56 ;; "EV5.7 (21164PC)") UNAME_MACHINE=alphapca57 ;; "EV6 (21264)") UNAME_MACHINE=alphaev6 ;; "EV6.7 (21264A)") UNAME_MACHINE=alphaev67 ;; "EV6.8CB (21264C)") UNAME_MACHINE=alphaev68 ;; "EV6.8AL (21264B)") UNAME_MACHINE=alphaev68 ;; "EV6.8CX (21264D)") UNAME_MACHINE=alphaev68 ;; "EV6.9A (21264/EV69A)") UNAME_MACHINE=alphaev69 ;; "EV7 (21364)") UNAME_MACHINE=alphaev7 ;; "EV7.9 (21364A)") UNAME_MACHINE=alphaev79 ;; esac # A Pn.n version is a patched version. # A Vn.n version is a released version. # A Tn.n version is a released field test version. # A Xn.n version is an unreleased experimental baselevel. # 1.2 uses "1.2" for uname -r. OSF_REL=`echo "$UNAME_RELEASE" | sed -e 's/^[PVTX]//' | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz` GUESS=$UNAME_MACHINE-dec-osf$OSF_REL ;; Amiga*:UNIX_System_V:4.0:*) GUESS=m68k-unknown-sysv4 ;; *:[Aa]miga[Oo][Ss]:*:*) GUESS=$UNAME_MACHINE-unknown-amigaos ;; *:[Mm]orph[Oo][Ss]:*:*) GUESS=$UNAME_MACHINE-unknown-morphos ;; *:OS/390:*:*) GUESS=i370-ibm-openedition ;; *:z/VM:*:*) GUESS=s390-ibm-zvmoe ;; *:OS400:*:*) GUESS=powerpc-ibm-os400 ;; arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) GUESS=arm-acorn-riscix$UNAME_RELEASE ;; arm*:riscos:*:*|arm*:RISCOS:*:*) GUESS=arm-unknown-riscos ;; SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) GUESS=hppa1.1-hitachi-hiuxmpp ;; Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. case `(/bin/universe) 2>/dev/null` in att) GUESS=pyramid-pyramid-sysv3 ;; *) GUESS=pyramid-pyramid-bsd ;; esac ;; NILE*:*:*:dcosx) GUESS=pyramid-pyramid-svr4 ;; DRS?6000:unix:4.0:6*) GUESS=sparc-icl-nx6 ;; DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*) case `/usr/bin/uname -p` in sparc) GUESS=sparc-icl-nx7 ;; esac ;; s390x:SunOS:*:*) SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` GUESS=$UNAME_MACHINE-ibm-solaris2$SUN_REL ;; sun4H:SunOS:5.*:*) SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` GUESS=sparc-hal-solaris2$SUN_REL ;; sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` GUESS=sparc-sun-solaris2$SUN_REL ;; i86pc:AuroraUX:5.*:* | i86xen:AuroraUX:5.*:*) GUESS=i386-pc-auroraux$UNAME_RELEASE ;; i86pc:SunOS:5.*:* | i86xen:SunOS:5.*:*) set_cc_for_build SUN_ARCH=i386 # If there is a compiler, see if it is configured for 64-bit objects. # Note that the Sun cc does not turn __LP64__ into 1 like gcc does. # This test works for both compilers. if test "$CC_FOR_BUILD" != no_compiler_found; then if (echo '#ifdef __amd64'; echo IS_64BIT_ARCH; echo '#endif') | \ (CCOPTS="" $CC_FOR_BUILD -m64 -E - 2>/dev/null) | \ grep IS_64BIT_ARCH >/dev/null then SUN_ARCH=x86_64 fi fi SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` GUESS=$SUN_ARCH-pc-solaris2$SUN_REL ;; sun4*:SunOS:6*:*) # According to config.sub, this is the proper way to canonicalize # SunOS6. Hard to guess exactly what SunOS6 will be like, but # it's likely to be more like Solaris than SunOS4. SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` GUESS=sparc-sun-solaris3$SUN_REL ;; sun4*:SunOS:*:*) case `/usr/bin/arch -k` in Series*|S4*) UNAME_RELEASE=`uname -v` ;; esac # Japanese Language versions have a version number like `4.1.3-JL'. SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/-/_/'` GUESS=sparc-sun-sunos$SUN_REL ;; sun3*:SunOS:*:*) GUESS=m68k-sun-sunos$UNAME_RELEASE ;; sun*:*:4.2BSD:*) UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` test "x$UNAME_RELEASE" = x && UNAME_RELEASE=3 case `/bin/arch` in sun3) GUESS=m68k-sun-sunos$UNAME_RELEASE ;; sun4) GUESS=sparc-sun-sunos$UNAME_RELEASE ;; esac ;; aushp:SunOS:*:*) GUESS=sparc-auspex-sunos$UNAME_RELEASE ;; # The situation for MiNT is a little confusing. The machine name # can be virtually everything (everything which is not # "atarist" or "atariste" at least should have a processor # > m68000). The system name ranges from "MiNT" over "FreeMiNT" # to the lowercase version "mint" (or "freemint"). Finally # the system name "TOS" denotes a system which is actually not # MiNT. But MiNT is downward compatible to TOS, so this should # be no problem. atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) GUESS=m68k-atari-mint$UNAME_RELEASE ;; atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) GUESS=m68k-atari-mint$UNAME_RELEASE ;; *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) GUESS=m68k-atari-mint$UNAME_RELEASE ;; milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) GUESS=m68k-milan-mint$UNAME_RELEASE ;; hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) GUESS=m68k-hades-mint$UNAME_RELEASE ;; *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) GUESS=m68k-unknown-mint$UNAME_RELEASE ;; m68k:machten:*:*) GUESS=m68k-apple-machten$UNAME_RELEASE ;; powerpc:machten:*:*) GUESS=powerpc-apple-machten$UNAME_RELEASE ;; RISC*:Mach:*:*) GUESS=mips-dec-mach_bsd4.3 ;; RISC*:ULTRIX:*:*) GUESS=mips-dec-ultrix$UNAME_RELEASE ;; VAX*:ULTRIX*:*:*) GUESS=vax-dec-ultrix$UNAME_RELEASE ;; 2020:CLIX:*:* | 2430:CLIX:*:*) GUESS=clipper-intergraph-clix$UNAME_RELEASE ;; mips:*:*:UMIPS | mips:*:*:RISCos) set_cc_for_build sed 's/^ //' << EOF > "$dummy.c" #ifdef __cplusplus #include /* for printf() prototype */ int main (int argc, char *argv[]) { #else int main (argc, argv) int argc; char *argv[]; { #endif #if defined (host_mips) && defined (MIPSEB) #if defined (SYSTYPE_SYSV) printf ("mips-mips-riscos%ssysv\\n", argv[1]); exit (0); #endif #if defined (SYSTYPE_SVR4) printf ("mips-mips-riscos%ssvr4\\n", argv[1]); exit (0); #endif #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) printf ("mips-mips-riscos%sbsd\\n", argv[1]); exit (0); #endif #endif exit (-1); } EOF $CC_FOR_BUILD -o "$dummy" "$dummy.c" && dummyarg=`echo "$UNAME_RELEASE" | sed -n 's/\([0-9]*\).*/\1/p'` && SYSTEM_NAME=`"$dummy" "$dummyarg"` && { echo "$SYSTEM_NAME"; exit; } GUESS=mips-mips-riscos$UNAME_RELEASE ;; Motorola:PowerMAX_OS:*:*) GUESS=powerpc-motorola-powermax ;; Motorola:*:4.3:PL8-*) GUESS=powerpc-harris-powermax ;; Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*) GUESS=powerpc-harris-powermax ;; Night_Hawk:Power_UNIX:*:*) GUESS=powerpc-harris-powerunix ;; m88k:CX/UX:7*:*) GUESS=m88k-harris-cxux7 ;; m88k:*:4*:R4*) GUESS=m88k-motorola-sysv4 ;; m88k:*:3*:R3*) GUESS=m88k-motorola-sysv3 ;; AViiON:dgux:*:*) # DG/UX returns AViiON for all architectures UNAME_PROCESSOR=`/usr/bin/uname -p` if test "$UNAME_PROCESSOR" = mc88100 || test "$UNAME_PROCESSOR" = mc88110 then if test "$TARGET_BINARY_INTERFACE"x = m88kdguxelfx || \ test "$TARGET_BINARY_INTERFACE"x = x then GUESS=m88k-dg-dgux$UNAME_RELEASE else GUESS=m88k-dg-dguxbcs$UNAME_RELEASE fi else GUESS=i586-dg-dgux$UNAME_RELEASE fi ;; M88*:DolphinOS:*:*) # DolphinOS (SVR3) GUESS=m88k-dolphin-sysv3 ;; M88*:*:R3*:*) # Delta 88k system running SVR3 GUESS=m88k-motorola-sysv3 ;; XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) GUESS=m88k-tektronix-sysv3 ;; Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) GUESS=m68k-tektronix-bsd ;; *:IRIX*:*:*) IRIX_REL=`echo "$UNAME_RELEASE" | sed -e 's/-/_/g'` GUESS=mips-sgi-irix$IRIX_REL ;; ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. GUESS=romp-ibm-aix # uname -m gives an 8 hex-code CPU id ;; # Note that: echo "'`uname -s`'" gives 'AIX ' i*86:AIX:*:*) GUESS=i386-ibm-aix ;; ia64:AIX:*:*) if test -x /usr/bin/oslevel ; then IBM_REV=`/usr/bin/oslevel` else IBM_REV=$UNAME_VERSION.$UNAME_RELEASE fi GUESS=$UNAME_MACHINE-ibm-aix$IBM_REV ;; *:AIX:2:3) if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then set_cc_for_build sed 's/^ //' << EOF > "$dummy.c" #include main() { if (!__power_pc()) exit(1); puts("powerpc-ibm-aix3.2.5"); exit(0); } EOF if $CC_FOR_BUILD -o "$dummy" "$dummy.c" && SYSTEM_NAME=`"$dummy"` then GUESS=$SYSTEM_NAME else GUESS=rs6000-ibm-aix3.2.5 fi elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then GUESS=rs6000-ibm-aix3.2.4 else GUESS=rs6000-ibm-aix3.2 fi ;; *:AIX:*:[4567]) IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'` if /usr/sbin/lsattr -El "$IBM_CPU_ID" | grep ' POWER' >/dev/null 2>&1; then IBM_ARCH=rs6000 else IBM_ARCH=powerpc fi if test -x /usr/bin/lslpp ; then IBM_REV=`/usr/bin/lslpp -Lqc bos.rte.libc | \ awk -F: '{ print $3 }' | sed s/[0-9]*$/0/` else IBM_REV=$UNAME_VERSION.$UNAME_RELEASE fi GUESS=$IBM_ARCH-ibm-aix$IBM_REV ;; *:AIX:*:*) GUESS=rs6000-ibm-aix ;; ibmrt:4.4BSD:*|romp-ibm:4.4BSD:*) GUESS=romp-ibm-bsd4.4 ;; ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and GUESS=romp-ibm-bsd$UNAME_RELEASE # 4.3 with uname added to ;; # report: romp-ibm BSD 4.3 *:BOSX:*:*) GUESS=rs6000-bull-bosx ;; DPX/2?00:B.O.S.:*:*) GUESS=m68k-bull-sysv3 ;; 9000/[34]??:4.3bsd:1.*:*) GUESS=m68k-hp-bsd ;; hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) GUESS=m68k-hp-bsd4.4 ;; 9000/[34678]??:HP-UX:*:*) HPUX_REV=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*.[0B]*//'` case $UNAME_MACHINE in 9000/31?) HP_ARCH=m68000 ;; 9000/[34]??) HP_ARCH=m68k ;; 9000/[678][0-9][0-9]) if test -x /usr/bin/getconf; then sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null` sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null` case $sc_cpu_version in 523) HP_ARCH=hppa1.0 ;; # CPU_PA_RISC1_0 528) HP_ARCH=hppa1.1 ;; # CPU_PA_RISC1_1 532) # CPU_PA_RISC2_0 case $sc_kernel_bits in 32) HP_ARCH=hppa2.0n ;; 64) HP_ARCH=hppa2.0w ;; '') HP_ARCH=hppa2.0 ;; # HP-UX 10.20 esac ;; esac fi if test "$HP_ARCH" = ""; then set_cc_for_build sed 's/^ //' << EOF > "$dummy.c" #define _HPUX_SOURCE #include #include int main () { #if defined(_SC_KERNEL_BITS) long bits = sysconf(_SC_KERNEL_BITS); #endif long cpu = sysconf (_SC_CPU_VERSION); switch (cpu) { case CPU_PA_RISC1_0: puts ("hppa1.0"); break; case CPU_PA_RISC1_1: puts ("hppa1.1"); break; case CPU_PA_RISC2_0: #if defined(_SC_KERNEL_BITS) switch (bits) { case 64: puts ("hppa2.0w"); break; case 32: puts ("hppa2.0n"); break; default: puts ("hppa2.0"); break; } break; #else /* !defined(_SC_KERNEL_BITS) */ puts ("hppa2.0"); break; #endif default: puts ("hppa1.0"); break; } exit (0); } EOF (CCOPTS="" $CC_FOR_BUILD -o "$dummy" "$dummy.c" 2>/dev/null) && HP_ARCH=`"$dummy"` test -z "$HP_ARCH" && HP_ARCH=hppa fi ;; esac if test "$HP_ARCH" = hppa2.0w then set_cc_for_build # hppa2.0w-hp-hpux* has a 64-bit kernel and a compiler generating # 32-bit code. hppa64-hp-hpux* has the same kernel and a compiler # generating 64-bit code. GNU and HP use different nomenclature: # # $ CC_FOR_BUILD=cc ./config.guess # => hppa2.0w-hp-hpux11.23 # $ CC_FOR_BUILD="cc +DA2.0w" ./config.guess # => hppa64-hp-hpux11.23 if echo __LP64__ | (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | grep -q __LP64__ then HP_ARCH=hppa2.0w else HP_ARCH=hppa64 fi fi GUESS=$HP_ARCH-hp-hpux$HPUX_REV ;; ia64:HP-UX:*:*) HPUX_REV=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*.[0B]*//'` GUESS=ia64-hp-hpux$HPUX_REV ;; 3050*:HI-UX:*:*) set_cc_for_build sed 's/^ //' << EOF > "$dummy.c" #include int main () { long cpu = sysconf (_SC_CPU_VERSION); /* The order matters, because CPU_IS_HP_MC68K erroneously returns true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct results, however. */ if (CPU_IS_PA_RISC (cpu)) { switch (cpu) { case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; default: puts ("hppa-hitachi-hiuxwe2"); break; } } else if (CPU_IS_HP_MC68K (cpu)) puts ("m68k-hitachi-hiuxwe2"); else puts ("unknown-hitachi-hiuxwe2"); exit (0); } EOF $CC_FOR_BUILD -o "$dummy" "$dummy.c" && SYSTEM_NAME=`"$dummy"` && { echo "$SYSTEM_NAME"; exit; } GUESS=unknown-hitachi-hiuxwe2 ;; 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:*) GUESS=hppa1.1-hp-bsd ;; 9000/8??:4.3bsd:*:*) GUESS=hppa1.0-hp-bsd ;; *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*) GUESS=hppa1.0-hp-mpeix ;; hp7??:OSF1:*:* | hp8?[79]:OSF1:*:*) GUESS=hppa1.1-hp-osf ;; hp8??:OSF1:*:*) GUESS=hppa1.0-hp-osf ;; i*86:OSF1:*:*) if test -x /usr/sbin/sysversion ; then GUESS=$UNAME_MACHINE-unknown-osf1mk else GUESS=$UNAME_MACHINE-unknown-osf1 fi ;; parisc*:Lites*:*:*) GUESS=hppa1.1-hp-lites ;; C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) GUESS=c1-convex-bsd ;; C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) if getsysinfo -f scalar_acc then echo c32-convex-bsd else echo c2-convex-bsd fi exit ;; C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) GUESS=c34-convex-bsd ;; C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) GUESS=c38-convex-bsd ;; C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) GUESS=c4-convex-bsd ;; CRAY*Y-MP:*:*:*) CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'` GUESS=ymp-cray-unicos$CRAY_REL ;; CRAY*[A-Z]90:*:*:*) echo "$UNAME_MACHINE"-cray-unicos"$UNAME_RELEASE" \ | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \ -e 's/\.[^.]*$/.X/' exit ;; CRAY*TS:*:*:*) CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'` GUESS=t90-cray-unicos$CRAY_REL ;; CRAY*T3E:*:*:*) CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'` GUESS=alphaev5-cray-unicosmk$CRAY_REL ;; CRAY*SV1:*:*:*) CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'` GUESS=sv1-cray-unicos$CRAY_REL ;; *:UNICOS/mp:*:*) CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'` GUESS=craynv-cray-unicosmp$CRAY_REL ;; F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*) FUJITSU_PROC=`uname -m | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz` FUJITSU_SYS=`uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///'` FUJITSU_REL=`echo "$UNAME_RELEASE" | sed -e 's/ /_/'` GUESS=${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL} ;; 5000:UNIX_System_V:4.*:*) FUJITSU_SYS=`uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///'` FUJITSU_REL=`echo "$UNAME_RELEASE" | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/ /_/'` GUESS=sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL} ;; i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) GUESS=$UNAME_MACHINE-pc-bsdi$UNAME_RELEASE ;; sparc*:BSD/OS:*:*) GUESS=sparc-unknown-bsdi$UNAME_RELEASE ;; *:BSD/OS:*:*) GUESS=$UNAME_MACHINE-unknown-bsdi$UNAME_RELEASE ;; arm:FreeBSD:*:*) UNAME_PROCESSOR=`uname -p` set_cc_for_build if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \ | grep -q __ARM_PCS_VFP then FREEBSD_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'` GUESS=$UNAME_PROCESSOR-unknown-freebsd$FREEBSD_REL-gnueabi else FREEBSD_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'` GUESS=$UNAME_PROCESSOR-unknown-freebsd$FREEBSD_REL-gnueabihf fi ;; *:FreeBSD:*:*) UNAME_PROCESSOR=`/usr/bin/uname -p` case $UNAME_PROCESSOR in amd64) UNAME_PROCESSOR=x86_64 ;; i386) UNAME_PROCESSOR=i586 ;; esac FREEBSD_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'` GUESS=$UNAME_PROCESSOR-unknown-freebsd$FREEBSD_REL ;; i*:CYGWIN*:*) GUESS=$UNAME_MACHINE-pc-cygwin ;; *:MINGW64*:*) GUESS=$UNAME_MACHINE-pc-mingw64 ;; *:MINGW*:*) GUESS=$UNAME_MACHINE-pc-mingw32 ;; *:MSYS*:*) GUESS=$UNAME_MACHINE-pc-msys ;; i*:PW*:*) GUESS=$UNAME_MACHINE-pc-pw32 ;; *:SerenityOS:*:*) GUESS=$UNAME_MACHINE-pc-serenity ;; *:Interix*:*) case $UNAME_MACHINE in x86) GUESS=i586-pc-interix$UNAME_RELEASE ;; authenticamd | genuineintel | EM64T) GUESS=x86_64-unknown-interix$UNAME_RELEASE ;; IA64) GUESS=ia64-unknown-interix$UNAME_RELEASE ;; esac ;; i*:UWIN*:*) GUESS=$UNAME_MACHINE-pc-uwin ;; amd64:CYGWIN*:*:* | x86_64:CYGWIN*:*:*) GUESS=x86_64-pc-cygwin ;; prep*:SunOS:5.*:*) SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` GUESS=powerpcle-unknown-solaris2$SUN_REL ;; *:GNU:*:*) # the GNU system GNU_ARCH=`echo "$UNAME_MACHINE" | sed -e 's,[-/].*$,,'` GNU_REL=`echo "$UNAME_RELEASE" | sed -e 's,/.*$,,'` GUESS=$GNU_ARCH-unknown-$LIBC$GNU_REL ;; *:GNU/*:*:*) # other systems with GNU libc and userland GNU_SYS=`echo "$UNAME_SYSTEM" | sed 's,^[^/]*/,,' | tr "[:upper:]" "[:lower:]"` GNU_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'` GUESS=$UNAME_MACHINE-unknown-$GNU_SYS$GNU_REL-$LIBC ;; x86_64:[Mm]anagarm:*:*|i?86:[Mm]anagarm:*:*) GUESS="$UNAME_MACHINE-pc-managarm-mlibc" ;; *:[Mm]anagarm:*:*) GUESS="$UNAME_MACHINE-unknown-managarm-mlibc" ;; *:Minix:*:*) GUESS=$UNAME_MACHINE-unknown-minix ;; aarch64:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; aarch64_be:Linux:*:*) UNAME_MACHINE=aarch64_be GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; alpha:Linux:*:*) case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' /proc/cpuinfo 2>/dev/null` in EV5) UNAME_MACHINE=alphaev5 ;; EV56) UNAME_MACHINE=alphaev56 ;; PCA56) UNAME_MACHINE=alphapca56 ;; PCA57) UNAME_MACHINE=alphapca56 ;; EV6) UNAME_MACHINE=alphaev6 ;; EV67) UNAME_MACHINE=alphaev67 ;; EV68*) UNAME_MACHINE=alphaev68 ;; esac objdump --private-headers /bin/sh | grep -q ld.so.1 if test "$?" = 0 ; then LIBC=gnulibc1 ; fi GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; arc:Linux:*:* | arceb:Linux:*:* | arc32:Linux:*:* | arc64:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; arm*:Linux:*:*) set_cc_for_build if echo __ARM_EABI__ | $CC_FOR_BUILD -E - 2>/dev/null \ | grep -q __ARM_EABI__ then GUESS=$UNAME_MACHINE-unknown-linux-$LIBC else if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \ | grep -q __ARM_PCS_VFP then GUESS=$UNAME_MACHINE-unknown-linux-${LIBC}eabi else GUESS=$UNAME_MACHINE-unknown-linux-${LIBC}eabihf fi fi ;; avr32*:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; cris:Linux:*:*) GUESS=$UNAME_MACHINE-axis-linux-$LIBC ;; crisv32:Linux:*:*) GUESS=$UNAME_MACHINE-axis-linux-$LIBC ;; e2k:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; frv:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; hexagon:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; i*86:Linux:*:*) GUESS=$UNAME_MACHINE-pc-linux-$LIBC ;; ia64:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; k1om:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; loongarch32:Linux:*:* | loongarch64:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; m32r*:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; m68*:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; mips:Linux:*:* | mips64:Linux:*:*) set_cc_for_build IS_GLIBC=0 test x"${LIBC}" = xgnu && IS_GLIBC=1 sed 's/^ //' << EOF > "$dummy.c" #undef CPU #undef mips #undef mipsel #undef mips64 #undef mips64el #if ${IS_GLIBC} && defined(_ABI64) LIBCABI=gnuabi64 #else #if ${IS_GLIBC} && defined(_ABIN32) LIBCABI=gnuabin32 #else LIBCABI=${LIBC} #endif #endif #if ${IS_GLIBC} && defined(__mips64) && defined(__mips_isa_rev) && __mips_isa_rev>=6 CPU=mipsisa64r6 #else #if ${IS_GLIBC} && !defined(__mips64) && defined(__mips_isa_rev) && __mips_isa_rev>=6 CPU=mipsisa32r6 #else #if defined(__mips64) CPU=mips64 #else CPU=mips #endif #endif #endif #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) MIPS_ENDIAN=el #else #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) MIPS_ENDIAN= #else MIPS_ENDIAN= #endif #endif EOF cc_set_vars=`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^CPU\|^MIPS_ENDIAN\|^LIBCABI'` eval "$cc_set_vars" test "x$CPU" != x && { echo "$CPU${MIPS_ENDIAN}-unknown-linux-$LIBCABI"; exit; } ;; mips64el:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; openrisc*:Linux:*:*) GUESS=or1k-unknown-linux-$LIBC ;; or32:Linux:*:* | or1k*:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; padre:Linux:*:*) GUESS=sparc-unknown-linux-$LIBC ;; parisc64:Linux:*:* | hppa64:Linux:*:*) GUESS=hppa64-unknown-linux-$LIBC ;; parisc:Linux:*:* | hppa:Linux:*:*) # Look for CPU level case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in PA7*) GUESS=hppa1.1-unknown-linux-$LIBC ;; PA8*) GUESS=hppa2.0-unknown-linux-$LIBC ;; *) GUESS=hppa-unknown-linux-$LIBC ;; esac ;; ppc64:Linux:*:*) GUESS=powerpc64-unknown-linux-$LIBC ;; ppc:Linux:*:*) GUESS=powerpc-unknown-linux-$LIBC ;; ppc64le:Linux:*:*) GUESS=powerpc64le-unknown-linux-$LIBC ;; ppcle:Linux:*:*) GUESS=powerpcle-unknown-linux-$LIBC ;; riscv32:Linux:*:* | riscv32be:Linux:*:* | riscv64:Linux:*:* | riscv64be:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; s390:Linux:*:* | s390x:Linux:*:*) GUESS=$UNAME_MACHINE-ibm-linux-$LIBC ;; sh64*:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; sh*:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; sparc:Linux:*:* | sparc64:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; tile*:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; vax:Linux:*:*) GUESS=$UNAME_MACHINE-dec-linux-$LIBC ;; x86_64:Linux:*:*) set_cc_for_build CPU=$UNAME_MACHINE LIBCABI=$LIBC if test "$CC_FOR_BUILD" != no_compiler_found; then ABI=64 sed 's/^ //' << EOF > "$dummy.c" #ifdef __i386__ ABI=x86 #else #ifdef __ILP32__ ABI=x32 #endif #endif EOF cc_set_abi=`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^ABI' | sed 's, ,,g'` eval "$cc_set_abi" case $ABI in x86) CPU=i686 ;; x32) LIBCABI=${LIBC}x32 ;; esac fi GUESS=$CPU-pc-linux-$LIBCABI ;; xtensa*:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; i*86:DYNIX/ptx:4*:*) # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. # earlier versions are messed up and put the nodename in both # sysname and nodename. GUESS=i386-sequent-sysv4 ;; i*86:UNIX_SV:4.2MP:2.*) # Unixware is an offshoot of SVR4, but it has its own version # number series starting with 2... # I am not positive that other SVR4 systems won't match this, # I just have to hope. -- rms. # Use sysv4.2uw... so that sysv4* matches it. GUESS=$UNAME_MACHINE-pc-sysv4.2uw$UNAME_VERSION ;; i*86:OS/2:*:*) # If we were able to find `uname', then EMX Unix compatibility # is probably installed. GUESS=$UNAME_MACHINE-pc-os2-emx ;; i*86:XTS-300:*:STOP) GUESS=$UNAME_MACHINE-unknown-stop ;; i*86:atheos:*:*) GUESS=$UNAME_MACHINE-unknown-atheos ;; i*86:syllable:*:*) GUESS=$UNAME_MACHINE-pc-syllable ;; i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.[02]*:*) GUESS=i386-unknown-lynxos$UNAME_RELEASE ;; i*86:*DOS:*:*) GUESS=$UNAME_MACHINE-pc-msdosdjgpp ;; i*86:*:4.*:*) UNAME_REL=`echo "$UNAME_RELEASE" | sed 's/\/MP$//'` if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then GUESS=$UNAME_MACHINE-univel-sysv$UNAME_REL else GUESS=$UNAME_MACHINE-pc-sysv$UNAME_REL fi ;; i*86:*:5:[678]*) # UnixWare 7.x, OpenUNIX and OpenServer 6. case `/bin/uname -X | grep "^Machine"` in *486*) UNAME_MACHINE=i486 ;; *Pentium) UNAME_MACHINE=i586 ;; *Pent*|*Celeron) UNAME_MACHINE=i686 ;; esac GUESS=$UNAME_MACHINE-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION} ;; i*86:*:3.2:*) if test -f /usr/options/cb.name; then UNAME_REL=`sed -n 's/.*Version //p' /dev/null >/dev/null ; then UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')` (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486 (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \ && UNAME_MACHINE=i586 (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \ && UNAME_MACHINE=i686 (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \ && UNAME_MACHINE=i686 GUESS=$UNAME_MACHINE-pc-sco$UNAME_REL else GUESS=$UNAME_MACHINE-pc-sysv32 fi ;; pc:*:*:*) # Left here for compatibility: # uname -m prints for DJGPP always 'pc', but it prints nothing about # the processor, so we play safe by assuming i586. # Note: whatever this is, it MUST be the same as what config.sub # prints for the "djgpp" host, or else GDB configure will decide that # this is a cross-build. GUESS=i586-pc-msdosdjgpp ;; Intel:Mach:3*:*) GUESS=i386-pc-mach3 ;; paragon:*:*:*) GUESS=i860-intel-osf1 ;; i860:*:4.*:*) # i860-SVR4 if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then GUESS=i860-stardent-sysv$UNAME_RELEASE # Stardent Vistra i860-SVR4 else # Add other i860-SVR4 vendors below as they are discovered. GUESS=i860-unknown-sysv$UNAME_RELEASE # Unknown i860-SVR4 fi ;; mini*:CTIX:SYS*5:*) # "miniframe" GUESS=m68010-convergent-sysv ;; mc68k:UNIX:SYSTEM5:3.51m) GUESS=m68k-convergent-sysv ;; M680?0:D-NIX:5.3:*) GUESS=m68k-diab-dnix ;; M68*:*:R3V[5678]*:*) test -r /sysV68 && { echo 'm68k-motorola-sysv'; exit; } ;; 3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0) OS_REL='' test -r /etc/.relid \ && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ && { echo i486-ncr-sysv4.3"$OS_REL"; exit; } /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } ;; 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ && { echo i486-ncr-sysv4; exit; } ;; NCR*:*:4.2:* | MPRAS*:*:4.2:*) OS_REL='.3' test -r /etc/.relid \ && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ && { echo i486-ncr-sysv4.3"$OS_REL"; exit; } /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } /bin/uname -p 2>/dev/null | /bin/grep pteron >/dev/null \ && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } ;; m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*) GUESS=m68k-unknown-lynxos$UNAME_RELEASE ;; mc68030:UNIX_System_V:4.*:*) GUESS=m68k-atari-sysv4 ;; TSUNAMI:LynxOS:2.*:*) GUESS=sparc-unknown-lynxos$UNAME_RELEASE ;; rs6000:LynxOS:2.*:*) GUESS=rs6000-unknown-lynxos$UNAME_RELEASE ;; PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.[02]*:*) GUESS=powerpc-unknown-lynxos$UNAME_RELEASE ;; SM[BE]S:UNIX_SV:*:*) GUESS=mips-dde-sysv$UNAME_RELEASE ;; RM*:ReliantUNIX-*:*:*) GUESS=mips-sni-sysv4 ;; RM*:SINIX-*:*:*) GUESS=mips-sni-sysv4 ;; *:SINIX-*:*:*) if uname -p 2>/dev/null >/dev/null ; then UNAME_MACHINE=`(uname -p) 2>/dev/null` GUESS=$UNAME_MACHINE-sni-sysv4 else GUESS=ns32k-sni-sysv fi ;; PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort # says GUESS=i586-unisys-sysv4 ;; *:UNIX_System_V:4*:FTX*) # From Gerald Hewes . # How about differentiating between stratus architectures? -djm GUESS=hppa1.1-stratus-sysv4 ;; *:*:*:FTX*) # From seanf@swdc.stratus.com. GUESS=i860-stratus-sysv4 ;; i*86:VOS:*:*) # From Paul.Green@stratus.com. GUESS=$UNAME_MACHINE-stratus-vos ;; *:VOS:*:*) # From Paul.Green@stratus.com. GUESS=hppa1.1-stratus-vos ;; mc68*:A/UX:*:*) GUESS=m68k-apple-aux$UNAME_RELEASE ;; news*:NEWS-OS:6*:*) GUESS=mips-sony-newsos6 ;; R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) if test -d /usr/nec; then GUESS=mips-nec-sysv$UNAME_RELEASE else GUESS=mips-unknown-sysv$UNAME_RELEASE fi ;; BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. GUESS=powerpc-be-beos ;; BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. GUESS=powerpc-apple-beos ;; BePC:BeOS:*:*) # BeOS running on Intel PC compatible. GUESS=i586-pc-beos ;; BePC:Haiku:*:*) # Haiku running on Intel PC compatible. GUESS=i586-pc-haiku ;; ppc:Haiku:*:*) # Haiku running on Apple PowerPC GUESS=powerpc-apple-haiku ;; *:Haiku:*:*) # Haiku modern gcc (not bound by BeOS compat) GUESS=$UNAME_MACHINE-unknown-haiku ;; SX-4:SUPER-UX:*:*) GUESS=sx4-nec-superux$UNAME_RELEASE ;; SX-5:SUPER-UX:*:*) GUESS=sx5-nec-superux$UNAME_RELEASE ;; SX-6:SUPER-UX:*:*) GUESS=sx6-nec-superux$UNAME_RELEASE ;; SX-7:SUPER-UX:*:*) GUESS=sx7-nec-superux$UNAME_RELEASE ;; SX-8:SUPER-UX:*:*) GUESS=sx8-nec-superux$UNAME_RELEASE ;; SX-8R:SUPER-UX:*:*) GUESS=sx8r-nec-superux$UNAME_RELEASE ;; SX-ACE:SUPER-UX:*:*) GUESS=sxace-nec-superux$UNAME_RELEASE ;; Power*:Rhapsody:*:*) GUESS=powerpc-apple-rhapsody$UNAME_RELEASE ;; *:Rhapsody:*:*) GUESS=$UNAME_MACHINE-apple-rhapsody$UNAME_RELEASE ;; arm64:Darwin:*:*) GUESS=aarch64-apple-darwin$UNAME_RELEASE ;; *:Darwin:*:*) UNAME_PROCESSOR=`uname -p` case $UNAME_PROCESSOR in unknown) UNAME_PROCESSOR=powerpc ;; esac if command -v xcode-select > /dev/null 2> /dev/null && \ ! xcode-select --print-path > /dev/null 2> /dev/null ; then # Avoid executing cc if there is no toolchain installed as # cc will be a stub that puts up a graphical alert # prompting the user to install developer tools. CC_FOR_BUILD=no_compiler_found else set_cc_for_build fi if test "$CC_FOR_BUILD" != no_compiler_found; then if (echo '#ifdef __LP64__'; echo IS_64BIT_ARCH; echo '#endif') | \ (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \ grep IS_64BIT_ARCH >/dev/null then case $UNAME_PROCESSOR in i386) UNAME_PROCESSOR=x86_64 ;; powerpc) UNAME_PROCESSOR=powerpc64 ;; esac fi # On 10.4-10.6 one might compile for PowerPC via gcc -arch ppc if (echo '#ifdef __POWERPC__'; echo IS_PPC; echo '#endif') | \ (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \ grep IS_PPC >/dev/null then UNAME_PROCESSOR=powerpc fi elif test "$UNAME_PROCESSOR" = i386 ; then # uname -m returns i386 or x86_64 UNAME_PROCESSOR=$UNAME_MACHINE fi GUESS=$UNAME_PROCESSOR-apple-darwin$UNAME_RELEASE ;; *:procnto*:*:* | *:QNX:[0123456789]*:*) UNAME_PROCESSOR=`uname -p` if test "$UNAME_PROCESSOR" = x86; then UNAME_PROCESSOR=i386 UNAME_MACHINE=pc fi GUESS=$UNAME_PROCESSOR-$UNAME_MACHINE-nto-qnx$UNAME_RELEASE ;; *:QNX:*:4*) GUESS=i386-pc-qnx ;; NEO-*:NONSTOP_KERNEL:*:*) GUESS=neo-tandem-nsk$UNAME_RELEASE ;; NSE-*:NONSTOP_KERNEL:*:*) GUESS=nse-tandem-nsk$UNAME_RELEASE ;; NSR-*:NONSTOP_KERNEL:*:*) GUESS=nsr-tandem-nsk$UNAME_RELEASE ;; NSV-*:NONSTOP_KERNEL:*:*) GUESS=nsv-tandem-nsk$UNAME_RELEASE ;; NSX-*:NONSTOP_KERNEL:*:*) GUESS=nsx-tandem-nsk$UNAME_RELEASE ;; *:NonStop-UX:*:*) GUESS=mips-compaq-nonstopux ;; BS2000:POSIX*:*:*) GUESS=bs2000-siemens-sysv ;; DS/*:UNIX_System_V:*:*) GUESS=$UNAME_MACHINE-$UNAME_SYSTEM-$UNAME_RELEASE ;; *:Plan9:*:*) # "uname -m" is not consistent, so use $cputype instead. 386 # is converted to i386 for consistency with other x86 # operating systems. if test "${cputype-}" = 386; then UNAME_MACHINE=i386 elif test "x${cputype-}" != x; then UNAME_MACHINE=$cputype fi GUESS=$UNAME_MACHINE-unknown-plan9 ;; *:TOPS-10:*:*) GUESS=pdp10-unknown-tops10 ;; *:TENEX:*:*) GUESS=pdp10-unknown-tenex ;; KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) GUESS=pdp10-dec-tops20 ;; XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) GUESS=pdp10-xkl-tops20 ;; *:TOPS-20:*:*) GUESS=pdp10-unknown-tops20 ;; *:ITS:*:*) GUESS=pdp10-unknown-its ;; SEI:*:*:SEIUX) GUESS=mips-sei-seiux$UNAME_RELEASE ;; *:DragonFly:*:*) DRAGONFLY_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'` GUESS=$UNAME_MACHINE-unknown-dragonfly$DRAGONFLY_REL ;; *:*VMS:*:*) UNAME_MACHINE=`(uname -p) 2>/dev/null` case $UNAME_MACHINE in A*) GUESS=alpha-dec-vms ;; I*) GUESS=ia64-dec-vms ;; V*) GUESS=vax-dec-vms ;; esac ;; *:XENIX:*:SysV) GUESS=i386-pc-xenix ;; i*86:skyos:*:*) SKYOS_REL=`echo "$UNAME_RELEASE" | sed -e 's/ .*$//'` GUESS=$UNAME_MACHINE-pc-skyos$SKYOS_REL ;; i*86:rdos:*:*) GUESS=$UNAME_MACHINE-pc-rdos ;; i*86:Fiwix:*:*) GUESS=$UNAME_MACHINE-pc-fiwix ;; *:AROS:*:*) GUESS=$UNAME_MACHINE-unknown-aros ;; x86_64:VMkernel:*:*) GUESS=$UNAME_MACHINE-unknown-esx ;; amd64:Isilon\ OneFS:*:*) GUESS=x86_64-unknown-onefs ;; *:Unleashed:*:*) GUESS=$UNAME_MACHINE-unknown-unleashed$UNAME_RELEASE ;; esac # Do we have a guess based on uname results? if test "x$GUESS" != x; then echo "$GUESS" exit fi # No uname command or uname output not recognized. set_cc_for_build cat > "$dummy.c" < #include #endif #if defined(ultrix) || defined(_ultrix) || defined(__ultrix) || defined(__ultrix__) #if defined (vax) || defined (__vax) || defined (__vax__) || defined(mips) || defined(__mips) || defined(__mips__) || defined(MIPS) || defined(__MIPS__) #include #if defined(_SIZE_T_) || defined(SIGLOST) #include #endif #endif #endif main () { #if defined (sony) #if defined (MIPSEB) /* BFD wants "bsd" instead of "newsos". Perhaps BFD should be changed, I don't know.... */ printf ("mips-sony-bsd\n"); exit (0); #else #include printf ("m68k-sony-newsos%s\n", #ifdef NEWSOS4 "4" #else "" #endif ); exit (0); #endif #endif #if defined (NeXT) #if !defined (__ARCHITECTURE__) #define __ARCHITECTURE__ "m68k" #endif int version; version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`; if (version < 4) printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version); else printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version); exit (0); #endif #if defined (MULTIMAX) || defined (n16) #if defined (UMAXV) printf ("ns32k-encore-sysv\n"); exit (0); #else #if defined (CMU) printf ("ns32k-encore-mach\n"); exit (0); #else printf ("ns32k-encore-bsd\n"); exit (0); #endif #endif #endif #if defined (__386BSD__) printf ("i386-pc-bsd\n"); exit (0); #endif #if defined (sequent) #if defined (i386) printf ("i386-sequent-dynix\n"); exit (0); #endif #if defined (ns32000) printf ("ns32k-sequent-dynix\n"); exit (0); #endif #endif #if defined (_SEQUENT_) struct utsname un; uname(&un); if (strncmp(un.version, "V2", 2) == 0) { printf ("i386-sequent-ptx2\n"); exit (0); } if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */ printf ("i386-sequent-ptx1\n"); exit (0); } printf ("i386-sequent-ptx\n"); exit (0); #endif #if defined (vax) #if !defined (ultrix) #include #if defined (BSD) #if BSD == 43 printf ("vax-dec-bsd4.3\n"); exit (0); #else #if BSD == 199006 printf ("vax-dec-bsd4.3reno\n"); exit (0); #else printf ("vax-dec-bsd\n"); exit (0); #endif #endif #else printf ("vax-dec-bsd\n"); exit (0); #endif #else #if defined(_SIZE_T_) || defined(SIGLOST) struct utsname un; uname (&un); printf ("vax-dec-ultrix%s\n", un.release); exit (0); #else printf ("vax-dec-ultrix\n"); exit (0); #endif #endif #endif #if defined(ultrix) || defined(_ultrix) || defined(__ultrix) || defined(__ultrix__) #if defined(mips) || defined(__mips) || defined(__mips__) || defined(MIPS) || defined(__MIPS__) #if defined(_SIZE_T_) || defined(SIGLOST) struct utsname *un; uname (&un); printf ("mips-dec-ultrix%s\n", un.release); exit (0); #else printf ("mips-dec-ultrix\n"); exit (0); #endif #endif #endif #if defined (alliant) && defined (i860) printf ("i860-alliant-bsd\n"); exit (0); #endif exit (1); } EOF $CC_FOR_BUILD -o "$dummy" "$dummy.c" 2>/dev/null && SYSTEM_NAME=`"$dummy"` && { echo "$SYSTEM_NAME"; exit; } # Apollos put the system type in the environment. test -d /usr/apollo && { echo "$ISP-apollo-$SYSTYPE"; exit; } echo "$0: unable to guess system type" >&2 case $UNAME_MACHINE:$UNAME_SYSTEM in mips:Linux | mips64:Linux) # If we got here on MIPS GNU/Linux, output extra information. cat >&2 <&2 <&2 </dev/null || echo unknown` uname -r = `(uname -r) 2>/dev/null || echo unknown` uname -s = `(uname -s) 2>/dev/null || echo unknown` uname -v = `(uname -v) 2>/dev/null || echo unknown` /usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null` /bin/uname -X = `(/bin/uname -X) 2>/dev/null` hostinfo = `(hostinfo) 2>/dev/null` /bin/universe = `(/bin/universe) 2>/dev/null` /usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null` /bin/arch = `(/bin/arch) 2>/dev/null` /usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null` /usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null` UNAME_MACHINE = "$UNAME_MACHINE" UNAME_RELEASE = "$UNAME_RELEASE" UNAME_SYSTEM = "$UNAME_SYSTEM" UNAME_VERSION = "$UNAME_VERSION" EOF fi exit 1 # Local variables: # eval: (add-hook 'before-save-hook 'time-stamp) # time-stamp-start: "timestamp='" # time-stamp-format: "%:y-%02m-%02d" # time-stamp-end: "'" # End: proftpd-mod_proxy-0.9.5/config.sub000077500000000000000000001057521475737016700172370ustar00rootroot00000000000000#! /bin/sh # Configuration validation subroutine script. # Copyright 1992-2023 Free Software Foundation, Inc. # shellcheck disable=SC2006,SC2268 # see below for rationale timestamp='2023-01-21' # This file is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, see . # # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that # program. This Exception is an additional permission under section 7 # of the GNU General Public License, version 3 ("GPLv3"). # Please send patches to . # # Configuration subroutine to validate and canonicalize a configuration type. # Supply the specified configuration type as an argument. # If it is invalid, we print an error message on stderr and exit with code 1. # Otherwise, we print the canonical config type on stdout and succeed. # You can get the latest version of this script from: # https://git.savannah.gnu.org/cgit/config.git/plain/config.sub # This file is supposed to be the same for all GNU packages # and recognize all the CPU types, system types and aliases # that are meaningful with *any* GNU software. # Each package is responsible for reporting which valid configurations # it does not support. The user should be able to distinguish # a failure to support a valid configuration from a meaningless # configuration. # The goal of this file is to map all the various variations of a given # machine specification into a single specification in the form: # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM # or in some cases, the newer four-part form: # CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM # It is wrong to echo any other type of specification. # The "shellcheck disable" line above the timestamp inhibits complaints # about features and limitations of the classic Bourne shell that were # superseded or lifted in POSIX. However, this script identifies a wide # variety of pre-POSIX systems that do not have POSIX shells at all, and # even some reasonably current systems (Solaris 10 as case-in-point) still # have a pre-POSIX /bin/sh. me=`echo "$0" | sed -e 's,.*/,,'` usage="\ Usage: $0 [OPTION] CPU-MFR-OPSYS or ALIAS Canonicalize a configuration name. Options: -h, --help print this help, then exit -t, --time-stamp print date of last modification, then exit -v, --version print version number, then exit Report bugs and patches to ." version="\ GNU config.sub ($timestamp) Copyright 1992-2023 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." help=" Try \`$me --help' for more information." # Parse command line while test $# -gt 0 ; do case $1 in --time-stamp | --time* | -t ) echo "$timestamp" ; exit ;; --version | -v ) echo "$version" ; exit ;; --help | --h* | -h ) echo "$usage"; exit ;; -- ) # Stop option processing shift; break ;; - ) # Use stdin as input. break ;; -* ) echo "$me: invalid option $1$help" >&2 exit 1 ;; *local*) # First pass through any local machine types. echo "$1" exit ;; * ) break ;; esac done case $# in 0) echo "$me: missing argument$help" >&2 exit 1;; 1) ;; *) echo "$me: too many arguments$help" >&2 exit 1;; esac # Split fields of configuration type # shellcheck disable=SC2162 saved_IFS=$IFS IFS="-" read field1 field2 field3 field4 <&2 exit 1 ;; *-*-*-*) basic_machine=$field1-$field2 basic_os=$field3-$field4 ;; *-*-*) # Ambiguous whether COMPANY is present, or skipped and KERNEL-OS is two # parts maybe_os=$field2-$field3 case $maybe_os in nto-qnx* | linux-* | uclinux-uclibc* \ | uclinux-gnu* | kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* \ | netbsd*-eabi* | kopensolaris*-gnu* | cloudabi*-eabi* \ | storm-chaos* | os2-emx* | rtmk-nova* | managarm-*) basic_machine=$field1 basic_os=$maybe_os ;; android-linux) basic_machine=$field1-unknown basic_os=linux-android ;; *) basic_machine=$field1-$field2 basic_os=$field3 ;; esac ;; *-*) # A lone config we happen to match not fitting any pattern case $field1-$field2 in decstation-3100) basic_machine=mips-dec basic_os= ;; *-*) # Second component is usually, but not always the OS case $field2 in # Prevent following clause from handling this valid os sun*os*) basic_machine=$field1 basic_os=$field2 ;; zephyr*) basic_machine=$field1-unknown basic_os=$field2 ;; # Manufacturers dec* | mips* | sequent* | encore* | pc533* | sgi* | sony* \ | att* | 7300* | 3300* | delta* | motorola* | sun[234]* \ | unicom* | ibm* | next | hp | isi* | apollo | altos* \ | convergent* | ncr* | news | 32* | 3600* | 3100* \ | hitachi* | c[123]* | convex* | sun | crds | omron* | dg \ | ultra | tti* | harris | dolphin | highlevel | gould \ | cbm | ns | masscomp | apple | axis | knuth | cray \ | microblaze* | sim | cisco \ | oki | wec | wrs | winbond) basic_machine=$field1-$field2 basic_os= ;; *) basic_machine=$field1 basic_os=$field2 ;; esac ;; esac ;; *) # Convert single-component short-hands not valid as part of # multi-component configurations. case $field1 in 386bsd) basic_machine=i386-pc basic_os=bsd ;; a29khif) basic_machine=a29k-amd basic_os=udi ;; adobe68k) basic_machine=m68010-adobe basic_os=scout ;; alliant) basic_machine=fx80-alliant basic_os= ;; altos | altos3068) basic_machine=m68k-altos basic_os= ;; am29k) basic_machine=a29k-none basic_os=bsd ;; amdahl) basic_machine=580-amdahl basic_os=sysv ;; amiga) basic_machine=m68k-unknown basic_os= ;; amigaos | amigados) basic_machine=m68k-unknown basic_os=amigaos ;; amigaunix | amix) basic_machine=m68k-unknown basic_os=sysv4 ;; apollo68) basic_machine=m68k-apollo basic_os=sysv ;; apollo68bsd) basic_machine=m68k-apollo basic_os=bsd ;; aros) basic_machine=i386-pc basic_os=aros ;; aux) basic_machine=m68k-apple basic_os=aux ;; balance) basic_machine=ns32k-sequent basic_os=dynix ;; blackfin) basic_machine=bfin-unknown basic_os=linux ;; cegcc) basic_machine=arm-unknown basic_os=cegcc ;; convex-c1) basic_machine=c1-convex basic_os=bsd ;; convex-c2) basic_machine=c2-convex basic_os=bsd ;; convex-c32) basic_machine=c32-convex basic_os=bsd ;; convex-c34) basic_machine=c34-convex basic_os=bsd ;; convex-c38) basic_machine=c38-convex basic_os=bsd ;; cray) basic_machine=j90-cray basic_os=unicos ;; crds | unos) basic_machine=m68k-crds basic_os= ;; da30) basic_machine=m68k-da30 basic_os= ;; decstation | pmax | pmin | dec3100 | decstatn) basic_machine=mips-dec basic_os= ;; delta88) basic_machine=m88k-motorola basic_os=sysv3 ;; dicos) basic_machine=i686-pc basic_os=dicos ;; djgpp) basic_machine=i586-pc basic_os=msdosdjgpp ;; ebmon29k) basic_machine=a29k-amd basic_os=ebmon ;; es1800 | OSE68k | ose68k | ose | OSE) basic_machine=m68k-ericsson basic_os=ose ;; gmicro) basic_machine=tron-gmicro basic_os=sysv ;; go32) basic_machine=i386-pc basic_os=go32 ;; h8300hms) basic_machine=h8300-hitachi basic_os=hms ;; h8300xray) basic_machine=h8300-hitachi basic_os=xray ;; h8500hms) basic_machine=h8500-hitachi basic_os=hms ;; harris) basic_machine=m88k-harris basic_os=sysv3 ;; hp300 | hp300hpux) basic_machine=m68k-hp basic_os=hpux ;; hp300bsd) basic_machine=m68k-hp basic_os=bsd ;; hppaosf) basic_machine=hppa1.1-hp basic_os=osf ;; hppro) basic_machine=hppa1.1-hp basic_os=proelf ;; i386mach) basic_machine=i386-mach basic_os=mach ;; isi68 | isi) basic_machine=m68k-isi basic_os=sysv ;; m68knommu) basic_machine=m68k-unknown basic_os=linux ;; magnum | m3230) basic_machine=mips-mips basic_os=sysv ;; merlin) basic_machine=ns32k-utek basic_os=sysv ;; mingw64) basic_machine=x86_64-pc basic_os=mingw64 ;; mingw32) basic_machine=i686-pc basic_os=mingw32 ;; mingw32ce) basic_machine=arm-unknown basic_os=mingw32ce ;; monitor) basic_machine=m68k-rom68k basic_os=coff ;; morphos) basic_machine=powerpc-unknown basic_os=morphos ;; moxiebox) basic_machine=moxie-unknown basic_os=moxiebox ;; msdos) basic_machine=i386-pc basic_os=msdos ;; msys) basic_machine=i686-pc basic_os=msys ;; mvs) basic_machine=i370-ibm basic_os=mvs ;; nacl) basic_machine=le32-unknown basic_os=nacl ;; ncr3000) basic_machine=i486-ncr basic_os=sysv4 ;; netbsd386) basic_machine=i386-pc basic_os=netbsd ;; netwinder) basic_machine=armv4l-rebel basic_os=linux ;; news | news700 | news800 | news900) basic_machine=m68k-sony basic_os=newsos ;; news1000) basic_machine=m68030-sony basic_os=newsos ;; necv70) basic_machine=v70-nec basic_os=sysv ;; nh3000) basic_machine=m68k-harris basic_os=cxux ;; nh[45]000) basic_machine=m88k-harris basic_os=cxux ;; nindy960) basic_machine=i960-intel basic_os=nindy ;; mon960) basic_machine=i960-intel basic_os=mon960 ;; nonstopux) basic_machine=mips-compaq basic_os=nonstopux ;; os400) basic_machine=powerpc-ibm basic_os=os400 ;; OSE68000 | ose68000) basic_machine=m68000-ericsson basic_os=ose ;; os68k) basic_machine=m68k-none basic_os=os68k ;; paragon) basic_machine=i860-intel basic_os=osf ;; parisc) basic_machine=hppa-unknown basic_os=linux ;; psp) basic_machine=mipsallegrexel-sony basic_os=psp ;; pw32) basic_machine=i586-unknown basic_os=pw32 ;; rdos | rdos64) basic_machine=x86_64-pc basic_os=rdos ;; rdos32) basic_machine=i386-pc basic_os=rdos ;; rom68k) basic_machine=m68k-rom68k basic_os=coff ;; sa29200) basic_machine=a29k-amd basic_os=udi ;; sei) basic_machine=mips-sei basic_os=seiux ;; sequent) basic_machine=i386-sequent basic_os= ;; sps7) basic_machine=m68k-bull basic_os=sysv2 ;; st2000) basic_machine=m68k-tandem basic_os= ;; stratus) basic_machine=i860-stratus basic_os=sysv4 ;; sun2) basic_machine=m68000-sun basic_os= ;; sun2os3) basic_machine=m68000-sun basic_os=sunos3 ;; sun2os4) basic_machine=m68000-sun basic_os=sunos4 ;; sun3) basic_machine=m68k-sun basic_os= ;; sun3os3) basic_machine=m68k-sun basic_os=sunos3 ;; sun3os4) basic_machine=m68k-sun basic_os=sunos4 ;; sun4) basic_machine=sparc-sun basic_os= ;; sun4os3) basic_machine=sparc-sun basic_os=sunos3 ;; sun4os4) basic_machine=sparc-sun basic_os=sunos4 ;; sun4sol2) basic_machine=sparc-sun basic_os=solaris2 ;; sun386 | sun386i | roadrunner) basic_machine=i386-sun basic_os= ;; sv1) basic_machine=sv1-cray basic_os=unicos ;; symmetry) basic_machine=i386-sequent basic_os=dynix ;; t3e) basic_machine=alphaev5-cray basic_os=unicos ;; t90) basic_machine=t90-cray basic_os=unicos ;; toad1) basic_machine=pdp10-xkl basic_os=tops20 ;; tpf) basic_machine=s390x-ibm basic_os=tpf ;; udi29k) basic_machine=a29k-amd basic_os=udi ;; ultra3) basic_machine=a29k-nyu basic_os=sym1 ;; v810 | necv810) basic_machine=v810-nec basic_os=none ;; vaxv) basic_machine=vax-dec basic_os=sysv ;; vms) basic_machine=vax-dec basic_os=vms ;; vsta) basic_machine=i386-pc basic_os=vsta ;; vxworks960) basic_machine=i960-wrs basic_os=vxworks ;; vxworks68) basic_machine=m68k-wrs basic_os=vxworks ;; vxworks29k) basic_machine=a29k-wrs basic_os=vxworks ;; xbox) basic_machine=i686-pc basic_os=mingw32 ;; ymp) basic_machine=ymp-cray basic_os=unicos ;; *) basic_machine=$1 basic_os= ;; esac ;; esac # Decode 1-component or ad-hoc basic machines case $basic_machine in # Here we handle the default manufacturer of certain CPU types. It is in # some cases the only manufacturer, in others, it is the most popular. w89k) cpu=hppa1.1 vendor=winbond ;; op50n) cpu=hppa1.1 vendor=oki ;; op60c) cpu=hppa1.1 vendor=oki ;; ibm*) cpu=i370 vendor=ibm ;; orion105) cpu=clipper vendor=highlevel ;; mac | mpw | mac-mpw) cpu=m68k vendor=apple ;; pmac | pmac-mpw) cpu=powerpc vendor=apple ;; # Recognize the various machine names and aliases which stand # for a CPU type and a company and sometimes even an OS. 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc) cpu=m68000 vendor=att ;; 3b*) cpu=we32k vendor=att ;; bluegene*) cpu=powerpc vendor=ibm basic_os=cnk ;; decsystem10* | dec10*) cpu=pdp10 vendor=dec basic_os=tops10 ;; decsystem20* | dec20*) cpu=pdp10 vendor=dec basic_os=tops20 ;; delta | 3300 | motorola-3300 | motorola-delta \ | 3300-motorola | delta-motorola) cpu=m68k vendor=motorola ;; dpx2*) cpu=m68k vendor=bull basic_os=sysv3 ;; encore | umax | mmax) cpu=ns32k vendor=encore ;; elxsi) cpu=elxsi vendor=elxsi basic_os=${basic_os:-bsd} ;; fx2800) cpu=i860 vendor=alliant ;; genix) cpu=ns32k vendor=ns ;; h3050r* | hiux*) cpu=hppa1.1 vendor=hitachi basic_os=hiuxwe2 ;; hp3k9[0-9][0-9] | hp9[0-9][0-9]) cpu=hppa1.0 vendor=hp ;; hp9k2[0-9][0-9] | hp9k31[0-9]) cpu=m68000 vendor=hp ;; hp9k3[2-9][0-9]) cpu=m68k vendor=hp ;; hp9k6[0-9][0-9] | hp6[0-9][0-9]) cpu=hppa1.0 vendor=hp ;; hp9k7[0-79][0-9] | hp7[0-79][0-9]) cpu=hppa1.1 vendor=hp ;; hp9k78[0-9] | hp78[0-9]) # FIXME: really hppa2.0-hp cpu=hppa1.1 vendor=hp ;; hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893) # FIXME: really hppa2.0-hp cpu=hppa1.1 vendor=hp ;; hp9k8[0-9][13679] | hp8[0-9][13679]) cpu=hppa1.1 vendor=hp ;; hp9k8[0-9][0-9] | hp8[0-9][0-9]) cpu=hppa1.0 vendor=hp ;; i*86v32) cpu=`echo "$1" | sed -e 's/86.*/86/'` vendor=pc basic_os=sysv32 ;; i*86v4*) cpu=`echo "$1" | sed -e 's/86.*/86/'` vendor=pc basic_os=sysv4 ;; i*86v) cpu=`echo "$1" | sed -e 's/86.*/86/'` vendor=pc basic_os=sysv ;; i*86sol2) cpu=`echo "$1" | sed -e 's/86.*/86/'` vendor=pc basic_os=solaris2 ;; j90 | j90-cray) cpu=j90 vendor=cray basic_os=${basic_os:-unicos} ;; iris | iris4d) cpu=mips vendor=sgi case $basic_os in irix*) ;; *) basic_os=irix4 ;; esac ;; miniframe) cpu=m68000 vendor=convergent ;; *mint | mint[0-9]* | *MiNT | *MiNT[0-9]*) cpu=m68k vendor=atari basic_os=mint ;; news-3600 | risc-news) cpu=mips vendor=sony basic_os=newsos ;; next | m*-next) cpu=m68k vendor=next case $basic_os in openstep*) ;; nextstep*) ;; ns2*) basic_os=nextstep2 ;; *) basic_os=nextstep3 ;; esac ;; np1) cpu=np1 vendor=gould ;; op50n-* | op60c-*) cpu=hppa1.1 vendor=oki basic_os=proelf ;; pa-hitachi) cpu=hppa1.1 vendor=hitachi basic_os=hiuxwe2 ;; pbd) cpu=sparc vendor=tti ;; pbb) cpu=m68k vendor=tti ;; pc532) cpu=ns32k vendor=pc532 ;; pn) cpu=pn vendor=gould ;; power) cpu=power vendor=ibm ;; ps2) cpu=i386 vendor=ibm ;; rm[46]00) cpu=mips vendor=siemens ;; rtpc | rtpc-*) cpu=romp vendor=ibm ;; sde) cpu=mipsisa32 vendor=sde basic_os=${basic_os:-elf} ;; simso-wrs) cpu=sparclite vendor=wrs basic_os=vxworks ;; tower | tower-32) cpu=m68k vendor=ncr ;; vpp*|vx|vx-*) cpu=f301 vendor=fujitsu ;; w65) cpu=w65 vendor=wdc ;; w89k-*) cpu=hppa1.1 vendor=winbond basic_os=proelf ;; none) cpu=none vendor=none ;; leon|leon[3-9]) cpu=sparc vendor=$basic_machine ;; leon-*|leon[3-9]-*) cpu=sparc vendor=`echo "$basic_machine" | sed 's/-.*//'` ;; *-*) # shellcheck disable=SC2162 saved_IFS=$IFS IFS="-" read cpu vendor <&2 exit 1 ;; esac ;; esac # Here we canonicalize certain aliases for manufacturers. case $vendor in digital*) vendor=dec ;; commodore*) vendor=cbm ;; *) ;; esac # Decode manufacturer-specific aliases for certain operating systems. if test x$basic_os != x then # First recognize some ad-hoc cases, or perhaps split kernel-os, or else just # set os. case $basic_os in gnu/linux*) kernel=linux os=`echo "$basic_os" | sed -e 's|gnu/linux|gnu|'` ;; os2-emx) kernel=os2 os=`echo "$basic_os" | sed -e 's|os2-emx|emx|'` ;; nto-qnx*) kernel=nto os=`echo "$basic_os" | sed -e 's|nto-qnx|qnx|'` ;; *-*) # shellcheck disable=SC2162 saved_IFS=$IFS IFS="-" read kernel os <&2 exit 1 ;; esac # As a final step for OS-related things, validate the OS-kernel combination # (given a valid OS), if there is a kernel. case $kernel-$os in linux-gnu* | linux-dietlibc* | linux-android* | linux-newlib* \ | linux-musl* | linux-relibc* | linux-uclibc* | linux-mlibc* ) ;; uclinux-uclibc* ) ;; managarm-mlibc* | managarm-kernel* ) ;; -dietlibc* | -newlib* | -musl* | -relibc* | -uclibc* | -mlibc* ) # These are just libc implementations, not actual OSes, and thus # require a kernel. echo "Invalid configuration \`$1': libc \`$os' needs explicit kernel." 1>&2 exit 1 ;; -kernel* ) echo "Invalid configuration \`$1': \`$os' needs explicit kernel." 1>&2 exit 1 ;; *-kernel* ) echo "Invalid configuration \`$1': \`$kernel' does not support \`$os'." 1>&2 exit 1 ;; kfreebsd*-gnu* | kopensolaris*-gnu*) ;; vxworks-simlinux | vxworks-simwindows | vxworks-spe) ;; nto-qnx*) ;; os2-emx) ;; *-eabi* | *-gnueabi*) ;; -*) # Blank kernel with real OS is always fine. ;; *-*) echo "Invalid configuration \`$1': Kernel \`$kernel' not known to work with OS \`$os'." 1>&2 exit 1 ;; esac # Here we handle the case where we know the os, and the CPU type, but not the # manufacturer. We pick the logical manufacturer. case $vendor in unknown) case $cpu-$os in *-riscix*) vendor=acorn ;; *-sunos*) vendor=sun ;; *-cnk* | *-aix*) vendor=ibm ;; *-beos*) vendor=be ;; *-hpux*) vendor=hp ;; *-mpeix*) vendor=hp ;; *-hiux*) vendor=hitachi ;; *-unos*) vendor=crds ;; *-dgux*) vendor=dg ;; *-luna*) vendor=omron ;; *-genix*) vendor=ns ;; *-clix*) vendor=intergraph ;; *-mvs* | *-opened*) vendor=ibm ;; *-os400*) vendor=ibm ;; s390-* | s390x-*) vendor=ibm ;; *-ptx*) vendor=sequent ;; *-tpf*) vendor=ibm ;; *-vxsim* | *-vxworks* | *-windiss*) vendor=wrs ;; *-aux*) vendor=apple ;; *-hms*) vendor=hitachi ;; *-mpw* | *-macos*) vendor=apple ;; *-*mint | *-mint[0-9]* | *-*MiNT | *-MiNT[0-9]*) vendor=atari ;; *-vos*) vendor=stratus ;; esac ;; esac echo "$cpu-$vendor-${kernel:+$kernel-}$os" exit # Local variables: # eval: (add-hook 'before-save-hook 'time-stamp) # time-stamp-start: "timestamp='" # time-stamp-format: "%:y-%02m-%02d" # time-stamp-end: "'" # End: proftpd-mod_proxy-0.9.5/configure000077500000000000000000004762071475737016700171710ustar00rootroot00000000000000#! /bin/sh # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.69. # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. # # # This configure script is free software; the Free Software Foundation # gives unlimited permission to copy, distribute and modify it. ## -------------------- ## ## M4sh Initialization. ## ## -------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; esac fi as_nl=' ' export as_nl # Printing a long string crashes Solaris 7 /usr/bin/printf. as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo # Prefer a ksh shell builtin over an external printf program on Solaris, # but without wasting forks for bash or zsh. if test -z "$BASH_VERSION$ZSH_VERSION" \ && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then as_echo='print -r --' as_echo_n='print -rn --' elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then as_echo='printf %s\n' as_echo_n='printf %s' else if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' as_echo_n='/usr/ucb/echo -n' else as_echo_body='eval expr "X$1" : "X\\(.*\\)"' as_echo_n_body='eval arg=$1; case $arg in #( *"$as_nl"*) expr "X$arg" : "X\\(.*\\)$as_nl"; arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; esac; expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" ' export as_echo_n_body as_echo_n='sh -c $as_echo_n_body as_echo' fi export as_echo_body as_echo='sh -c $as_echo_body as_echo' fi # The user is always right. if test "${PATH_SEPARATOR+set}" != set; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || PATH_SEPARATOR=';' } fi # IFS # We need space, tab and new line, in precisely that order. Quoting is # there to prevent editors from complaining about space-tab. # (If _AS_PATH_WALK were called with IFS unset, it would disable word # splitting by setting IFS to empty value.) IFS=" "" $as_nl" # Find who we are. Look in the path if we contain no directory separator. as_myself= case $0 in #(( *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break done IFS=$as_save_IFS ;; esac # We did not find ourselves, most probably we were run as `sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi # Unset variables that we do not need and which cause bugs (e.g. in # pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" # suppresses any "Segmentation fault" message there. '((' could # trigger a bug in pdksh 5.2.14. for as_var in BASH_ENV ENV MAIL MAILPATH do eval test x\${$as_var+set} = xset \ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : done PS1='$ ' PS2='> ' PS4='+ ' # NLS nuisances. LC_ALL=C export LC_ALL LANGUAGE=C export LANGUAGE # CDPATH. (unset CDPATH) >/dev/null 2>&1 && unset CDPATH # Use a proper internal environment variable to ensure we don't fall # into an infinite loop, continuously re-executing ourselves. if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then _as_can_reexec=no; export _as_can_reexec; # We cannot yet assume a decent shell, so we have to provide a # neutralization value for shells without unset; and this also # works around shells that cannot unset nonexistent variables. # Preserve -v and -x to the replacement shell. BASH_ENV=/dev/null ENV=/dev/null (unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV case $- in # (((( *v*x* | *x*v* ) as_opts=-vx ;; *v* ) as_opts=-v ;; *x* ) as_opts=-x ;; * ) as_opts= ;; esac exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} # Admittedly, this is quite paranoid, since all the known shells bail # out after a failed `exec'. $as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 as_fn_exit 255 fi # We don't want this to propagate to other subprocesses. { _as_can_reexec=; unset _as_can_reexec;} if test "x$CONFIG_SHELL" = x; then as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which # is contrary to our usage. Disable this feature. alias -g '\${1+\"\$@\"}'='\"\$@\"' setopt NO_GLOB_SUBST else case \`(set -o) 2>/dev/null\` in #( *posix*) : set -o posix ;; #( *) : ;; esac fi " as_required="as_fn_return () { (exit \$1); } as_fn_success () { as_fn_return 0; } as_fn_failure () { as_fn_return 1; } as_fn_ret_success () { return 0; } as_fn_ret_failure () { return 1; } exitcode=0 as_fn_success || { exitcode=1; echo as_fn_success failed.; } as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then : else exitcode=1; echo positional parameters were not saved. fi test x\$exitcode = x0 || exit 1 test -x / || exit 1" as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1 test \$(( 1 + 1 )) = 2 || exit 1" if (eval "$as_required") 2>/dev/null; then : as_have_required=yes else as_have_required=no fi if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then : else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_found=false for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. as_found=: case $as_dir in #( /*) for as_base in sh bash ksh sh5; do # Try only shells that exist, to save several forks. as_shell=$as_dir/$as_base if { test -f "$as_shell" || test -f "$as_shell.exe"; } && { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then : CONFIG_SHELL=$as_shell as_have_required=yes if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then : break 2 fi fi done;; esac as_found=false done $as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } && { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then : CONFIG_SHELL=$SHELL as_have_required=yes fi; } IFS=$as_save_IFS if test "x$CONFIG_SHELL" != x; then : export CONFIG_SHELL # We cannot yet assume a decent shell, so we have to provide a # neutralization value for shells without unset; and this also # works around shells that cannot unset nonexistent variables. # Preserve -v and -x to the replacement shell. BASH_ENV=/dev/null ENV=/dev/null (unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV case $- in # (((( *v*x* | *x*v* ) as_opts=-vx ;; *v* ) as_opts=-v ;; *x* ) as_opts=-x ;; * ) as_opts= ;; esac exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} # Admittedly, this is quite paranoid, since all the known shells bail # out after a failed `exec'. $as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 exit 255 fi if test x$as_have_required = xno; then : $as_echo "$0: This script requires a shell more modern than all" $as_echo "$0: the shells that I found on your system." if test x${ZSH_VERSION+set} = xset ; then $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should" $as_echo "$0: be upgraded to zsh 4.3.4 or later." else $as_echo "$0: Please tell bug-autoconf@gnu.org about your system, $0: including any error possibly output before this $0: message. Then install a modern shell, or manually run $0: the script under such a shell if you do have one." fi exit 1 fi fi fi SHELL=${CONFIG_SHELL-/bin/sh} export SHELL # Unset more variables known to interfere with behavior of common tools. CLICOLOR_FORCE= GREP_OPTIONS= unset CLICOLOR_FORCE GREP_OPTIONS ## --------------------- ## ## M4sh Shell Functions. ## ## --------------------- ## # as_fn_unset VAR # --------------- # Portably unset VAR. as_fn_unset () { { eval $1=; unset $1;} } as_unset=as_fn_unset # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. as_fn_set_status () { return $1 } # as_fn_set_status # as_fn_exit STATUS # ----------------- # Exit the shell with STATUS, even in a "trap 0" or "set -e" context. as_fn_exit () { set +e as_fn_set_status $1 exit $1 } # as_fn_exit # as_fn_mkdir_p # ------------- # Create "$as_dir" as a directory, including parents if necessary. as_fn_mkdir_p () { case $as_dir in #( -*) as_dir=./$as_dir;; esac test -d "$as_dir" || eval $as_mkdir_p || { as_dirs= while :; do case $as_dir in #( *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" as_dir=`$as_dirname -- "$as_dir" || $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || $as_echo X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` test -d "$as_dir" && break done test -z "$as_dirs" || eval "mkdir $as_dirs" } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" } # as_fn_mkdir_p # as_fn_executable_p FILE # ----------------------- # Test if FILE is an executable regular file. as_fn_executable_p () { test -f "$1" && test -x "$1" } # as_fn_executable_p # as_fn_append VAR VALUE # ---------------------- # Append the text in VALUE to the end of the definition contained in VAR. Take # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : eval 'as_fn_append () { eval $1+=\$2 }' else as_fn_append () { eval $1=\$$1\$2 } fi # as_fn_append # as_fn_arith ARG... # ------------------ # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : eval 'as_fn_arith () { as_val=$(( $* )) }' else as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` } fi # as_fn_arith # as_fn_error STATUS ERROR [LINENO LOG_FD] # ---------------------------------------- # Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are # provided, also output the error to LOG_FD, referencing LINENO. Then exit the # script with STATUS, using 1 if that was 0. as_fn_error () { as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi $as_echo "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then as_dirname=dirname else as_dirname=false fi as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || $as_echo X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits as_lineno_1=$LINENO as_lineno_1a=$LINENO as_lineno_2=$LINENO as_lineno_2a=$LINENO eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" && test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || { # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-) sed -n ' p /[$]LINENO/= ' <$as_myself | sed ' s/[$]LINENO.*/&-/ t lineno b :lineno N :loop s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ t loop s/-\n.*// ' >$as_me.lineno && chmod +x "$as_me.lineno" || { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } # If we had to re-execute with $CONFIG_SHELL, we're ensured to have # already done that, so ensure we don't try to do so again and fall # in an infinite loop. This has already happened in practice. _as_can_reexec=no; export _as_can_reexec # Don't try to exec as it changes $[0], causing all sort of problems # (the dirname of $[0] is not the place where we might find the # original and so on. Autoconf is especially sensitive to this). . "./$as_me.lineno" # Exit status is that of the last command. exit } ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) case `echo 'xy\c'` in *c*) ECHO_T=' ';; # ECHO_T is single tab character. xy) ECHO_C='\c';; *) echo `echo ksh88 bug on AIX 6.1` > /dev/null ECHO_T=' ';; esac;; *) ECHO_N='-n';; esac rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir mkdir conf$$.dir 2>/dev/null fi if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. # In both cases, we have to default to `cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -pR' fi else as_ln_s='cp -pR' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null if mkdir -p . 2>/dev/null; then as_mkdir_p='mkdir -p "$as_dir"' else test -d ./-p && rmdir ./-p as_mkdir_p=false fi as_test_x='test -x' as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" # Sed expression to map a string onto a valid variable name. as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" test -n "$DJDIR" || exec 7<&0 &1 # Name of the host. # hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status, # so uname gets run too. ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` # # Initializations. # ac_default_prefix=/usr/local ac_clean_files= ac_config_libobj_dir=. LIBOBJS= cross_compiling=no subdirs= MFLAGS= MAKEFLAGS= # Identity of this package. PACKAGE_NAME= PACKAGE_TARNAME= PACKAGE_VERSION= PACKAGE_STRING= PACKAGE_BUGREPORT= PACKAGE_URL= ac_unique_file="./mod_proxy.c" # Factoring default headers for most tests. ac_includes_default="\ #include #ifdef HAVE_SYS_TYPES_H # include #endif #ifdef HAVE_SYS_STAT_H # include #endif #ifdef STDC_HEADERS # include # include #else # ifdef HAVE_STDLIB_H # include # endif #endif #ifdef HAVE_STRING_H # if !defined STDC_HEADERS && defined HAVE_MEMORY_H # include # endif # include #endif #ifdef HAVE_STRINGS_H # include #endif #ifdef HAVE_INTTYPES_H # include #endif #ifdef HAVE_STDINT_H # include #endif #ifdef HAVE_UNISTD_H # include #endif" ac_subst_vars='LTLIBOBJS LIBOBJS MODULE_LIBS LIBDIRS INCLUDES ENABLE_TESTS SET_MAKE EGREP GREP CPP OBJEXT EXEEXT ac_ct_CC CPPFLAGS LDFLAGS CFLAGS CC target_os target_vendor target_cpu target host_os host_vendor host_cpu host build_os build_vendor build_cpu build target_alias host_alias build_alias LIBS ECHO_T ECHO_N ECHO_C DEFS mandir localedir libdir psdir pdfdir dvidir htmldir infodir docdir oldincludedir includedir localstatedir sharedstatedir sysconfdir datadir datarootdir libexecdir sbindir bindir program_transform_name prefix exec_prefix PACKAGE_URL PACKAGE_BUGREPORT PACKAGE_STRING PACKAGE_VERSION PACKAGE_TARNAME PACKAGE_NAME PATH_SEPARATOR SHELL' ac_subst_files='' ac_user_opts=' enable_option_checking with_includes with_libraries enable_tests ' ac_precious_vars='build_alias host_alias target_alias CC CFLAGS LDFLAGS LIBS CPPFLAGS CPP' # Initialize some variables set by options. ac_init_help= ac_init_version=false ac_unrecognized_opts= ac_unrecognized_sep= # The variables have the same names as the options, with # dashes changed to underlines. cache_file=/dev/null exec_prefix=NONE no_create= no_recursion= prefix=NONE program_prefix=NONE program_suffix=NONE program_transform_name=s,x,x, silent= site= srcdir= verbose= x_includes=NONE x_libraries=NONE # Installation directory options. # These are left unexpanded so users can "make install exec_prefix=/foo" # and all the variables that are supposed to be based on exec_prefix # by default will actually change. # Use braces instead of parens because sh, perl, etc. also accept them. # (The list follows the same order as the GNU Coding Standards.) bindir='${exec_prefix}/bin' sbindir='${exec_prefix}/sbin' libexecdir='${exec_prefix}/libexec' datarootdir='${prefix}/share' datadir='${datarootdir}' sysconfdir='${prefix}/etc' sharedstatedir='${prefix}/com' localstatedir='${prefix}/var' includedir='${prefix}/include' oldincludedir='/usr/include' docdir='${datarootdir}/doc/${PACKAGE}' infodir='${datarootdir}/info' htmldir='${docdir}' dvidir='${docdir}' pdfdir='${docdir}' psdir='${docdir}' libdir='${exec_prefix}/lib' localedir='${datarootdir}/locale' mandir='${datarootdir}/man' ac_prev= ac_dashdash= for ac_option do # If the previous option needs an argument, assign it. if test -n "$ac_prev"; then eval $ac_prev=\$ac_option ac_prev= continue fi case $ac_option in *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; *=) ac_optarg= ;; *) ac_optarg=yes ;; esac # Accept the important Cygnus configure options, so we can diagnose typos. case $ac_dashdash$ac_option in --) ac_dashdash=yes ;; -bindir | --bindir | --bindi | --bind | --bin | --bi) ac_prev=bindir ;; -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) bindir=$ac_optarg ;; -build | --build | --buil | --bui | --bu) ac_prev=build_alias ;; -build=* | --build=* | --buil=* | --bui=* | --bu=*) build_alias=$ac_optarg ;; -cache-file | --cache-file | --cache-fil | --cache-fi \ | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) ac_prev=cache_file ;; -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) cache_file=$ac_optarg ;; --config-cache | -C) cache_file=config.cache ;; -datadir | --datadir | --datadi | --datad) ac_prev=datadir ;; -datadir=* | --datadir=* | --datadi=* | --datad=*) datadir=$ac_optarg ;; -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \ | --dataroo | --dataro | --datar) ac_prev=datarootdir ;; -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \ | --dataroot=* | --dataroo=* | --dataro=* | --datar=*) datarootdir=$ac_optarg ;; -disable-* | --disable-*) ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid feature name: $ac_useropt" ac_useropt_orig=$ac_useropt ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval enable_$ac_useropt=no ;; -docdir | --docdir | --docdi | --doc | --do) ac_prev=docdir ;; -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*) docdir=$ac_optarg ;; -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv) ac_prev=dvidir ;; -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*) dvidir=$ac_optarg ;; -enable-* | --enable-*) ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid feature name: $ac_useropt" ac_useropt_orig=$ac_useropt ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval enable_$ac_useropt=\$ac_optarg ;; -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ | --exec | --exe | --ex) ac_prev=exec_prefix ;; -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ | --exec=* | --exe=* | --ex=*) exec_prefix=$ac_optarg ;; -gas | --gas | --ga | --g) # Obsolete; use --with-gas. with_gas=yes ;; -help | --help | --hel | --he | -h) ac_init_help=long ;; -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) ac_init_help=recursive ;; -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) ac_init_help=short ;; -host | --host | --hos | --ho) ac_prev=host_alias ;; -host=* | --host=* | --hos=* | --ho=*) host_alias=$ac_optarg ;; -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht) ac_prev=htmldir ;; -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \ | --ht=*) htmldir=$ac_optarg ;; -includedir | --includedir | --includedi | --included | --include \ | --includ | --inclu | --incl | --inc) ac_prev=includedir ;; -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ | --includ=* | --inclu=* | --incl=* | --inc=*) includedir=$ac_optarg ;; -infodir | --infodir | --infodi | --infod | --info | --inf) ac_prev=infodir ;; -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) infodir=$ac_optarg ;; -libdir | --libdir | --libdi | --libd) ac_prev=libdir ;; -libdir=* | --libdir=* | --libdi=* | --libd=*) libdir=$ac_optarg ;; -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ | --libexe | --libex | --libe) ac_prev=libexecdir ;; -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ | --libexe=* | --libex=* | --libe=*) libexecdir=$ac_optarg ;; -localedir | --localedir | --localedi | --localed | --locale) ac_prev=localedir ;; -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*) localedir=$ac_optarg ;; -localstatedir | --localstatedir | --localstatedi | --localstated \ | --localstate | --localstat | --localsta | --localst | --locals) ac_prev=localstatedir ;; -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*) localstatedir=$ac_optarg ;; -mandir | --mandir | --mandi | --mand | --man | --ma | --m) ac_prev=mandir ;; -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) mandir=$ac_optarg ;; -nfp | --nfp | --nf) # Obsolete; use --without-fp. with_fp=no ;; -no-create | --no-create | --no-creat | --no-crea | --no-cre \ | --no-cr | --no-c | -n) no_create=yes ;; -no-recursion | --no-recursion | --no-recursio | --no-recursi \ | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) no_recursion=yes ;; -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ | --oldin | --oldi | --old | --ol | --o) ac_prev=oldincludedir ;; -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) oldincludedir=$ac_optarg ;; -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) ac_prev=prefix ;; -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) prefix=$ac_optarg ;; -program-prefix | --program-prefix | --program-prefi | --program-pref \ | --program-pre | --program-pr | --program-p) ac_prev=program_prefix ;; -program-prefix=* | --program-prefix=* | --program-prefi=* \ | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) program_prefix=$ac_optarg ;; -program-suffix | --program-suffix | --program-suffi | --program-suff \ | --program-suf | --program-su | --program-s) ac_prev=program_suffix ;; -program-suffix=* | --program-suffix=* | --program-suffi=* \ | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) program_suffix=$ac_optarg ;; -program-transform-name | --program-transform-name \ | --program-transform-nam | --program-transform-na \ | --program-transform-n | --program-transform- \ | --program-transform | --program-transfor \ | --program-transfo | --program-transf \ | --program-trans | --program-tran \ | --progr-tra | --program-tr | --program-t) ac_prev=program_transform_name ;; -program-transform-name=* | --program-transform-name=* \ | --program-transform-nam=* | --program-transform-na=* \ | --program-transform-n=* | --program-transform-=* \ | --program-transform=* | --program-transfor=* \ | --program-transfo=* | --program-transf=* \ | --program-trans=* | --program-tran=* \ | --progr-tra=* | --program-tr=* | --program-t=*) program_transform_name=$ac_optarg ;; -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd) ac_prev=pdfdir ;; -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*) pdfdir=$ac_optarg ;; -psdir | --psdir | --psdi | --psd | --ps) ac_prev=psdir ;; -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*) psdir=$ac_optarg ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil) silent=yes ;; -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) ac_prev=sbindir ;; -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ | --sbi=* | --sb=*) sbindir=$ac_optarg ;; -sharedstatedir | --sharedstatedir | --sharedstatedi \ | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ | --sharedst | --shareds | --shared | --share | --shar \ | --sha | --sh) ac_prev=sharedstatedir ;; -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ | --sha=* | --sh=*) sharedstatedir=$ac_optarg ;; -site | --site | --sit) ac_prev=site ;; -site=* | --site=* | --sit=*) site=$ac_optarg ;; -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) ac_prev=srcdir ;; -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) srcdir=$ac_optarg ;; -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ | --syscon | --sysco | --sysc | --sys | --sy) ac_prev=sysconfdir ;; -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) sysconfdir=$ac_optarg ;; -target | --target | --targe | --targ | --tar | --ta | --t) ac_prev=target_alias ;; -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) target_alias=$ac_optarg ;; -v | -verbose | --verbose | --verbos | --verbo | --verb) verbose=yes ;; -version | --version | --versio | --versi | --vers | -V) ac_init_version=: ;; -with-* | --with-*) ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid package name: $ac_useropt" ac_useropt_orig=$ac_useropt ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval with_$ac_useropt=\$ac_optarg ;; -without-* | --without-*) ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid package name: $ac_useropt" ac_useropt_orig=$ac_useropt ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval with_$ac_useropt=no ;; --x) # Obsolete; use --with-x. with_x=yes ;; -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ | --x-incl | --x-inc | --x-in | --x-i) ac_prev=x_includes ;; -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) x_includes=$ac_optarg ;; -x-libraries | --x-libraries | --x-librarie | --x-librari \ | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) ac_prev=x_libraries ;; -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) x_libraries=$ac_optarg ;; -*) as_fn_error $? "unrecognized option: \`$ac_option' Try \`$0 --help' for more information" ;; *=*) ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` # Reject names that are not valid shell variable names. case $ac_envvar in #( '' | [0-9]* | *[!_$as_cr_alnum]* ) as_fn_error $? "invalid variable name: \`$ac_envvar'" ;; esac eval $ac_envvar=\$ac_optarg export $ac_envvar ;; *) # FIXME: should be removed in autoconf 3.0. $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2 expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2 : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" ;; esac done if test -n "$ac_prev"; then ac_option=--`echo $ac_prev | sed 's/_/-/g'` as_fn_error $? "missing argument to $ac_option" fi if test -n "$ac_unrecognized_opts"; then case $enable_option_checking in no) ;; fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; *) $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; esac fi # Check all directory arguments for consistency. for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ datadir sysconfdir sharedstatedir localstatedir includedir \ oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ libdir localedir mandir do eval ac_val=\$$ac_var # Remove trailing slashes. case $ac_val in */ ) ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'` eval $ac_var=\$ac_val;; esac # Be sure to have absolute directory names. case $ac_val in [\\/$]* | ?:[\\/]* ) continue;; NONE | '' ) case $ac_var in *prefix ) continue;; esac;; esac as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" done # There might be people who depend on the old broken behavior: `$host' # used to hold the argument of --host etc. # FIXME: To remove some day. build=$build_alias host=$host_alias target=$target_alias # FIXME: To remove some day. if test "x$host_alias" != x; then if test "x$build_alias" = x; then cross_compiling=maybe elif test "x$build_alias" != "x$host_alias"; then cross_compiling=yes fi fi ac_tool_prefix= test -n "$host_alias" && ac_tool_prefix=$host_alias- test "$silent" = yes && exec 6>/dev/null ac_pwd=`pwd` && test -n "$ac_pwd" && ac_ls_di=`ls -di .` && ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || as_fn_error $? "working directory cannot be determined" test "X$ac_ls_di" = "X$ac_pwd_ls_di" || as_fn_error $? "pwd does not report name of working directory" # Find the source files, if location was not specified. if test -z "$srcdir"; then ac_srcdir_defaulted=yes # Try the directory containing this script, then the parent directory. ac_confdir=`$as_dirname -- "$as_myself" || $as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_myself" : 'X\(//\)[^/]' \| \ X"$as_myself" : 'X\(//\)$' \| \ X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || $as_echo X"$as_myself" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` srcdir=$ac_confdir if test ! -r "$srcdir/$ac_unique_file"; then srcdir=.. fi else ac_srcdir_defaulted=no fi if test ! -r "$srcdir/$ac_unique_file"; then test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" fi ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work" ac_abs_confdir=`( cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" pwd)` # When building in place, set srcdir=. if test "$ac_abs_confdir" = "$ac_pwd"; then srcdir=. fi # Remove unnecessary trailing slashes from srcdir. # Double slashes in file names in object file debugging info # mess up M-x gdb in Emacs. case $srcdir in */) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;; esac for ac_var in $ac_precious_vars; do eval ac_env_${ac_var}_set=\${${ac_var}+set} eval ac_env_${ac_var}_value=\$${ac_var} eval ac_cv_env_${ac_var}_set=\${${ac_var}+set} eval ac_cv_env_${ac_var}_value=\$${ac_var} done # # Report the --help message. # if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF \`configure' configures this package to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... To assign environment variables (e.g., CC, CFLAGS...), specify them as VAR=VALUE. See below for descriptions of some of the useful variables. Defaults for the options are specified in brackets. Configuration: -h, --help display this help and exit --help=short display options specific to this package --help=recursive display the short help of all the included packages -V, --version display version information and exit -q, --quiet, --silent do not print \`checking ...' messages --cache-file=FILE cache test results in FILE [disabled] -C, --config-cache alias for \`--cache-file=config.cache' -n, --no-create do not create output files --srcdir=DIR find the sources in DIR [configure dir or \`..'] Installation directories: --prefix=PREFIX install architecture-independent files in PREFIX [$ac_default_prefix] --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX [PREFIX] By default, \`make install' will install all the files in \`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify an installation prefix other than \`$ac_default_prefix' using \`--prefix', for instance \`--prefix=\$HOME'. For better control, use the options below. Fine tuning of the installation directories: --bindir=DIR user executables [EPREFIX/bin] --sbindir=DIR system admin executables [EPREFIX/sbin] --libexecdir=DIR program executables [EPREFIX/libexec] --sysconfdir=DIR read-only single-machine data [PREFIX/etc] --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] --localstatedir=DIR modifiable single-machine data [PREFIX/var] --libdir=DIR object code libraries [EPREFIX/lib] --includedir=DIR C header files [PREFIX/include] --oldincludedir=DIR C header files for non-gcc [/usr/include] --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] --datadir=DIR read-only architecture-independent data [DATAROOTDIR] --infodir=DIR info documentation [DATAROOTDIR/info] --localedir=DIR locale-dependent data [DATAROOTDIR/locale] --mandir=DIR man documentation [DATAROOTDIR/man] --docdir=DIR documentation root [DATAROOTDIR/doc/PACKAGE] --htmldir=DIR html documentation [DOCDIR] --dvidir=DIR dvi documentation [DOCDIR] --pdfdir=DIR pdf documentation [DOCDIR] --psdir=DIR ps documentation [DOCDIR] _ACEOF cat <<\_ACEOF System types: --build=BUILD configure for building on BUILD [guessed] --host=HOST cross-compile to build programs to run on HOST [BUILD] --target=TARGET configure for building compilers for TARGET [HOST] _ACEOF fi if test -n "$ac_init_help"; then cat <<\_ACEOF Optional Features: --disable-option-checking ignore unrecognized --enable/--with options --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) --enable-FEATURE[=ARG] include FEATURE [ARG=yes] --enable-tests enable unit tests (default=no) Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) --with-includes=LIST add additional include paths to proftpd. LIST is a colon-separated list of include paths to add e.g. --with-includes=/some/mysql/include:/my/include --with-libraries=LIST add additional library paths to proftpd. LIST is a colon-separated list of include paths to add e.g. --with-libraries=/some/mysql/libdir:/my/libs Some influential environment variables: CC C compiler command CFLAGS C compiler flags LDFLAGS linker flags, e.g. -L if you have libraries in a nonstandard directory LIBS libraries to pass to the linker, e.g. -l CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I if you have headers in a nonstandard directory CPP C preprocessor Use these variables to override the choices made by `configure' or to help it to find libraries and programs with nonstandard names/locations. Report bugs to the package provider. _ACEOF ac_status=$? fi if test "$ac_init_help" = "recursive"; then # If there are subdirs, report their specific --help. for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue test -d "$ac_dir" || { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } || continue ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; esac ;; esac ac_abs_top_builddir=$ac_pwd ac_abs_builddir=$ac_pwd$ac_dir_suffix # for backward compatibility: ac_top_builddir=$ac_top_build_prefix case $srcdir in .) # We are building in place. ac_srcdir=. ac_top_srcdir=$ac_top_builddir_sub ac_abs_top_srcdir=$ac_pwd ;; [\\/]* | ?:[\\/]* ) # Absolute name. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ac_abs_top_srcdir=$srcdir ;; *) # Relative name. ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_build_prefix$srcdir ac_abs_top_srcdir=$ac_pwd/$srcdir ;; esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix cd "$ac_dir" || { ac_status=$?; continue; } # Check for guested configure. if test -f "$ac_srcdir/configure.gnu"; then echo && $SHELL "$ac_srcdir/configure.gnu" --help=recursive elif test -f "$ac_srcdir/configure"; then echo && $SHELL "$ac_srcdir/configure" --help=recursive else $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2 fi || ac_status=$? cd "$ac_pwd" || { ac_status=$?; break; } done fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF configure generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. This configure script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it. _ACEOF exit fi ## ------------------------ ## ## Autoconf initialization. ## ## ------------------------ ## # ac_fn_c_try_compile LINENO # -------------------------- # Try to compile conftest.$ac_ext, and return whether this succeeded. ac_fn_c_try_compile () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack rm -f conftest.$ac_objext if { { ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_compile") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { test -z "$ac_c_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then : ac_retval=0 else $as_echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 fi eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_compile # ac_fn_c_try_cpp LINENO # ---------------------- # Try to preprocess conftest.$ac_ext, and return whether this succeeded. ac_fn_c_try_cpp () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack if { { ac_try="$ac_cpp conftest.$ac_ext" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } > conftest.i && { test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" || test ! -s conftest.err }; then : ac_retval=0 else $as_echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 fi eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_cpp # ac_fn_c_check_header_mongrel LINENO HEADER VAR INCLUDES # ------------------------------------------------------- # Tests whether HEADER exists, giving a warning if it cannot be compiled using # the include files in INCLUDES and setting the cache variable VAR # accordingly. ac_fn_c_check_header_mongrel () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack if eval \${$3+:} false; then : { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 $as_echo_n "checking for $2... " >&6; } if eval \${$3+:} false; then : $as_echo_n "(cached) " >&6 fi eval ac_res=\$$3 { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 $as_echo "$ac_res" >&6; } else # Is the header compilable? { $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 usability" >&5 $as_echo_n "checking $2 usability... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 #include <$2> _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_header_compiler=yes else ac_header_compiler=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_compiler" >&5 $as_echo "$ac_header_compiler" >&6; } # Is the header present? { $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 presence" >&5 $as_echo_n "checking $2 presence... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include <$2> _ACEOF if ac_fn_c_try_cpp "$LINENO"; then : ac_header_preproc=yes else ac_header_preproc=no fi rm -f conftest.err conftest.i conftest.$ac_ext { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_preproc" >&5 $as_echo "$ac_header_preproc" >&6; } # So? What about this header? case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in #(( yes:no: ) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&5 $as_echo "$as_me: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&2;} { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5 $as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;} ;; no:yes:* ) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: present but cannot be compiled" >&5 $as_echo "$as_me: WARNING: $2: present but cannot be compiled" >&2;} { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: check for missing prerequisite headers?" >&5 $as_echo "$as_me: WARNING: $2: check for missing prerequisite headers?" >&2;} { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: see the Autoconf documentation" >&5 $as_echo "$as_me: WARNING: $2: see the Autoconf documentation" >&2;} { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&5 $as_echo "$as_me: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&2;} { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5 $as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;} ;; esac { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 $as_echo_n "checking for $2... " >&6; } if eval \${$3+:} false; then : $as_echo_n "(cached) " >&6 else eval "$3=\$ac_header_compiler" fi eval ac_res=\$$3 { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 $as_echo "$ac_res" >&6; } fi eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_check_header_mongrel # ac_fn_c_try_run LINENO # ---------------------- # Try to link conftest.$ac_ext, and return whether this succeeded. Assumes # that executables *can* be run. ac_fn_c_try_run () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { ac_try='./conftest$ac_exeext' { { case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_try") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; }; then : ac_retval=0 else $as_echo "$as_me: program exited with status $ac_status" >&5 $as_echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=$ac_status fi rm -rf conftest.dSYM conftest_ipa8_conftest.oo eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_run # ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES # ------------------------------------------------------- # Tests whether HEADER exists and can be compiled using the include files in # INCLUDES, setting the cache variable VAR accordingly. ac_fn_c_check_header_compile () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 $as_echo_n "checking for $2... " >&6; } if eval \${$3+:} false; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 #include <$2> _ACEOF if ac_fn_c_try_compile "$LINENO"; then : eval "$3=yes" else eval "$3=no" fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi eval ac_res=\$$3 { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 $as_echo "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_check_header_compile # ac_fn_c_try_link LINENO # ----------------------- # Try to link conftest.$ac_ext, and return whether this succeeded. ac_fn_c_try_link () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack rm -f conftest.$ac_objext conftest$ac_exeext if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_link") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { test -z "$ac_c_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && { test "$cross_compiling" = yes || test -x conftest$ac_exeext }; then : ac_retval=0 else $as_echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 fi # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would # interfere with the next link command; also delete a directory that is # left behind by Apple's compiler. We do this before executing the actions. rm -rf conftest.dSYM conftest_ipa8_conftest.oo eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_link # ac_fn_c_check_func LINENO FUNC VAR # ---------------------------------- # Tests whether FUNC exists, setting the cache variable VAR accordingly ac_fn_c_check_func () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 $as_echo_n "checking for $2... " >&6; } if eval \${$3+:} false; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Define $2 to an innocuous variant, in case declares $2. For example, HP-UX 11i declares gettimeofday. */ #define $2 innocuous_$2 /* System header to define __stub macros and hopefully few prototypes, which can conflict with char $2 (); below. Prefer to if __STDC__ is defined, since exists even on freestanding compilers. */ #ifdef __STDC__ # include #else # include #endif #undef $2 /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char $2 (); /* The GNU C library defines this for functions which it implements to always fail with ENOSYS. Some functions are actually named something starting with __ and the normal name is an alias. */ #if defined __stub_$2 || defined __stub___$2 choke me #endif int main () { return $2 (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : eval "$3=yes" else eval "$3=no" fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext fi eval ac_res=\$$3 { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 $as_echo "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_check_func cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. It was created by $as_me, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ _ACEOF exec 5>>config.log { cat <<_ASUNAME ## --------- ## ## Platform. ## ## --------- ## hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` uname -m = `(uname -m) 2>/dev/null || echo unknown` uname -r = `(uname -r) 2>/dev/null || echo unknown` uname -s = `(uname -s) 2>/dev/null || echo unknown` uname -v = `(uname -v) 2>/dev/null || echo unknown` /usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` /bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` /bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` /usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` /usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` /usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown` /bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` /usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` /bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` _ASUNAME as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. $as_echo "PATH: $as_dir" done IFS=$as_save_IFS } >&5 cat >&5 <<_ACEOF ## ----------- ## ## Core tests. ## ## ----------- ## _ACEOF # Keep a trace of the command line. # Strip out --no-create and --no-recursion so they do not pile up. # Strip out --silent because we don't want to record it for future runs. # Also quote any args containing shell meta-characters. # Make two passes to allow for proper duplicate-argument suppression. ac_configure_args= ac_configure_args0= ac_configure_args1= ac_must_keep_next=false for ac_pass in 1 2 do for ac_arg do case $ac_arg in -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil) continue ;; *\'*) ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; esac case $ac_pass in 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; 2) as_fn_append ac_configure_args1 " '$ac_arg'" if test $ac_must_keep_next = true; then ac_must_keep_next=false # Got value, back to normal. else case $ac_arg in *=* | --config-cache | -C | -disable-* | --disable-* \ | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ | -with-* | --with-* | -without-* | --without-* | --x) case "$ac_configure_args0 " in "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; esac ;; -* ) ac_must_keep_next=true ;; esac fi as_fn_append ac_configure_args " '$ac_arg'" ;; esac done done { ac_configure_args0=; unset ac_configure_args0;} { ac_configure_args1=; unset ac_configure_args1;} # When interrupted or exit'd, cleanup temporary files, and complete # config.log. We remove comments because anyway the quotes in there # would cause problems or look ugly. # WARNING: Use '\'' to represent an apostrophe within the trap. # WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. trap 'exit_status=$? # Save into config.log some information that might help in debugging. { echo $as_echo "## ---------------- ## ## Cache variables. ## ## ---------------- ##" echo # The following way of writing the cache mishandles newlines in values, ( for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do eval ac_val=\$$ac_var case $ac_val in #( *${as_nl}*) case $ac_var in #( *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( *) { eval $ac_var=; unset $ac_var;} ;; esac ;; esac done (set) 2>&1 | case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #( *${as_nl}ac_space=\ *) sed -n \ "s/'\''/'\''\\\\'\'''\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p" ;; #( *) sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | sort ) echo $as_echo "## ----------------- ## ## Output variables. ## ## ----------------- ##" echo for ac_var in $ac_subst_vars do eval ac_val=\$$ac_var case $ac_val in *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac $as_echo "$ac_var='\''$ac_val'\''" done | sort echo if test -n "$ac_subst_files"; then $as_echo "## ------------------- ## ## File substitutions. ## ## ------------------- ##" echo for ac_var in $ac_subst_files do eval ac_val=\$$ac_var case $ac_val in *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac $as_echo "$ac_var='\''$ac_val'\''" done | sort echo fi if test -s confdefs.h; then $as_echo "## ----------- ## ## confdefs.h. ## ## ----------- ##" echo cat confdefs.h echo fi test "$ac_signal" != 0 && $as_echo "$as_me: caught signal $ac_signal" $as_echo "$as_me: exit $exit_status" } >&5 rm -f core *.core core.conftest.* && rm -f -r conftest* confdefs* conf$$* $ac_clean_files && exit $exit_status ' 0 for ac_signal in 1 2 13 15; do trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal done ac_signal=0 # confdefs.h avoids OS command line length limits that DEFS can exceed. rm -f -r conftest* confdefs.h $as_echo "/* confdefs.h */" > confdefs.h # Predefined preprocessor variables. cat >>confdefs.h <<_ACEOF #define PACKAGE_NAME "$PACKAGE_NAME" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_TARNAME "$PACKAGE_TARNAME" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_VERSION "$PACKAGE_VERSION" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_STRING "$PACKAGE_STRING" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_URL "$PACKAGE_URL" _ACEOF # Let the site file select an alternate cache file if it wants to. # Prefer an explicitly selected file to automatically selected ones. ac_site_file1=NONE ac_site_file2=NONE if test -n "$CONFIG_SITE"; then # We do not want a PATH search for config.site. case $CONFIG_SITE in #(( -*) ac_site_file1=./$CONFIG_SITE;; */*) ac_site_file1=$CONFIG_SITE;; *) ac_site_file1=./$CONFIG_SITE;; esac elif test "x$prefix" != xNONE; then ac_site_file1=$prefix/share/config.site ac_site_file2=$prefix/etc/config.site else ac_site_file1=$ac_default_prefix/share/config.site ac_site_file2=$ac_default_prefix/etc/config.site fi for ac_site_file in "$ac_site_file1" "$ac_site_file2" do test "x$ac_site_file" = xNONE && continue if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 $as_echo "$as_me: loading site script $ac_site_file" >&6;} sed 's/^/| /' "$ac_site_file" >&5 . "$ac_site_file" \ || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "failed to load site script $ac_site_file See \`config.log' for more details" "$LINENO" 5; } fi done if test -r "$cache_file"; then # Some versions of bash will fail to source /dev/null (special files # actually), so we avoid doing that. DJGPP emulates it as a regular file. if test /dev/null != "$cache_file" && test -f "$cache_file"; then { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 $as_echo "$as_me: loading cache $cache_file" >&6;} case $cache_file in [\\/]* | ?:[\\/]* ) . "$cache_file";; *) . "./$cache_file";; esac fi else { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 $as_echo "$as_me: creating cache $cache_file" >&6;} >$cache_file fi # Check that the precious variables saved in the cache have kept the same # value. ac_cache_corrupted=false for ac_var in $ac_precious_vars; do eval ac_old_set=\$ac_cv_env_${ac_var}_set eval ac_new_set=\$ac_env_${ac_var}_set eval ac_old_val=\$ac_cv_env_${ac_var}_value eval ac_new_val=\$ac_env_${ac_var}_value case $ac_old_set,$ac_new_set in set,) { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 $as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} ac_cache_corrupted=: ;; ,set) { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5 $as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} ac_cache_corrupted=: ;; ,);; *) if test "x$ac_old_val" != "x$ac_new_val"; then # differences in whitespace do not lead to failure. ac_old_val_w=`echo x $ac_old_val` ac_new_val_w=`echo x $ac_new_val` if test "$ac_old_val_w" != "$ac_new_val_w"; then { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5 $as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} ac_cache_corrupted=: else { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5 $as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;} eval $ac_var=\$ac_old_val fi { $as_echo "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5 $as_echo "$as_me: former value: \`$ac_old_val'" >&2;} { $as_echo "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5 $as_echo "$as_me: current value: \`$ac_new_val'" >&2;} fi;; esac # Pass precious variables to config.status. if test "$ac_new_set" = set; then case $ac_new_val in *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; *) ac_arg=$ac_var=$ac_new_val ;; esac case " $ac_configure_args " in *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. *) as_fn_append ac_configure_args " '$ac_arg'" ;; esac fi done if $ac_cache_corrupted; then { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 $as_echo "$as_me: error: changes in the environment can compromise the build" >&2;} as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5 fi ## -------------------- ## ## Main body of script. ## ## -------------------- ## ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_aux_dir= for ac_dir in "$srcdir" "$srcdir/.." "$srcdir/../.."; do if test -f "$ac_dir/install-sh"; then ac_aux_dir=$ac_dir ac_install_sh="$ac_aux_dir/install-sh -c" break elif test -f "$ac_dir/install.sh"; then ac_aux_dir=$ac_dir ac_install_sh="$ac_aux_dir/install.sh -c" break elif test -f "$ac_dir/shtool"; then ac_aux_dir=$ac_dir ac_install_sh="$ac_aux_dir/shtool install -c" break fi done if test -z "$ac_aux_dir"; then as_fn_error $? "cannot find install-sh, install.sh, or shtool in \"$srcdir\" \"$srcdir/..\" \"$srcdir/../..\"" "$LINENO" 5 fi # These three variables are undocumented and unsupported, # and are intended to be withdrawn in a future Autoconf release. # They can cause serious problems if a builder's source tree is in a directory # whose full name contains unusual characters. ac_config_guess="$SHELL $ac_aux_dir/config.guess" # Please don't use this var. ac_config_sub="$SHELL $ac_aux_dir/config.sub" # Please don't use this var. ac_configure="$SHELL $ac_aux_dir/configure" # Please don't use this var. # Make sure we can run config.sub. $SHELL "$ac_aux_dir/config.sub" sun4 >/dev/null 2>&1 || as_fn_error $? "cannot run $SHELL $ac_aux_dir/config.sub" "$LINENO" 5 { $as_echo "$as_me:${as_lineno-$LINENO}: checking build system type" >&5 $as_echo_n "checking build system type... " >&6; } if ${ac_cv_build+:} false; then : $as_echo_n "(cached) " >&6 else ac_build_alias=$build_alias test "x$ac_build_alias" = x && ac_build_alias=`$SHELL "$ac_aux_dir/config.guess"` test "x$ac_build_alias" = x && as_fn_error $? "cannot guess build type; you must specify one" "$LINENO" 5 ac_cv_build=`$SHELL "$ac_aux_dir/config.sub" $ac_build_alias` || as_fn_error $? "$SHELL $ac_aux_dir/config.sub $ac_build_alias failed" "$LINENO" 5 fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_build" >&5 $as_echo "$ac_cv_build" >&6; } case $ac_cv_build in *-*-*) ;; *) as_fn_error $? "invalid value of canonical build" "$LINENO" 5;; esac build=$ac_cv_build ac_save_IFS=$IFS; IFS='-' set x $ac_cv_build shift build_cpu=$1 build_vendor=$2 shift; shift # Remember, the first character of IFS is used to create $*, # except with old shells: build_os=$* IFS=$ac_save_IFS case $build_os in *\ *) build_os=`echo "$build_os" | sed 's/ /-/g'`;; esac { $as_echo "$as_me:${as_lineno-$LINENO}: checking host system type" >&5 $as_echo_n "checking host system type... " >&6; } if ${ac_cv_host+:} false; then : $as_echo_n "(cached) " >&6 else if test "x$host_alias" = x; then ac_cv_host=$ac_cv_build else ac_cv_host=`$SHELL "$ac_aux_dir/config.sub" $host_alias` || as_fn_error $? "$SHELL $ac_aux_dir/config.sub $host_alias failed" "$LINENO" 5 fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_host" >&5 $as_echo "$ac_cv_host" >&6; } case $ac_cv_host in *-*-*) ;; *) as_fn_error $? "invalid value of canonical host" "$LINENO" 5;; esac host=$ac_cv_host ac_save_IFS=$IFS; IFS='-' set x $ac_cv_host shift host_cpu=$1 host_vendor=$2 shift; shift # Remember, the first character of IFS is used to create $*, # except with old shells: host_os=$* IFS=$ac_save_IFS case $host_os in *\ *) host_os=`echo "$host_os" | sed 's/ /-/g'`;; esac { $as_echo "$as_me:${as_lineno-$LINENO}: checking target system type" >&5 $as_echo_n "checking target system type... " >&6; } if ${ac_cv_target+:} false; then : $as_echo_n "(cached) " >&6 else if test "x$target_alias" = x; then ac_cv_target=$ac_cv_host else ac_cv_target=`$SHELL "$ac_aux_dir/config.sub" $target_alias` || as_fn_error $? "$SHELL $ac_aux_dir/config.sub $target_alias failed" "$LINENO" 5 fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_target" >&5 $as_echo "$ac_cv_target" >&6; } case $ac_cv_target in *-*-*) ;; *) as_fn_error $? "invalid value of canonical target" "$LINENO" 5;; esac target=$ac_cv_target ac_save_IFS=$IFS; IFS='-' set x $ac_cv_target shift target_cpu=$1 target_vendor=$2 shift; shift # Remember, the first character of IFS is used to create $*, # except with old shells: target_os=$* IFS=$ac_save_IFS case $target_os in *\ *) target_os=`echo "$target_os" | sed 's/ /-/g'`;; esac # The aliases save the names the user supplied, while $host etc. # will get canonicalized. test -n "$target_alias" && test "$program_prefix$program_suffix$program_transform_name" = \ NONENONEs,x,x, && program_prefix=${target_alias}- ostype=`echo $build_os | sed 's/\..*$//g' | sed 's/-.*//g' | tr abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ` ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. set dummy ${ac_tool_prefix}gcc; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_CC+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}gcc" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 $as_echo "$CC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi if test -z "$ac_cv_prog_CC"; then ac_ct_CC=$CC # Extract the first word of "gcc", so it can be a program name with args. set dummy gcc; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_ac_ct_CC+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="gcc" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 $as_echo "$ac_ct_CC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi if test "x$ac_ct_CC" = x; then CC="" else case $cross_compiling:$ac_tool_warned in yes:) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 $as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC fi else CC="$ac_cv_prog_CC" fi if test -z "$CC"; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. set dummy ${ac_tool_prefix}cc; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_CC+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}cc" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 $as_echo "$CC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi fi if test -z "$CC"; then # Extract the first word of "cc", so it can be a program name with args. set dummy cc; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_CC+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else ac_prog_rejected=no as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then ac_prog_rejected=yes continue fi ac_cv_prog_CC="cc" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS if test $ac_prog_rejected = yes; then # We found a bogon in the path, so make sure we never use it. set dummy $ac_cv_prog_CC shift if test $# != 0; then # We chose a different compiler from the bogus one. # However, it has the same basename, so the bogon will be chosen # first if we set CC to just the basename; use the full file name. shift ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@" fi fi fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 $as_echo "$CC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi if test -z "$CC"; then if test -n "$ac_tool_prefix"; then for ac_prog in cl.exe do # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. set dummy $ac_tool_prefix$ac_prog; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_CC+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_CC="$ac_tool_prefix$ac_prog" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 $as_echo "$CC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi test -n "$CC" && break done fi if test -z "$CC"; then ac_ct_CC=$CC for ac_prog in cl.exe do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_ac_ct_CC+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="$ac_prog" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 $as_echo "$ac_ct_CC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi test -n "$ac_ct_CC" && break done if test "x$ac_ct_CC" = x; then CC="" else case $cross_compiling:$ac_tool_warned in yes:) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 $as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC fi fi fi test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "no acceptable C compiler found in \$PATH See \`config.log' for more details" "$LINENO" 5; } # Provide some information about the compiler. $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 set X $ac_compile ac_compiler=$2 for ac_option in --version -v -V -qversion; do { { ac_try="$ac_compiler $ac_option >&5" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_compiler $ac_option >&5") 2>conftest.err ac_status=$? if test -s conftest.err; then sed '10a\ ... rest of stderr output deleted ... 10q' conftest.err >conftest.er1 cat conftest.er1 >&5 fi rm -f conftest.er1 conftest.err $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } done cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out" # Try to create an executable without -o first, disregard a.out. # It will help us diagnose broken compilers, and finding out an intuition # of exeext. { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5 $as_echo_n "checking whether the C compiler works... " >&6; } ac_link_default=`$as_echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'` # The possible output files: ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*" ac_rmfiles= for ac_file in $ac_files do case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; * ) ac_rmfiles="$ac_rmfiles $ac_file";; esac done rm -f $ac_rmfiles if { { ac_try="$ac_link_default" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_link_default") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then : # Autoconf-2.13 could set the ac_cv_exeext variable to `no'. # So ignore a value of `no', otherwise this would lead to `EXEEXT = no' # in a Makefile. We should not override ac_cv_exeext if it was cached, # so that the user can short-circuit this test for compilers unknown to # Autoconf. for ac_file in $ac_files '' do test -f "$ac_file" || continue case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; [ab].out ) # We found the default executable, but exeext='' is most # certainly right. break;; *.* ) if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no; then :; else ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` fi # We set ac_cv_exeext here because the later test for it is not # safe: cross compilers may not add the suffix if given an `-o' # argument, so we may need to know it at that point already. # Even if this section looks crufty: it has the advantage of # actually working. break;; * ) break;; esac done test "$ac_cv_exeext" = no && ac_cv_exeext= else ac_file='' fi if test -z "$ac_file"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } $as_echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error 77 "C compiler cannot create executables See \`config.log' for more details" "$LINENO" 5; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5 $as_echo_n "checking for C compiler default output file name... " >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 $as_echo "$ac_file" >&6; } ac_exeext=$ac_cv_exeext rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out ac_clean_files=$ac_clean_files_save { $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 $as_echo_n "checking for suffix of executables... " >&6; } if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then : # If both `conftest.exe' and `conftest' are `present' (well, observable) # catch `conftest.exe'. For instance with Cygwin, `ls conftest' will # work properly (i.e., refer to `conftest.exe'), while it won't with # `rm'. for ac_file in conftest.exe conftest conftest.*; do test -f "$ac_file" || continue case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` break;; * ) break;; esac done else { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of executables: cannot compile and link See \`config.log' for more details" "$LINENO" 5; } fi rm -f conftest conftest$ac_cv_exeext { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 $as_echo "$ac_cv_exeext" >&6; } rm -f conftest.$ac_ext EXEEXT=$ac_cv_exeext ac_exeext=$EXEEXT cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main () { FILE *f = fopen ("conftest.out", "w"); return ferror (f) || fclose (f) != 0; ; return 0; } _ACEOF ac_clean_files="$ac_clean_files conftest.out" # Check that the compiler produces executables we can run. If not, either # the compiler is broken, or we cross compile. { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5 $as_echo_n "checking whether we are cross compiling... " >&6; } if test "$cross_compiling" != yes; then { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } if { ac_try='./conftest$ac_cv_exeext' { { case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_try") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; }; then cross_compiling=no else if test "$cross_compiling" = maybe; then cross_compiling=yes else { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot run C compiled programs. If you meant to cross compile, use \`--host'. See \`config.log' for more details" "$LINENO" 5; } fi fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 $as_echo "$cross_compiling" >&6; } rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out ac_clean_files=$ac_clean_files_save { $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 $as_echo_n "checking for suffix of object files... " >&6; } if ${ac_cv_objext+:} false; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF rm -f conftest.o conftest.obj if { { ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_compile") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then : for ac_file in conftest.o conftest.obj conftest.*; do test -f "$ac_file" || continue; case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;; *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'` break;; esac done else $as_echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of object files: cannot compile See \`config.log' for more details" "$LINENO" 5; } fi rm -f conftest.$ac_cv_objext conftest.$ac_ext fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 $as_echo "$ac_cv_objext" >&6; } OBJEXT=$ac_cv_objext ac_objext=$OBJEXT { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5 $as_echo_n "checking whether we are using the GNU C compiler... " >&6; } if ${ac_cv_c_compiler_gnu+:} false; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { #ifndef __GNUC__ choke me #endif ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_compiler_gnu=yes else ac_compiler_gnu=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext ac_cv_c_compiler_gnu=$ac_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 $as_echo "$ac_cv_c_compiler_gnu" >&6; } if test $ac_compiler_gnu = yes; then GCC=yes else GCC= fi ac_test_CFLAGS=${CFLAGS+set} ac_save_CFLAGS=$CFLAGS { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 $as_echo_n "checking whether $CC accepts -g... " >&6; } if ${ac_cv_prog_cc_g+:} false; then : $as_echo_n "(cached) " >&6 else ac_save_c_werror_flag=$ac_c_werror_flag ac_c_werror_flag=yes ac_cv_prog_cc_g=no CFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_cv_prog_cc_g=yes else CFLAGS="" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : else ac_c_werror_flag=$ac_save_c_werror_flag CFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_cv_prog_cc_g=yes fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext ac_c_werror_flag=$ac_save_c_werror_flag fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 $as_echo "$ac_cv_prog_cc_g" >&6; } if test "$ac_test_CFLAGS" = set; then CFLAGS=$ac_save_CFLAGS elif test $ac_cv_prog_cc_g = yes; then if test "$GCC" = yes; then CFLAGS="-g -O2" else CFLAGS="-g" fi else if test "$GCC" = yes; then CFLAGS="-O2" else CFLAGS= fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5 $as_echo_n "checking for $CC option to accept ISO C89... " >&6; } if ${ac_cv_prog_cc_c89+:} false; then : $as_echo_n "(cached) " >&6 else ac_cv_prog_cc_c89=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include struct stat; /* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */ struct buf { int x; }; FILE * (*rcsopen) (struct buf *, struct stat *, int); static char *e (p, i) char **p; int i; { return p[i]; } static char *f (char * (*g) (char **, int), char **p, ...) { char *s; va_list v; va_start (v,p); s = g (p, va_arg (v,int)); va_end (v); return s; } /* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has function prototypes and stuff, but not '\xHH' hex character constants. These don't provoke an error unfortunately, instead are silently treated as 'x'. The following induces an error, until -std is added to get proper ANSI mode. Curiously '\x00'!='x' always comes out true, for an array size at least. It's necessary to write '\x00'==0 to get something that's true only with -std. */ int osf4_cc_array ['\x00' == 0 ? 1 : -1]; /* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters inside strings and character constants. */ #define FOO(x) 'x' int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1]; int test (int i, double x); struct s1 {int (*f) (int a);}; struct s2 {int (*f) (double a);}; int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int); int argc; char **argv; int main () { return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]; ; return 0; } _ACEOF for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \ -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" do CC="$ac_save_CC $ac_arg" if ac_fn_c_try_compile "$LINENO"; then : ac_cv_prog_cc_c89=$ac_arg fi rm -f core conftest.err conftest.$ac_objext test "x$ac_cv_prog_cc_c89" != "xno" && break done rm -f conftest.$ac_ext CC=$ac_save_CC fi # AC_CACHE_VAL case "x$ac_cv_prog_cc_c89" in x) { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 $as_echo "none needed" >&6; } ;; xno) { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 $as_echo "unsupported" >&6; } ;; *) CC="$CC $ac_cv_prog_cc_c89" { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 $as_echo "$ac_cv_prog_cc_c89" >&6; } ;; esac if test "x$ac_cv_prog_cc_c89" != xno; then : fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5 $as_echo_n "checking how to run the C preprocessor... " >&6; } # On Suns, sometimes $CPP names a directory. if test -n "$CPP" && test -d "$CPP"; then CPP= fi if test -z "$CPP"; then if ${ac_cv_prog_CPP+:} false; then : $as_echo_n "(cached) " >&6 else # Double quotes because CPP needs to be expanded for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp" do ac_preproc_ok=false for ac_c_preproc_warn_flag in '' yes do # Use a header file that comes with gcc, so configuring glibc # with a fresh cross-compiler works. # Prefer to if __STDC__ is defined, since # exists even on freestanding compilers. # On the NeXT, cc -E runs the code through the compiler's parser, # not just through cpp. "Syntax error" is here to catch this case. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #ifdef __STDC__ # include #else # include #endif Syntax error _ACEOF if ac_fn_c_try_cpp "$LINENO"; then : else # Broken: fails on valid input. continue fi rm -f conftest.err conftest.i conftest.$ac_ext # OK, works on sane cases. Now check whether nonexistent headers # can be detected and how. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include _ACEOF if ac_fn_c_try_cpp "$LINENO"; then : # Broken: success on invalid input. continue else # Passes both tests. ac_preproc_ok=: break fi rm -f conftest.err conftest.i conftest.$ac_ext done # Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. rm -f conftest.i conftest.err conftest.$ac_ext if $ac_preproc_ok; then : break fi done ac_cv_prog_CPP=$CPP fi CPP=$ac_cv_prog_CPP else ac_cv_prog_CPP=$CPP fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5 $as_echo "$CPP" >&6; } ac_preproc_ok=false for ac_c_preproc_warn_flag in '' yes do # Use a header file that comes with gcc, so configuring glibc # with a fresh cross-compiler works. # Prefer to if __STDC__ is defined, since # exists even on freestanding compilers. # On the NeXT, cc -E runs the code through the compiler's parser, # not just through cpp. "Syntax error" is here to catch this case. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #ifdef __STDC__ # include #else # include #endif Syntax error _ACEOF if ac_fn_c_try_cpp "$LINENO"; then : else # Broken: fails on valid input. continue fi rm -f conftest.err conftest.i conftest.$ac_ext # OK, works on sane cases. Now check whether nonexistent headers # can be detected and how. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include _ACEOF if ac_fn_c_try_cpp "$LINENO"; then : # Broken: success on invalid input. continue else # Passes both tests. ac_preproc_ok=: break fi rm -f conftest.err conftest.i conftest.$ac_ext done # Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. rm -f conftest.i conftest.err conftest.$ac_ext if $ac_preproc_ok; then : else { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "C preprocessor \"$CPP\" fails sanity check See \`config.log' for more details" "$LINENO" 5; } fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu { $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5 $as_echo_n "checking for grep that handles long lines and -e... " >&6; } if ${ac_cv_path_GREP+:} false; then : $as_echo_n "(cached) " >&6 else if test -z "$GREP"; then ac_path_GREP_found=false # Loop through the user's path and test for each of PROGNAME-LIST as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_prog in grep ggrep; do for ac_exec_ext in '' $ac_executable_extensions; do ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext" as_fn_executable_p "$ac_path_GREP" || continue # Check for GNU ac_path_GREP and select it if it is found. # Check for GNU $ac_path_GREP case `"$ac_path_GREP" --version 2>&1` in *GNU*) ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;; *) ac_count=0 $as_echo_n 0123456789 >"conftest.in" while : do cat "conftest.in" "conftest.in" >"conftest.tmp" mv "conftest.tmp" "conftest.in" cp "conftest.in" "conftest.nl" $as_echo 'GREP' >> "conftest.nl" "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break as_fn_arith $ac_count + 1 && ac_count=$as_val if test $ac_count -gt ${ac_path_GREP_max-0}; then # Best one so far, save it but keep looking for a better one ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_max=$ac_count fi # 10*(2^10) chars as input seems more than enough test $ac_count -gt 10 && break done rm -f conftest.in conftest.tmp conftest.nl conftest.out;; esac $ac_path_GREP_found && break 3 done done done IFS=$as_save_IFS if test -z "$ac_cv_path_GREP"; then as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 fi else ac_cv_path_GREP=$GREP fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5 $as_echo "$ac_cv_path_GREP" >&6; } GREP="$ac_cv_path_GREP" { $as_echo "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5 $as_echo_n "checking for egrep... " >&6; } if ${ac_cv_path_EGREP+:} false; then : $as_echo_n "(cached) " >&6 else if echo a | $GREP -E '(a|b)' >/dev/null 2>&1 then ac_cv_path_EGREP="$GREP -E" else if test -z "$EGREP"; then ac_path_EGREP_found=false # Loop through the user's path and test for each of PROGNAME-LIST as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_prog in egrep; do for ac_exec_ext in '' $ac_executable_extensions; do ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext" as_fn_executable_p "$ac_path_EGREP" || continue # Check for GNU ac_path_EGREP and select it if it is found. # Check for GNU $ac_path_EGREP case `"$ac_path_EGREP" --version 2>&1` in *GNU*) ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;; *) ac_count=0 $as_echo_n 0123456789 >"conftest.in" while : do cat "conftest.in" "conftest.in" >"conftest.tmp" mv "conftest.tmp" "conftest.in" cp "conftest.in" "conftest.nl" $as_echo 'EGREP' >> "conftest.nl" "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break as_fn_arith $ac_count + 1 && ac_count=$as_val if test $ac_count -gt ${ac_path_EGREP_max-0}; then # Best one so far, save it but keep looking for a better one ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_max=$ac_count fi # 10*(2^10) chars as input seems more than enough test $ac_count -gt 10 && break done rm -f conftest.in conftest.tmp conftest.nl conftest.out;; esac $ac_path_EGREP_found && break 3 done done done IFS=$as_save_IFS if test -z "$ac_cv_path_EGREP"; then as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 fi else ac_cv_path_EGREP=$EGREP fi fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5 $as_echo "$ac_cv_path_EGREP" >&6; } EGREP="$ac_cv_path_EGREP" { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5 $as_echo_n "checking for ANSI C header files... " >&6; } if ${ac_cv_header_stdc+:} false; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #include #include int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_cv_header_stdc=yes else ac_cv_header_stdc=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext if test $ac_cv_header_stdc = yes; then # SunOS 4.x string.h does not declare mem*, contrary to ANSI. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include _ACEOF if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | $EGREP "memchr" >/dev/null 2>&1; then : else ac_cv_header_stdc=no fi rm -f conftest* fi if test $ac_cv_header_stdc = yes; then # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include _ACEOF if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | $EGREP "free" >/dev/null 2>&1; then : else ac_cv_header_stdc=no fi rm -f conftest* fi if test $ac_cv_header_stdc = yes; then # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi. if test "$cross_compiling" = yes; then : : else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #if ((' ' & 0x0FF) == 0x020) # define ISLOWER(c) ('a' <= (c) && (c) <= 'z') # define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c)) #else # define ISLOWER(c) \ (('a' <= (c) && (c) <= 'i') \ || ('j' <= (c) && (c) <= 'r') \ || ('s' <= (c) && (c) <= 'z')) # define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c)) #endif #define XOR(e, f) (((e) && !(f)) || (!(e) && (f))) int main () { int i; for (i = 0; i < 256; i++) if (XOR (islower (i), ISLOWER (i)) || toupper (i) != TOUPPER (i)) return 2; return 0; } _ACEOF if ac_fn_c_try_run "$LINENO"; then : else ac_cv_header_stdc=no fi rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ conftest.$ac_objext conftest.beam conftest.$ac_ext fi fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5 $as_echo "$ac_cv_header_stdc" >&6; } if test $ac_cv_header_stdc = yes; then $as_echo "#define STDC_HEADERS 1" >>confdefs.h fi # On IRIX 5.3, sys/types and inttypes.h are conflicting. for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \ inttypes.h stdint.h unistd.h do : as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default " if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : cat >>confdefs.h <<_ACEOF #define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 _ACEOF fi done ac_fn_c_check_header_mongrel "$LINENO" "minix/config.h" "ac_cv_header_minix_config_h" "$ac_includes_default" if test "x$ac_cv_header_minix_config_h" = xyes; then : MINIX=yes else MINIX= fi if test "$MINIX" = yes; then $as_echo "#define _POSIX_SOURCE 1" >>confdefs.h $as_echo "#define _POSIX_1_SOURCE 2" >>confdefs.h $as_echo "#define _MINIX 1" >>confdefs.h fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether it is safe to define __EXTENSIONS__" >&5 $as_echo_n "checking whether it is safe to define __EXTENSIONS__... " >&6; } if ${ac_cv_safe_to_define___extensions__+:} false; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ # define __EXTENSIONS__ 1 $ac_includes_default int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_cv_safe_to_define___extensions__=yes else ac_cv_safe_to_define___extensions__=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_safe_to_define___extensions__" >&5 $as_echo "$ac_cv_safe_to_define___extensions__" >&6; } test $ac_cv_safe_to_define___extensions__ = yes && $as_echo "#define __EXTENSIONS__ 1" >>confdefs.h $as_echo "#define _ALL_SOURCE 1" >>confdefs.h $as_echo "#define _GNU_SOURCE 1" >>confdefs.h $as_echo "#define _POSIX_PTHREAD_SEMANTICS 1" >>confdefs.h $as_echo "#define _TANDEM_SOURCE 1" >>confdefs.h { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing strerror" >&5 $as_echo_n "checking for library containing strerror... " >&6; } if ${ac_cv_search_strerror+:} false; then : $as_echo_n "(cached) " >&6 else ac_func_search_save_LIBS=$LIBS cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char strerror (); int main () { return strerror (); ; return 0; } _ACEOF for ac_lib in '' cposix; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi if ac_fn_c_try_link "$LINENO"; then : ac_cv_search_strerror=$ac_res fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext if ${ac_cv_search_strerror+:} false; then : break fi done if ${ac_cv_search_strerror+:} false; then : else ac_cv_search_strerror=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_strerror" >&5 $as_echo "$ac_cv_search_strerror" >&6; } ac_res=$ac_cv_search_strerror if test "$ac_res" != no; then : test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} sets \$(MAKE)" >&5 $as_echo_n "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; } set x ${MAKE-make} ac_make=`$as_echo "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'` if eval \${ac_cv_prog_make_${ac_make}_set+:} false; then : $as_echo_n "(cached) " >&6 else cat >conftest.make <<\_ACEOF SHELL = /bin/sh all: @echo '@@@%%%=$(MAKE)=@@@%%%' _ACEOF # GNU make sometimes prints "make[1]: Entering ...", which would confuse us. case `${MAKE-make} -f conftest.make 2>/dev/null` in *@@@%%%=?*=@@@%%%*) eval ac_cv_prog_make_${ac_make}_set=yes;; *) eval ac_cv_prog_make_${ac_make}_set=no;; esac rm -f conftest.make fi if eval test \$ac_cv_prog_make_${ac_make}_set = yes; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } SET_MAKE= else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } SET_MAKE="MAKE=${MAKE-make}" fi # Check whether --with-includes was given. if test "${with_includes+set}" = set; then : withval=$with_includes; ac_addl_includes=`echo "$withval" | sed -e 's/:/ /g'` ; for ainclude in $ac_addl_includes; do if test x"$ac_build_addl_includes" = x ; then ac_build_addl_includes="-I$ainclude" else ac_build_addl_includes="-I$ainclude $ac_build_addl_includes" fi done CPPFLAGS="$CPPFLAGS $ac_build_addl_includes" fi # Check whether --with-libraries was given. if test "${with_libraries+set}" = set; then : withval=$with_libraries; ac_addl_libdirs=`echo "$withval" | sed -e 's/:/ /g'` ; for alibdir in $ac_addl_libdirs; do if test x"$ac_build_addl_libdirs" = x ; then ac_build_addl_libdirs="-L$alibdir" else ac_build_addl_libdirs="-L$alibdir $ac_build_addl_libdirs" fi done LDFLAGS="$LDFLAGS $ac_build_addl_libdirs" fi ENABLE_TESTS="\"\"" # Check whether --enable-tests was given. if test "${enable_tests+set}" = set; then : enableval=$enable_tests; if test x"$enableval" != xno ; then for ac_header in check.h do : ac_fn_c_check_header_mongrel "$LINENO" "check.h" "ac_cv_header_check_h" "$ac_includes_default" if test "x$ac_cv_header_check_h" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_CHECK_H 1 _ACEOF fi done { $as_echo "$as_me:${as_lineno-$LINENO}: checking for tcase_create in -lcheck" >&5 $as_echo_n "checking for tcase_create in -lcheck... " >&6; } if ${ac_cv_lib_check_tcase_create+:} false; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lcheck $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char tcase_create (); int main () { return tcase_create (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_check_tcase_create=yes else ac_cv_lib_check_tcase_create=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_check_tcase_create" >&5 $as_echo "$ac_cv_lib_check_tcase_create" >&6; } if test "x$ac_cv_lib_check_tcase_create" = xyes; then : $as_echo "#define HAVE_LIBCHECK 1" >>confdefs.h ENABLE_TESTS="1" else as_fn_error $? "libcheck support, required for tests, not present -- aborting" "$LINENO" 5 fi fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5 $as_echo_n "checking for ANSI C header files... " >&6; } if ${ac_cv_header_stdc+:} false; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #include #include int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_cv_header_stdc=yes else ac_cv_header_stdc=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext if test $ac_cv_header_stdc = yes; then # SunOS 4.x string.h does not declare mem*, contrary to ANSI. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include _ACEOF if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | $EGREP "memchr" >/dev/null 2>&1; then : else ac_cv_header_stdc=no fi rm -f conftest* fi if test $ac_cv_header_stdc = yes; then # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include _ACEOF if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | $EGREP "free" >/dev/null 2>&1; then : else ac_cv_header_stdc=no fi rm -f conftest* fi if test $ac_cv_header_stdc = yes; then # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi. if test "$cross_compiling" = yes; then : : else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #if ((' ' & 0x0FF) == 0x020) # define ISLOWER(c) ('a' <= (c) && (c) <= 'z') # define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c)) #else # define ISLOWER(c) \ (('a' <= (c) && (c) <= 'i') \ || ('j' <= (c) && (c) <= 'r') \ || ('s' <= (c) && (c) <= 'z')) # define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c)) #endif #define XOR(e, f) (((e) && !(f)) || (!(e) && (f))) int main () { int i; for (i = 0; i < 256; i++) if (XOR (islower (i), ISLOWER (i)) || toupper (i) != TOUPPER (i)) return 2; return 0; } _ACEOF if ac_fn_c_try_run "$LINENO"; then : else ac_cv_header_stdc=no fi rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ conftest.$ac_objext conftest.beam conftest.$ac_ext fi fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5 $as_echo "$ac_cv_header_stdc" >&6; } if test $ac_cv_header_stdc = yes; then $as_echo "#define STDC_HEADERS 1" >>confdefs.h fi for ac_header in sqlite3.h stdlib.h unistd.h limits.h fcntl.h sys/sysctl.h sys/sysinfo.h do : as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default" if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : cat >>confdefs.h <<_ACEOF #define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 _ACEOF fi done ac_fn_c_check_header_mongrel "$LINENO" "zlib.h" "ac_cv_header_zlib_h" "$ac_includes_default" if test "x$ac_cv_header_zlib_h" = xyes; then : $as_echo "#define HAVE_ZLIB_H 1" >>confdefs.h MODULE_LIBS="$MODULE_LIBS -lz" fi for ac_func in random srandom strnstr sysctl sysinfo do : as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" if eval test \"x\$"$as_ac_var"\" = x"yes"; then : cat >>confdefs.h <<_ACEOF #define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1 _ACEOF fi done ac_dns_libs="" { $as_echo "$as_me:${as_lineno-$LINENO}: checking for resolver symbols in libc" >&5 $as_echo_n "checking for resolver symbols in libc... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #include #include int main () { int res; res = res_query(NULL, ns_c_in, ns_t_txt, NULL, 0); res = ns_initparse(NULL, 0, NULL); res = ns_parserr(NULL, ns_s_an, 0, NULL); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: checking for resolver symbols in libresolv" >&5 $as_echo_n "checking for resolver symbols in libresolv... " >&6; } saved_libs="$LIBS" LIBS="$LIBS -lresolv" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #include #include int main () { int res; res = res_query(NULL, ns_c_in, ns_t_txt, NULL, 0); res = ns_initparse(NULL, 0, NULL); res = ns_parserr(NULL, ns_s_an, 0, NULL); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } LIBS="$saved_libs" ac_dns_libs="-lresolv" else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } LIBS="$saved_libs" { $as_echo "$as_me:${as_lineno-$LINENO}: checking for resolver symbols in /usr/lib64/libresolv.a" >&5 $as_echo_n "checking for resolver symbols in /usr/lib64/libresolv.a... " >&6; } saved_libs="$LIBS" LIBS="$LIBS /usr/lib64/libresolv.a" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #include #include int main () { int res; res = res_query(NULL, ns_c_in, ns_t_txt, NULL, 0); res = ns_initparse(NULL, 0, NULL); res = ns_parserr(NULL, ns_s_an, 0, NULL); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } LIBS="$saved_libs" ac_dns_libs="/usr/lib64/libresolv.a" else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } LIBS="$saved_libs" { $as_echo "$as_me:${as_lineno-$LINENO}: checking for resolver symbols in /usr/lib/libresolv.a" >&5 $as_echo_n "checking for resolver symbols in /usr/lib/libresolv.a... " >&6; } saved_libs="$LIBS" LIBS="$LIBS /usr/lib/libresolv.a" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #include #include int main () { int res; res = res_query(NULL, ns_c_in, ns_t_txt, NULL, 0); res = ns_initparse(NULL, 0, NULL); res = ns_parserr(NULL, ns_s_an, 0, NULL); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } LIBS="$saved_libs" ac_dns_libs="/usr/lib/libresolv.a" else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } LIBS="$saved_libs" fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext # Check for OpenSSL-isms { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether OpenSSL has crippled AES support" >&5 $as_echo_n "checking whether OpenSSL has crippled AES support... " >&6; } LIBS="-lcrypto $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #ifdef HAVE_STRING_H # include #endif #include int main () { EVP_CIPHER *c; c = EVP_aes_192_cbc(); c = EVP_aes_256_cbc(); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } LIBS="$saved_libs" else { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } $as_echo "#define HAVE_AES_CRIPPLED_OPENSSL 1" >>confdefs.h LIBS="$saved_libs" fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether OpenSSL supports SHA256" >&5 $as_echo_n "checking whether OpenSSL supports SHA256... " >&6; } LIBS="-lcrypto $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main () { const EVP_MD *md; md = EVP_sha256(); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } $as_echo "#define HAVE_SHA256_OPENSSL 1" >>confdefs.h LIBS="$saved_libs" else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } LIBS="$saved_libs" fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether OpenSSL supports SHA512" >&5 $as_echo_n "checking whether OpenSSL supports SHA512... " >&6; } LIBS="-lcrypto $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main () { const EVP_MD *md; md = EVP_sha512(); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } $as_echo "#define HAVE_SHA512_OPENSSL 1" >>confdefs.h LIBS="$saved_libs" else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } LIBS="$saved_libs" fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether OpenSSL supports EVP_aes_128_ctr" >&5 $as_echo_n "checking whether OpenSSL supports EVP_aes_128_ctr... " >&6; } LIBS="-lcrypto $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main () { EVP_CIPHER *cipher; cipher = EVP_aes_128_ctr(); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } $as_echo "#define HAVE_EVP_AES_128_CTR_OPENSSL 1" >>confdefs.h LIBS="$saved_libs" else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } LIBS="$saved_libs" fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether OpenSSL supports EVP_aes_192_ctr" >&5 $as_echo_n "checking whether OpenSSL supports EVP_aes_192_ctr... " >&6; } LIBS="-lcrypto $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main () { EVP_CIPHER *cipher; cipher = EVP_aes_192_ctr(); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } $as_echo "#define HAVE_EVP_AES_192_CTR_OPENSSL 1" >>confdefs.h LIBS="$saved_libs" else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } LIBS="$saved_libs" fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether OpenSSL supports EVP_aes_256_ctr" >&5 $as_echo_n "checking whether OpenSSL supports EVP_aes_256_ctr... " >&6; } LIBS="-lcrypto $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main () { EVP_CIPHER *cipher; cipher = EVP_aes_256_ctr(); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } $as_echo "#define HAVE_EVP_AES_256_CTR_OPENSSL 1" >>confdefs.h LIBS="$saved_libs" else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } LIBS="$saved_libs" fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether OpenSSL supports EVP_aes_256_gcm" >&5 $as_echo_n "checking whether OpenSSL supports EVP_aes_256_gcm... " >&6; } LIBS="-lcrypto $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main () { EVP_CIPHER *cipher; cipher = EVP_aes_256_gcm(); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } $as_echo "#define HAVE_EVP_AES_256_GCM_OPENSSL 1" >>confdefs.h LIBS="$saved_libs" else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } LIBS="$saved_libs" fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether OpenSSL supports X448 algorithm" >&5 $as_echo_n "checking whether OpenSSL supports X448 algorithm... " >&6; } LIBS="-lcrypto $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main () { EVP_PKEY *pkey; EVP_PKEY_CTX *pctx; pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_X448, NULL, NULL, 0); pctx = EVP_PKEY_CTX_new_id(NID_X448, NULL); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } $as_echo "#define HAVE_X448_OPENSSL 1" >>confdefs.h LIBS="$saved_libs" else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } LIBS="$saved_libs" fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether OpenSSL supports OSSL_PROVIDER_load" >&5 $as_echo_n "checking whether OpenSSL supports OSSL_PROVIDER_load... " >&6; } LIBS="-lcrypto $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main () { OSSL_PROVIDER *provider; provider = OSSL_PROVIDER_load(NULL, "default"); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } $as_echo "#define HAVE_OSSL_PROVIDER_LOAD_OPENSSL 1" >>confdefs.h LIBS="$saved_libs" else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } LIBS="$saved_libs" fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext # Check for SQLite-isms { $as_echo "$as_me:${as_lineno-$LINENO}: checking for sqlite3_stmt_readonly" >&5 $as_echo_n "checking for sqlite3_stmt_readonly... " >&6; } saved_libs="$LIBS" LIBS="-lsqlite3" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #ifdef HAVE_SQLITE3_H # include #endif int main () { (void) sqlite3_stmt_readonly(NULL); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } $as_echo "#define HAVE_SQLITE3_STMT_READONLY 1" >>confdefs.h else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS="$saved_libs" { $as_echo "$as_me:${as_lineno-$LINENO}: checking for sqlite3_trace" >&5 $as_echo_n "checking for sqlite3_trace... " >&6; } saved_libs="$LIBS" LIBS="-lsqlite3" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #ifdef HAVE_SQLITE3_H # include #endif int main () { (void) sqlite3_trace(NULL, NULL, NULL); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } $as_echo "#define HAVE_SQLITE3_TRACE 1" >>confdefs.h else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS="$saved_libs" { $as_echo "$as_me:${as_lineno-$LINENO}: checking for sqlite3_trace_v2" >&5 $as_echo_n "checking for sqlite3_trace_v2... " >&6; } saved_libs="$LIBS" LIBS="-lsqlite3" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #ifdef HAVE_SQLITE3_H # include #endif int main () { (void) sqlite3_trace_v2(NULL, 0, NULL, NULL); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } $as_echo "#define HAVE_SQLITE3_TRACE_V2 1" >>confdefs.h else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS="$saved_libs" INCLUDES="$ac_build_addl_includes" LIBDIRS="$ac_build_addl_libdirs" MODULE_LIBS="$MODULE_LIBS $ac_dns_libs" ac_config_headers="$ac_config_headers mod_proxy.h" ac_config_files="$ac_config_files t/Makefile Makefile" cat >confcache <<\_ACEOF # This file is a shell script that caches the results of configure # tests run on this system so they can be shared between configure # scripts and configure runs, see configure's option --config-cache. # It is not useful on other systems. If it contains results you don't # want to keep, you may remove or edit it. # # config.status only pays attention to the cache file if you give it # the --recheck option to rerun configure. # # `ac_cv_env_foo' variables (set or unset) will be overridden when # loading this file, other *unset* `ac_cv_foo' will be assigned the # following values. _ACEOF # The following way of writing the cache mishandles newlines in values, # but we know of no workaround that is simple, portable, and efficient. # So, we kill variables containing newlines. # Ultrix sh set writes to stderr and can't be redirected directly, # and sets the high bit in the cache file unless we assign to the vars. ( for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do eval ac_val=\$$ac_var case $ac_val in #( *${as_nl}*) case $ac_var in #( *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( *) { eval $ac_var=; unset $ac_var;} ;; esac ;; esac done (set) 2>&1 | case $as_nl`(ac_space=' '; set) 2>&1` in #( *${as_nl}ac_space=\ *) # `set' does not quote correctly, so add quotes: double-quote # substitution turns \\\\ into \\, and sed turns \\ into \. sed -n \ "s/'/'\\\\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" ;; #( *) # `set' quotes correctly as required by POSIX, so do not add quotes. sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | sort ) | sed ' /^ac_cv_env_/b end t clear :clear s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/ t end s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ :end' >>confcache if diff "$cache_file" confcache >/dev/null 2>&1; then :; else if test -w "$cache_file"; then if test "x$cache_file" != "x/dev/null"; then { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 $as_echo "$as_me: updating cache $cache_file" >&6;} if test ! -f "$cache_file" || test -h "$cache_file"; then cat confcache >"$cache_file" else case $cache_file in #( */* | ?:*) mv -f confcache "$cache_file"$$ && mv -f "$cache_file"$$ "$cache_file" ;; #( *) mv -f confcache "$cache_file" ;; esac fi fi else { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 $as_echo "$as_me: not updating unwritable cache $cache_file" >&6;} fi fi rm -f confcache test "x$prefix" = xNONE && prefix=$ac_default_prefix # Let make expand exec_prefix. test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' DEFS=-DHAVE_CONFIG_H ac_libobjs= ac_ltlibobjs= U= for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue # 1. Remove the extension, and $U if already installed. ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' ac_i=`$as_echo "$ac_i" | sed "$ac_script"` # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR # will be set to the directory where LIBOBJS objects are built. as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo' done LIBOBJS=$ac_libobjs LTLIBOBJS=$ac_ltlibobjs : "${CONFIG_STATUS=./config.status}" ac_write_fail=0 ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files $CONFIG_STATUS" { $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 $as_echo "$as_me: creating $CONFIG_STATUS" >&6;} as_write_fail=0 cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 #! $SHELL # Generated by $as_me. # Run this file to recreate the current configuration. # Compiler output produced by configure, useful for debugging # configure, is in config.log if it exists. debug=false ac_cs_recheck=false ac_cs_silent=false SHELL=\${CONFIG_SHELL-$SHELL} export SHELL _ASEOF cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 ## -------------------- ## ## M4sh Initialization. ## ## -------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; esac fi as_nl=' ' export as_nl # Printing a long string crashes Solaris 7 /usr/bin/printf. as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo # Prefer a ksh shell builtin over an external printf program on Solaris, # but without wasting forks for bash or zsh. if test -z "$BASH_VERSION$ZSH_VERSION" \ && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then as_echo='print -r --' as_echo_n='print -rn --' elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then as_echo='printf %s\n' as_echo_n='printf %s' else if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' as_echo_n='/usr/ucb/echo -n' else as_echo_body='eval expr "X$1" : "X\\(.*\\)"' as_echo_n_body='eval arg=$1; case $arg in #( *"$as_nl"*) expr "X$arg" : "X\\(.*\\)$as_nl"; arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; esac; expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" ' export as_echo_n_body as_echo_n='sh -c $as_echo_n_body as_echo' fi export as_echo_body as_echo='sh -c $as_echo_body as_echo' fi # The user is always right. if test "${PATH_SEPARATOR+set}" != set; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || PATH_SEPARATOR=';' } fi # IFS # We need space, tab and new line, in precisely that order. Quoting is # there to prevent editors from complaining about space-tab. # (If _AS_PATH_WALK were called with IFS unset, it would disable word # splitting by setting IFS to empty value.) IFS=" "" $as_nl" # Find who we are. Look in the path if we contain no directory separator. as_myself= case $0 in #(( *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break done IFS=$as_save_IFS ;; esac # We did not find ourselves, most probably we were run as `sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi # Unset variables that we do not need and which cause bugs (e.g. in # pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" # suppresses any "Segmentation fault" message there. '((' could # trigger a bug in pdksh 5.2.14. for as_var in BASH_ENV ENV MAIL MAILPATH do eval test x\${$as_var+set} = xset \ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : done PS1='$ ' PS2='> ' PS4='+ ' # NLS nuisances. LC_ALL=C export LC_ALL LANGUAGE=C export LANGUAGE # CDPATH. (unset CDPATH) >/dev/null 2>&1 && unset CDPATH # as_fn_error STATUS ERROR [LINENO LOG_FD] # ---------------------------------------- # Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are # provided, also output the error to LOG_FD, referencing LINENO. Then exit the # script with STATUS, using 1 if that was 0. as_fn_error () { as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi $as_echo "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. as_fn_set_status () { return $1 } # as_fn_set_status # as_fn_exit STATUS # ----------------- # Exit the shell with STATUS, even in a "trap 0" or "set -e" context. as_fn_exit () { set +e as_fn_set_status $1 exit $1 } # as_fn_exit # as_fn_unset VAR # --------------- # Portably unset VAR. as_fn_unset () { { eval $1=; unset $1;} } as_unset=as_fn_unset # as_fn_append VAR VALUE # ---------------------- # Append the text in VALUE to the end of the definition contained in VAR. Take # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : eval 'as_fn_append () { eval $1+=\$2 }' else as_fn_append () { eval $1=\$$1\$2 } fi # as_fn_append # as_fn_arith ARG... # ------------------ # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : eval 'as_fn_arith () { as_val=$(( $* )) }' else as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` } fi # as_fn_arith if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then as_dirname=dirname else as_dirname=false fi as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || $as_echo X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) case `echo 'xy\c'` in *c*) ECHO_T=' ';; # ECHO_T is single tab character. xy) ECHO_C='\c';; *) echo `echo ksh88 bug on AIX 6.1` > /dev/null ECHO_T=' ';; esac;; *) ECHO_N='-n';; esac rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir mkdir conf$$.dir 2>/dev/null fi if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. # In both cases, we have to default to `cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -pR' fi else as_ln_s='cp -pR' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null # as_fn_mkdir_p # ------------- # Create "$as_dir" as a directory, including parents if necessary. as_fn_mkdir_p () { case $as_dir in #( -*) as_dir=./$as_dir;; esac test -d "$as_dir" || eval $as_mkdir_p || { as_dirs= while :; do case $as_dir in #( *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" as_dir=`$as_dirname -- "$as_dir" || $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || $as_echo X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` test -d "$as_dir" && break done test -z "$as_dirs" || eval "mkdir $as_dirs" } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" } # as_fn_mkdir_p if mkdir -p . 2>/dev/null; then as_mkdir_p='mkdir -p "$as_dir"' else test -d ./-p && rmdir ./-p as_mkdir_p=false fi # as_fn_executable_p FILE # ----------------------- # Test if FILE is an executable regular file. as_fn_executable_p () { test -f "$1" && test -x "$1" } # as_fn_executable_p as_test_x='test -x' as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" # Sed expression to map a string onto a valid variable name. as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" exec 6>&1 ## ----------------------------------- ## ## Main body of $CONFIG_STATUS script. ## ## ----------------------------------- ## _ASEOF test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1 cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # Save the log message, to keep $0 and so on meaningful, and to # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" This file was extended by $as_me, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES CONFIG_HEADERS = $CONFIG_HEADERS CONFIG_LINKS = $CONFIG_LINKS CONFIG_COMMANDS = $CONFIG_COMMANDS $ $0 $@ on `(hostname || uname -n) 2>/dev/null | sed 1q` " _ACEOF case $ac_config_files in *" "*) set x $ac_config_files; shift; ac_config_files=$*;; esac case $ac_config_headers in *" "*) set x $ac_config_headers; shift; ac_config_headers=$*;; esac cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 # Files that config.status was made for. config_files="$ac_config_files" config_headers="$ac_config_headers" _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 ac_cs_usage="\ \`$as_me' instantiates files and other configuration actions from templates according to the current configuration. Unless the files and actions are specified as TAGs, all are instantiated by default. Usage: $0 [OPTION]... [TAG]... -h, --help print this help, then exit -V, --version print version number and configuration settings, then exit --config print configuration, then exit -q, --quiet, --silent do not print progress messages -d, --debug don't remove temporary files --recheck update $as_me by reconfiguring in the same conditions --file=FILE[:TEMPLATE] instantiate the configuration file FILE --header=FILE[:TEMPLATE] instantiate the configuration header FILE Configuration files: $config_files Configuration headers: $config_headers Report bugs to the package provider." _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ config.status configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" Copyright (C) 2012 Free Software Foundation, Inc. This config.status script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it." ac_pwd='$ac_pwd' srcdir='$srcdir' test -n "\$AWK" || AWK=awk _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # The default lists apply if the user does not specify any file. ac_need_defaults=: while test $# != 0 do case $1 in --*=?*) ac_option=`expr "X$1" : 'X\([^=]*\)='` ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` ac_shift=: ;; --*=) ac_option=`expr "X$1" : 'X\([^=]*\)='` ac_optarg= ac_shift=: ;; *) ac_option=$1 ac_optarg=$2 ac_shift=shift ;; esac case $ac_option in # Handling of the options. -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) ac_cs_recheck=: ;; --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) $as_echo "$ac_cs_version"; exit ;; --config | --confi | --conf | --con | --co | --c ) $as_echo "$ac_cs_config"; exit ;; --debug | --debu | --deb | --de | --d | -d ) debug=: ;; --file | --fil | --fi | --f ) $ac_shift case $ac_optarg in *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; '') as_fn_error $? "missing file argument" ;; esac as_fn_append CONFIG_FILES " '$ac_optarg'" ac_need_defaults=false;; --header | --heade | --head | --hea ) $ac_shift case $ac_optarg in *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; esac as_fn_append CONFIG_HEADERS " '$ac_optarg'" ac_need_defaults=false;; --he | --h) # Conflict between --help and --header as_fn_error $? "ambiguous option: \`$1' Try \`$0 --help' for more information.";; --help | --hel | -h ) $as_echo "$ac_cs_usage"; exit ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil | --si | --s) ac_cs_silent=: ;; # This is an error. -*) as_fn_error $? "unrecognized option: \`$1' Try \`$0 --help' for more information." ;; *) as_fn_append ac_config_targets " $1" ac_need_defaults=false ;; esac shift done ac_configure_extra_args= if $ac_cs_silent; then exec 6>/dev/null ac_configure_extra_args="$ac_configure_extra_args --silent" fi _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 if \$ac_cs_recheck; then set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion shift \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6 CONFIG_SHELL='$SHELL' export CONFIG_SHELL exec "\$@" fi _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 exec 5>>config.log { echo sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX ## Running $as_me. ## _ASBOX $as_echo "$ac_log" } >&5 _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # Handling of arguments. for ac_config_target in $ac_config_targets do case $ac_config_target in "mod_proxy.h") CONFIG_HEADERS="$CONFIG_HEADERS mod_proxy.h" ;; "t/Makefile") CONFIG_FILES="$CONFIG_FILES t/Makefile" ;; "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; esac done # If the user did not use the arguments to specify the items to instantiate, # then the envvar interface is used. Set only those that are not. # We use the long form for the default assignment because of an extremely # bizarre bug on SunOS 4.1.3. if $ac_need_defaults; then test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files test "${CONFIG_HEADERS+set}" = set || CONFIG_HEADERS=$config_headers fi # Have a temporary directory for convenience. Make it in the build tree # simply because there is no reason against having it here, and in addition, # creating and moving files from /tmp can sometimes cause problems. # Hook for its removal unless debugging. # Note that there is a small window in which the directory will not be cleaned: # after its creation but before its name has been assigned to `$tmp'. $debug || { tmp= ac_tmp= trap 'exit_status=$? : "${ac_tmp:=$tmp}" { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status ' 0 trap 'as_fn_exit 1' 1 2 13 15 } # Create a (secure) tmp directory for tmp files. { tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && test -d "$tmp" } || { tmp=./conf$$-$RANDOM (umask 077 && mkdir "$tmp") } || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5 ac_tmp=$tmp # Set up the scripts for CONFIG_FILES section. # No need to generate them if there are no CONFIG_FILES. # This happens for instance with `./config.status config.h'. if test -n "$CONFIG_FILES"; then ac_cr=`echo X | tr X '\015'` # On cygwin, bash can eat \r inside `` if the user requested igncr. # But we know of no other shell where ac_cr would be empty at this # point, so we can use a bashism as a fallback. if test "x$ac_cr" = x; then eval ac_cr=\$\'\\r\' fi ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' /dev/null` if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then ac_cs_awk_cr='\\r' else ac_cs_awk_cr=$ac_cr fi echo 'BEGIN {' >"$ac_tmp/subs1.awk" && _ACEOF { echo "cat >conf$$subs.awk <<_ACEOF" && echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' && echo "_ACEOF" } >conf$$subs.sh || as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'` ac_delim='%!_!# ' for ac_last_try in false false false false false :; do . ./conf$$subs.sh || as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X` if test $ac_delim_n = $ac_delim_num; then break elif $ac_last_try; then as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 else ac_delim="$ac_delim!$ac_delim _$ac_delim!! " fi done rm -f conf$$subs.sh cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK && _ACEOF sed -n ' h s/^/S["/; s/!.*/"]=/ p g s/^[^!]*!// :repl t repl s/'"$ac_delim"'$// t delim :nl h s/\(.\{148\}\)..*/\1/ t more1 s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/ p n b repl :more1 s/["\\]/\\&/g; s/^/"/; s/$/"\\/ p g s/.\{148\}// t nl :delim h s/\(.\{148\}\)..*/\1/ t more2 s/["\\]/\\&/g; s/^/"/; s/$/"/ p b :more2 s/["\\]/\\&/g; s/^/"/; s/$/"\\/ p g s/.\{148\}// t delim ' >$CONFIG_STATUS || ac_write_fail=1 rm -f conf$$subs.awk cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 _ACAWK cat >>"\$ac_tmp/subs1.awk" <<_ACAWK && for (key in S) S_is_set[key] = 1 FS = "" } { line = $ 0 nfields = split(line, field, "@") substed = 0 len = length(field[1]) for (i = 2; i < nfields; i++) { key = field[i] keylen = length(key) if (S_is_set[key]) { value = S[key] line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3) len += length(value) + length(field[++i]) substed = 1 } else len += 1 + keylen } print line } _ACAWK _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" else cat fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \ || as_fn_error $? "could not setup config files machinery" "$LINENO" 5 _ACEOF # VPATH may cause trouble with some makes, so we remove sole $(srcdir), # ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and # trailing colons and then remove the whole line if VPATH becomes empty # (actually we leave an empty line to preserve line numbers). if test "x$srcdir" = x.; then ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{ h s/// s/^/:/ s/[ ]*$/:/ s/:\$(srcdir):/:/g s/:\${srcdir}:/:/g s/:@srcdir@:/:/g s/^:*// s/:*$// x s/\(=[ ]*\).*/\1/ G s/\n// s/^[^=]*=[ ]*$// }' fi cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 fi # test -n "$CONFIG_FILES" # Set up the scripts for CONFIG_HEADERS section. # No need to generate them if there are no CONFIG_HEADERS. # This happens for instance with `./config.status Makefile'. if test -n "$CONFIG_HEADERS"; then cat >"$ac_tmp/defines.awk" <<\_ACAWK || BEGIN { _ACEOF # Transform confdefs.h into an awk script `defines.awk', embedded as # here-document in config.status, that substitutes the proper values into # config.h.in to produce config.h. # Create a delimiter string that does not exist in confdefs.h, to ease # handling of long lines. ac_delim='%!_!# ' for ac_last_try in false false :; do ac_tt=`sed -n "/$ac_delim/p" confdefs.h` if test -z "$ac_tt"; then break elif $ac_last_try; then as_fn_error $? "could not make $CONFIG_HEADERS" "$LINENO" 5 else ac_delim="$ac_delim!$ac_delim _$ac_delim!! " fi done # For the awk script, D is an array of macro values keyed by name, # likewise P contains macro parameters if any. Preserve backslash # newline sequences. ac_word_re=[_$as_cr_Letters][_$as_cr_alnum]* sed -n ' s/.\{148\}/&'"$ac_delim"'/g t rset :rset s/^[ ]*#[ ]*define[ ][ ]*/ / t def d :def s/\\$// t bsnl s/["\\]/\\&/g s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ D["\1"]=" \3"/p s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2"/p d :bsnl s/["\\]/\\&/g s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ D["\1"]=" \3\\\\\\n"\\/p t cont s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2\\\\\\n"\\/p t cont d :cont n s/.\{148\}/&'"$ac_delim"'/g t clear :clear s/\\$// t bsnlc s/["\\]/\\&/g; s/^/"/; s/$/"/p d :bsnlc s/["\\]/\\&/g; s/^/"/; s/$/\\\\\\n"\\/p b cont ' >$CONFIG_STATUS || ac_write_fail=1 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 for (key in D) D_is_set[key] = 1 FS = "" } /^[\t ]*#[\t ]*(define|undef)[\t ]+$ac_word_re([\t (]|\$)/ { line = \$ 0 split(line, arg, " ") if (arg[1] == "#") { defundef = arg[2] mac1 = arg[3] } else { defundef = substr(arg[1], 2) mac1 = arg[2] } split(mac1, mac2, "(") #) macro = mac2[1] prefix = substr(line, 1, index(line, defundef) - 1) if (D_is_set[macro]) { # Preserve the white space surrounding the "#". print prefix "define", macro P[macro] D[macro] next } else { # Replace #undef with comments. This is necessary, for example, # in the case of _POSIX_SOURCE, which is predefined and required # on some systems where configure will not decide to define it. if (defundef == "undef") { print "/*", prefix defundef, macro, "*/" next } } } { print } _ACAWK _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 as_fn_error $? "could not setup config headers machinery" "$LINENO" 5 fi # test -n "$CONFIG_HEADERS" eval set X " :F $CONFIG_FILES :H $CONFIG_HEADERS " shift for ac_tag do case $ac_tag in :[FHLC]) ac_mode=$ac_tag; continue;; esac case $ac_mode$ac_tag in :[FHL]*:*);; :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;; :[FH]-) ac_tag=-:-;; :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; esac ac_save_IFS=$IFS IFS=: set x $ac_tag IFS=$ac_save_IFS shift ac_file=$1 shift case $ac_mode in :L) ac_source=$1;; :[FH]) ac_file_inputs= for ac_f do case $ac_f in -) ac_f="$ac_tmp/stdin";; *) # Look for the file first in the build tree, then in the source tree # (if the path is not absolute). The absolute path cannot be DOS-style, # because $ac_f cannot contain `:'. test -f "$ac_f" || case $ac_f in [\\/$]*) false;; *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; esac || as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;; esac case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac as_fn_append ac_file_inputs " '$ac_f'" done # Let's still pretend it is `configure' which instantiates (i.e., don't # use $as_me), people would be surprised to read: # /* config.h. Generated by config.status. */ configure_input='Generated from '` $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' `' by configure.' if test x"$ac_file" != x-; then configure_input="$ac_file. $configure_input" { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 $as_echo "$as_me: creating $ac_file" >&6;} fi # Neutralize special characters interpreted by sed in replacement strings. case $configure_input in #( *\&* | *\|* | *\\* ) ac_sed_conf_input=`$as_echo "$configure_input" | sed 's/[\\\\&|]/\\\\&/g'`;; #( *) ac_sed_conf_input=$configure_input;; esac case $ac_tag in *:-:* | *:-) cat >"$ac_tmp/stdin" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; esac ;; esac ac_dir=`$as_dirname -- "$ac_file" || $as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$ac_file" : 'X\(//\)[^/]' \| \ X"$ac_file" : 'X\(//\)$' \| \ X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || $as_echo X"$ac_file" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` as_dir="$ac_dir"; as_fn_mkdir_p ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; esac ;; esac ac_abs_top_builddir=$ac_pwd ac_abs_builddir=$ac_pwd$ac_dir_suffix # for backward compatibility: ac_top_builddir=$ac_top_build_prefix case $srcdir in .) # We are building in place. ac_srcdir=. ac_top_srcdir=$ac_top_builddir_sub ac_abs_top_srcdir=$ac_pwd ;; [\\/]* | ?:[\\/]* ) # Absolute name. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ac_abs_top_srcdir=$srcdir ;; *) # Relative name. ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_build_prefix$srcdir ac_abs_top_srcdir=$ac_pwd/$srcdir ;; esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix case $ac_mode in :F) # # CONFIG_FILE # _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # If the template does not know about datarootdir, expand it. # FIXME: This hack should be removed a few years after 2.60. ac_datarootdir_hack=; ac_datarootdir_seen= ac_sed_dataroot=' /datarootdir/ { p q } /@datadir@/p /@docdir@/p /@infodir@/p /@localedir@/p /@mandir@/p' case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in *datarootdir*) ac_datarootdir_seen=yes;; *@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 $as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_datarootdir_hack=' s&@datadir@&$datadir&g s&@docdir@&$docdir&g s&@infodir@&$infodir&g s&@localedir@&$localedir&g s&@mandir@&$mandir&g s&\\\${datarootdir}&$datarootdir&g' ;; esac _ACEOF # Neutralize VPATH when `$srcdir' = `.'. # Shell code in configure.ac might set extrasub. # FIXME: do we really want to maintain this feature? cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_sed_extra="$ac_vpsub $extrasub _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 :t /@[a-zA-Z_][a-zA-Z_0-9]*@/!b s|@configure_input@|$ac_sed_conf_input|;t t s&@top_builddir@&$ac_top_builddir_sub&;t t s&@top_build_prefix@&$ac_top_build_prefix&;t t s&@srcdir@&$ac_srcdir&;t t s&@abs_srcdir@&$ac_abs_srcdir&;t t s&@top_srcdir@&$ac_top_srcdir&;t t s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t s&@builddir@&$ac_builddir&;t t s&@abs_builddir@&$ac_abs_builddir&;t t s&@abs_top_builddir@&$ac_abs_top_builddir&;t t $ac_datarootdir_hack " eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \ >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5 test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ "$ac_tmp/out"`; test -z "$ac_out"; } && { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined" >&5 $as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined" >&2;} rm -f "$ac_tmp/stdin" case $ac_file in -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";; *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";; esac \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; :H) # # CONFIG_HEADER # if test x"$ac_file" != x-; then { $as_echo "/* $configure_input */" \ && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" } >"$ac_tmp/config.h" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 if diff "$ac_file" "$ac_tmp/config.h" >/dev/null 2>&1; then { $as_echo "$as_me:${as_lineno-$LINENO}: $ac_file is unchanged" >&5 $as_echo "$as_me: $ac_file is unchanged" >&6;} else rm -f "$ac_file" mv "$ac_tmp/config.h" "$ac_file" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 fi else $as_echo "/* $configure_input */" \ && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" \ || as_fn_error $? "could not create -" "$LINENO" 5 fi ;; esac done # for ac_tag as_fn_exit 0 _ACEOF ac_clean_files=$ac_clean_files_save test $ac_write_fail = 0 || as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5 # configure is writing to config.log, and then calls config.status. # config.status does its own redirection, appending to config.log. # Unfortunately, on DOS this fails, as config.log is still kept open # by configure, so config.status won't be able to write to it; its # output is simply discarded. So we exec the FD to /dev/null, # effectively closing config.log, so it can be properly (re)opened and # appended to by config.status. When coming back to configure, we # need to make the FD available again. if test "$no_create" != yes; then ac_cs_success=: ac_config_status_args= test "$silent" = yes && ac_config_status_args="$ac_config_status_args --quiet" exec 5>/dev/null $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false exec 5>>config.log # Use ||, not &&, to avoid exiting from the if with $? = 1, which # would make configure fail if this is the last instruction. $ac_cs_success || as_fn_exit 1 fi if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} fi proftpd-mod_proxy-0.9.5/configure.in000066400000000000000000000266611475737016700175660ustar00rootroot00000000000000dnl ProFTPD - mod_proxy dnl Copyright (c) 2012-2022 TJ Saunders dnl dnl This program is free software; you can redistribute it and/or modify dnl it under the terms of the GNU General Public License as published by dnl the Free Software Foundation; either version 2 of the License, or dnl (at your option) any later version. dnl dnl This program is distributed in the hope that it will be useful, dnl but WITHOUT ANY WARRANTY; without even the implied warranty of dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the dnl GNU General Public License for more details. dnl dnl You should have received a copy of the GNU General Public License dnl along with this program; if not, write to the Free Software dnl Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. dnl dnl Process this file with autoconf to produce a configure script. AC_INIT(./mod_proxy.c) AC_CANONICAL_SYSTEM ostype=`echo $build_os | sed 's/\..*$//g' | sed 's/-.*//g' | tr abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ` AC_PROG_CC AC_PROG_CPP AC_AIX AC_ISC_POSIX AC_MINIX AC_PROG_MAKE_SET dnl Need to support/handle the --with-includes, --with-libraries, --enable-tests dnl options AC_ARG_WITH(includes, [AC_HELP_STRING( [--with-includes=LIST], [add additional include paths to proftpd. LIST is a colon-separated list of include paths to add e.g. --with-includes=/some/mysql/include:/my/include]) ], [ ac_addl_includes=`echo "$withval" | sed -e 's/:/ /g'` ; for ainclude in $ac_addl_includes; do if test x"$ac_build_addl_includes" = x ; then ac_build_addl_includes="-I$ainclude" else ac_build_addl_includes="-I$ainclude $ac_build_addl_includes" fi done CPPFLAGS="$CPPFLAGS $ac_build_addl_includes" ]) AC_ARG_WITH(libraries, [AC_HELP_STRING( [--with-libraries=LIST], [add additional library paths to proftpd. LIST is a colon-separated list of include paths to add e.g. --with-libraries=/some/mysql/libdir:/my/libs]) ], [ ac_addl_libdirs=`echo "$withval" | sed -e 's/:/ /g'` ; for alibdir in $ac_addl_libdirs; do if test x"$ac_build_addl_libdirs" = x ; then ac_build_addl_libdirs="-L$alibdir" else ac_build_addl_libdirs="-L$alibdir $ac_build_addl_libdirs" fi done LDFLAGS="$LDFLAGS $ac_build_addl_libdirs" ]) ENABLE_TESTS="\"\"" AC_ARG_ENABLE(tests, [AC_HELP_STRING( [--enable-tests], [enable unit tests (default=no)]) ], [ if test x"$enableval" != xno ; then AC_CHECK_HEADERS(check.h) AC_CHECK_LIB(check, tcase_create, [AC_DEFINE(HAVE_LIBCHECK, 1, [Define if libcheck is present.]) ENABLE_TESTS="1" ], [ AC_MSG_ERROR([libcheck support, required for tests, not present -- aborting]) ] ) fi ]) AC_HEADER_STDC AC_CHECK_HEADERS(sqlite3.h stdlib.h unistd.h limits.h fcntl.h sys/sysctl.h sys/sysinfo.h) AC_CHECK_HEADER(zlib.h, [AC_DEFINE(HAVE_ZLIB_H, 1, [Define if zlib.h is present.]) MODULE_LIBS="$MODULE_LIBS -lz" ]) AC_CHECK_FUNCS(random srandom strnstr sysctl sysinfo) dnl Check whether libc provides the DNS resolver symbols (e.g. *BSD/Mac OSX) dnl or not. And if not, check whether we need to link directly with dnl /usr/lib/libresolv.a (32-bit) or /usr/lib64/libresolv.a (64-bit). dnl dnl Ideally we would link with libresolv using -lresolv. However, it seems dnl that many Linux distributions shipped a broken version of libresolv.so dnl which did not export the necessary ns_initparse/ns_parserr symbols. The dnl static version of libresolv shipped DOES provide those symbols (probably dnl for use by libc). For these cases, we link againt the static library. ac_dns_libs="" AC_MSG_CHECKING([for resolver symbols in libc]) AC_TRY_LINK( [ #include #include #include #include ], [ int res; res = res_query(NULL, ns_c_in, ns_t_txt, NULL, 0); res = ns_initparse(NULL, 0, NULL); res = ns_parserr(NULL, ns_s_an, 0, NULL); ], [ AC_MSG_RESULT(yes) ], [ AC_MSG_RESULT(no) AC_MSG_CHECKING([for resolver symbols in libresolv]) saved_libs="$LIBS" LIBS="$LIBS -lresolv" AC_TRY_LINK( [ #include #include #include #include ], [ int res; res = res_query(NULL, ns_c_in, ns_t_txt, NULL, 0); res = ns_initparse(NULL, 0, NULL); res = ns_parserr(NULL, ns_s_an, 0, NULL); ], [ AC_MSG_RESULT(yes) LIBS="$saved_libs" ac_dns_libs="-lresolv" ], [ AC_MSG_RESULT(no) LIBS="$saved_libs" AC_MSG_CHECKING([for resolver symbols in /usr/lib64/libresolv.a]) saved_libs="$LIBS" LIBS="$LIBS /usr/lib64/libresolv.a" AC_TRY_LINK( [ #include #include #include #include ], [ int res; res = res_query(NULL, ns_c_in, ns_t_txt, NULL, 0); res = ns_initparse(NULL, 0, NULL); res = ns_parserr(NULL, ns_s_an, 0, NULL); ], [ AC_MSG_RESULT(yes) LIBS="$saved_libs" ac_dns_libs="/usr/lib64/libresolv.a" ], [ AC_MSG_RESULT(no) LIBS="$saved_libs" AC_MSG_CHECKING([for resolver symbols in /usr/lib/libresolv.a]) saved_libs="$LIBS" LIBS="$LIBS /usr/lib/libresolv.a" AC_TRY_LINK( [ #include #include #include #include ], [ int res; res = res_query(NULL, ns_c_in, ns_t_txt, NULL, 0); res = ns_initparse(NULL, 0, NULL); res = ns_parserr(NULL, ns_s_an, 0, NULL); ], [ AC_MSG_RESULT(yes) LIBS="$saved_libs" ac_dns_libs="/usr/lib/libresolv.a" ], [ AC_MSG_RESULT(no) LIBS="$saved_libs" ] ) ] ) ] ) ] ) # Check for OpenSSL-isms AC_MSG_CHECKING([whether OpenSSL has crippled AES support]) LIBS="-lcrypto $LIBS" AC_TRY_LINK( [ #ifdef HAVE_STRING_H # include #endif #include ], [ EVP_CIPHER *c; c = EVP_aes_192_cbc(); c = EVP_aes_256_cbc(); ], [ AC_MSG_RESULT(no) LIBS="$saved_libs" ], [ AC_MSG_RESULT(yes) AC_DEFINE(HAVE_AES_CRIPPLED_OPENSSL, 1, [OpenSSL is missing AES192 and AES256 support]) LIBS="$saved_libs" ] ) AC_MSG_CHECKING([whether OpenSSL supports SHA256]) LIBS="-lcrypto $LIBS" AC_TRY_LINK( [ #include ], [ const EVP_MD *md; md = EVP_sha256(); ], [ AC_MSG_RESULT(yes) AC_DEFINE(HAVE_SHA256_OPENSSL, 1, [OpenSSL supports SHA224/SHA256]) LIBS="$saved_libs" ], [ AC_MSG_RESULT(no) LIBS="$saved_libs" ] ) AC_MSG_CHECKING([whether OpenSSL supports SHA512]) LIBS="-lcrypto $LIBS" AC_TRY_LINK( [ #include ], [ const EVP_MD *md; md = EVP_sha512(); ], [ AC_MSG_RESULT(yes) AC_DEFINE(HAVE_SHA512_OPENSSL, 1, [OpenSSL supports SHA384/SHA512]) LIBS="$saved_libs" ], [ AC_MSG_RESULT(no) LIBS="$saved_libs" ] ) AC_MSG_CHECKING([whether OpenSSL supports EVP_aes_128_ctr]) LIBS="-lcrypto $LIBS" AC_TRY_LINK( [ #include ], [ EVP_CIPHER *cipher; cipher = EVP_aes_128_ctr(); ], [ AC_MSG_RESULT(yes) AC_DEFINE(HAVE_EVP_AES_128_CTR_OPENSSL, 1, [OpenSSL supports EVP_aes_128_ctr]) LIBS="$saved_libs" ], [ AC_MSG_RESULT(no) LIBS="$saved_libs" ] ) AC_MSG_CHECKING([whether OpenSSL supports EVP_aes_192_ctr]) LIBS="-lcrypto $LIBS" AC_TRY_LINK( [ #include ], [ EVP_CIPHER *cipher; cipher = EVP_aes_192_ctr(); ], [ AC_MSG_RESULT(yes) AC_DEFINE(HAVE_EVP_AES_192_CTR_OPENSSL, 1, [OpenSSL supports EVP_aes_192_ctr]) LIBS="$saved_libs" ], [ AC_MSG_RESULT(no) LIBS="$saved_libs" ] ) AC_MSG_CHECKING([whether OpenSSL supports EVP_aes_256_ctr]) LIBS="-lcrypto $LIBS" AC_TRY_LINK( [ #include ], [ EVP_CIPHER *cipher; cipher = EVP_aes_256_ctr(); ], [ AC_MSG_RESULT(yes) AC_DEFINE(HAVE_EVP_AES_256_CTR_OPENSSL, 1, [OpenSSL supports EVP_aes_256_ctr]) LIBS="$saved_libs" ], [ AC_MSG_RESULT(no) LIBS="$saved_libs" ] ) AC_MSG_CHECKING([whether OpenSSL supports EVP_aes_256_gcm]) LIBS="-lcrypto $LIBS" AC_TRY_LINK( [ #include ], [ EVP_CIPHER *cipher; cipher = EVP_aes_256_gcm(); ], [ AC_MSG_RESULT(yes) AC_DEFINE(HAVE_EVP_AES_256_GCM_OPENSSL, 1, [OpenSSL supports EVP_aes_256_gcm]) LIBS="$saved_libs" ], [ AC_MSG_RESULT(no) LIBS="$saved_libs" ] ) AC_MSG_CHECKING([whether OpenSSL supports X448 algorithm]) LIBS="-lcrypto $LIBS" AC_TRY_LINK( [ #include ], [ EVP_PKEY *pkey; EVP_PKEY_CTX *pctx; pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_X448, NULL, NULL, 0); pctx = EVP_PKEY_CTX_new_id(NID_X448, NULL); ], [ AC_MSG_RESULT(yes) AC_DEFINE(HAVE_X448_OPENSSL, 1, [OpenSSL supports X448 algorithm]) LIBS="$saved_libs" ], [ AC_MSG_RESULT(no) LIBS="$saved_libs" ] ) AC_MSG_CHECKING([whether OpenSSL supports OSSL_PROVIDER_load]) LIBS="-lcrypto $LIBS" AC_TRY_LINK( [ #include ], [ OSSL_PROVIDER *provider; provider = OSSL_PROVIDER_load(NULL, "default"); ], [ AC_MSG_RESULT(yes) AC_DEFINE(HAVE_OSSL_PROVIDER_LOAD_OPENSSL, 1, [OpenSSL supports OSSL_PROVIDER_load]) LIBS="$saved_libs" ], [ AC_MSG_RESULT(no) LIBS="$saved_libs" ] ) # Check for SQLite-isms AC_MSG_CHECKING([for sqlite3_stmt_readonly]) saved_libs="$LIBS" LIBS="-lsqlite3" AC_TRY_LINK([ #include #include #ifdef HAVE_SQLITE3_H # include #endif ], [ (void) sqlite3_stmt_readonly(NULL); ], [ AC_MSG_RESULT(yes) AC_DEFINE(HAVE_SQLITE3_STMT_READONLY, 1, [Define if you have the sqlite3_stmt_readonly function]) ], [ AC_MSG_RESULT(no) ] ) LIBS="$saved_libs" AC_MSG_CHECKING([for sqlite3_trace]) saved_libs="$LIBS" LIBS="-lsqlite3" AC_TRY_LINK([ #include #include #ifdef HAVE_SQLITE3_H # include #endif ], [ (void) sqlite3_trace(NULL, NULL, NULL); ], [ AC_MSG_RESULT(yes) AC_DEFINE(HAVE_SQLITE3_TRACE, 1, [Define if you have the sqlite3_trace function]) ], [ AC_MSG_RESULT(no) ] ) LIBS="$saved_libs" AC_MSG_CHECKING([for sqlite3_trace_v2]) saved_libs="$LIBS" LIBS="-lsqlite3" AC_TRY_LINK([ #include #include #ifdef HAVE_SQLITE3_H # include #endif ], [ (void) sqlite3_trace_v2(NULL, 0, NULL, NULL); ], [ AC_MSG_RESULT(yes) AC_DEFINE(HAVE_SQLITE3_TRACE_V2, 1, [Define if you have the sqlite3_trace_v2 function]) ], [ AC_MSG_RESULT(no) ] ) LIBS="$saved_libs" INCLUDES="$ac_build_addl_includes" LIBDIRS="$ac_build_addl_libdirs" MODULE_LIBS="$MODULE_LIBS $ac_dns_libs" AC_SUBST(ENABLE_TESTS) AC_SUBST(INCLUDES) AC_SUBST(LDFLAGS) AC_SUBST(LIBDIRS) AC_SUBST(MODULE_LIBS) AC_CONFIG_HEADER(mod_proxy.h) AC_OUTPUT( t/Makefile Makefile ) proftpd-mod_proxy-0.9.5/doc/000077500000000000000000000000001475737016700160075ustar00rootroot00000000000000proftpd-mod_proxy-0.9.5/doc/NOTES000066400000000000000000000150601475737016700166240ustar00rootroot00000000000000 Benefits: Front servers which can't do: PASV, EPRT/EPSV, FTPS Can use to do your logging: TransferLog, ExtendedLog, mod_log_zmq, etc. Can use to do your monitoring: mod_snmp, etc Single SSL cert (on the proxy) for several different backend servers Have mod_netsieve here, for sieving the inbound commands and outbound files (have netsieve rules for common cases like scanning for SSNs/credit card numbers in outbound data) What would a proxy module for proftpd look like? client <-------> mod_proxy <--------> FTP server So we'd be proxying FTP connections to other FTP servers, including data transfers. Would we support both forward and reverse proxy configurations? * Set DefaultServer on in the section using mod_proxy (or don't set it all). That way, on a proxy server, only connections to vhosts configured for proxying are handled; others are rejected. Forward Proxy Need some kind of mechanism that clients can use to specify their end targets. Reverse Proxy Terminates any SSL session (backend connection to use SSL or not?) What credentials to use for authenticating to backend server for a reverse proxy connection? Modifying of directory listing results as necessary How to map clients to origin servers? By user name, by client IP, or...? Re-use proxy USER/PASS with origin server, or override with shared/common origin server USER/PASS? Round-robin, consistent hashing, load balance? *Do it based on USER: that's the one feature that mod_proxy can do (i.e. it's protocol-aware); the other implementations are TCP-specific, and can be done by nginx, haproxy, etc. If map/lookup is done based on client IP, then it can be done at connect time, and thus mod_proxy can relay the connection to the remote host, and let all authentication be handled by the remote/target host. Scenarios: Complex: backend server selection based on USER; this means mod_proxy needs to handle login, _then_ select backend and create control connection to selected backend. *NB: Actually, the selection of the backend user *can* happen based on just the USER command, no PASS; this assumes that the auth will either succeed using the given USER, or it won't (and the connection will be closed). This distinction means that the proxy does not necessarily have to handle authentication itself. Balancing (or: selection of backend server) ProxyReverseConnect roundrobin [options] hashing (?) [options] userbased [options] ProxyReverseServers ftp://localhost:20741 ftp://localhost:20743 ProxyReverseServers file://path/to/servers.txt sql://SQLNamedQuery/... ldap://...? HAproxy PROXY protocol http://haproxy.1wt.eu/download/1.5/doc/proxy-protocol.txt http://ben.timby.com/?page_id=210 Implemented in mod_proxy_protocol. Any caching of proxied results, files, directory transfers, etc? Use of DNAT/SNAT, so the remote/target server sees real IP of connecting client, rather than that of the proxy? Or how else to inform the backend/origin of the connecting client info? Keep in mind this possibility: client -> proxy -> proxy -> proxy -> proxy -> server FTP to SFTP proxying: http://superuser.com/questions/422348/ftp-to-sftp-scp-proxy for securing outbound connections (in a forward proxy mode), as well as SFTP to FTP proxying (e.g. for SFTP for external clients, FTP for internal/legacy servers) for reverse proxying. * Use URIs for specifying protocol proxying; means needing a URI parser good enough to extract scheme, host, port, and perhaps authority. Health checks TCP Data Transfers: MODE Z on frontend, backend? Load balancing Failover Timeouts ProxyConnectTimeout Reverse connectivity feature? (Requires that the backend server know to connect to proxy for "listening" connection, so custom work.) HTTP CONNECT Client sends CONNECT to proxy, then proceeds to do an _FTP_ control channel through the established TCP connection. * Can this be used to proxy/hide the origin of an SSH connection as well? I think so... client --> (sftp) --> proxy --> (ftp) --> origin client --> (ftp) --> proxy --> (sftp) --> origin Other Proxy Implementations http://www.mcknight.de/jftpgw/features.html http://aggemam.dk/ftpproxy Forward FTP proxy, written in Java http://www.glub.com/ Secure FTP Wrapper; see their "Login" article http://frox.sourceforge.net/ http://www.ftpproxy.org/ http://freecode.com/projects/suseproxy-suite http://www.linuxjournal.com/magazine/configuring-and-using-ftp-proxy httpd.apache.org/docs/2.2/mod/mod_proxy.html http://forums.devshed.com/ftp-help-113/apache-ftp-reverse-proxy-78960.html http://www.apachelounge.com/viewtopic.php?t=3677 http://www.faqs.org/rfcs/rfc1919.html ("Classical Vs Transparent IP Proxies") http://www.faqs.org/rfcs/rfc1579.html ("Firewall-Friendly FTP") https://calomel.org/ftp_proxy.html ftp-proxy started off as pftpx? https://calomel.org/ http://www.codeproject.com/KB/IP/ProxyFtp.aspx http://www.chilkatsoft.com/refdoc/xChilkatFtp2Ref.html See ProxyMethod for various FTP proxy approaches http://linux.die.net/man/1/lftp ftp:proxy ftp:proxy-auth-type http://www.apsis.ch/pound/index_html http://haproxy.1wt.eu/ http://www.loadbalancer.org/ http://www.tomkleinpeter.com/2008/03/17/programmers-toolbox-part-3-consistent-hashing/ http://cr.yp.to/ftpparse.html Use for parsing FTP lists into SFTP dirlist What about MLSD, though? Need to find parser for that, if avail http://software.clapper.org/grizzled-python/epydoc/grizzled.net.ftp.parse.FTPMlstDataParser-class.html Links/Articles: http://ben.timby.com/?page_id=210 http://www.taiter.com/techlog/2012/09/ftp-load-balanced-through-haproxy.html FTP load balancing through HAproxy http://blog.cryptographyengineering.com/2012/03/how-do-interception-proxies-fail.html ftpcluster: http://www.awk-scripting.de/cluster/ Uses different cluster server addresses in PORT/PASV, and FXP among cluster nodes. Interesting idea. Goals Contrast with Squid, Apache mod_proxy, others Testing Net::FTP in Perl; use Firewall, FirewallType constructor args. See Net::Config perldocs (quite interesting, actually). Net::SOCKS IO::Socket::SOCKS IO::Socket::SecureSocks URI::Socks Net::Proxy::Type Need handle cases like: client --> proxy <--> SOCKS/HTTP proxy <--> server i.e. where the proxy needs to be able to tunnel its connections through SOCKS/HTTP proxies. proftpd-mod_proxy-0.9.5/doc/NOTES.dirlist-parsing000066400000000000000000000013761475737016700217430ustar00rootroot00000000000000 At some point, especially for protocol conversions, mod_proxy may need to parse the directory listing from the backend server to give to the frontend client. Thus we'll need some dirlist-parsing code. Here's a fun one, from the lftp changes: fixed MLSD parsing for semicolons in file names. See: http://lftp.yar.ru/news.html Maybe lftp has MLSD parsing code to reuse? It does indeed; see lftp-N.N.N/src/FtpListInfo.{h,cc}. Purportedly parses Unix, MLSD, OS/2, NT, AS400, and EPLF. Could write proxy module that does this. Note that FtpListInfo iterates through list of parsers to find the one which handles the current format; do same, but remember the parser which worked, per session/connection, so that such iteration isn't necessary per-dirlist. proftpd-mod_proxy-0.9.5/doc/NOTES.dns-srv000066400000000000000000000267231475737016700202270ustar00rootroot00000000000000 Use Docker Compose and: proftpd+mod_proxy proftpd/pure-ftpd dnsmasq Implementation: ProxyDNSOptions UseSRV (or +SRV) UseTXT (or +TXT) * allows room for later -A, -AAA OR, even better: ftp+srv://server.example.com ftps+srv://server.example.com DNS SRV query: _ftp._tcp.server.example.com Same for TXT records? ftp+txt://server.example.com ftps+txt://server.example.com See: https://docs.mongodb.com/manual/reference/connection-string/#connections-dns-seedlist These would require changes to the URI parsing, Conn API, for "hints"? NOTE: Port numbers are NOT allowed in the URL if the SRV scheme is used! Why not? Because port numbers are returned in the SRV records themselves; avoid any possible collisions/conflicts. Besides, mutiple SRV records for the same service name might have different ports. Similarly for TXT scheme; the port will be part of the URLs found in the TXT records. If ports ARE found in such URLs, they will be (logged and) ignored. NOTE: TXT records found to have URLs must NOT use the +txt, +srv schemes. lib/proxy/dns.c typedef enum { PROXY_DNS_SRV, PROXY_DNS_TXT } proxy_dns_typ_e; int proxy_dns_resolve(pool *p, const char *name, proxy_dns_type_e dns_type, array_header **addrs); NOTE: Check that the A, AAAA records for SRV targets MATCH the initial name's domain. If there is a domain mismatch, do we reject/skip that target? Answer: For now, no. Accept the given target names. NOTE: Watch/honor the TTLs on the retrieved records. A given SRV record will have its own TTL; the target resource records (included, or retrieved separately) will have their own TTLs (often shorter). Even if we go with the shortest TTL, and set a timer, that timer will be specific to that pconn object. In order to honor the TTLs, we'd need to schedule timers -- but only in the daemon/master process, NOT session/child processes. (Thus we'd need to remove these re-resolve timers on sess_init.) We would need to track the timer ID in the pconn struct; the timer callback would take that pconn as an argument, to update the addr, port, other addrs. And what about the memory pool to use for re-resolves, considering a long-lived daemon process? If resolution fails in timer, leave existing pconn (and timer) as is. These TTL timers ONLY need to be there for URLs parsed at start-up time, by the daemon/master process. This means we don't need it for URLs obtained from SQL databases, Redis servers, per-user/group, etc. Test: SRV records: one none per RFC 2782, if none, fallback to A record. multiple multiple weighted/prioritized * when to query/look at RRs in "additional data" section? ns_s_ar (vs ns_s_an)? Answer: Target The domain name of the target host. There MUST be one or more address records for this name, the name MUST NOT be an alias (in the sense of RFC 1034 or RFC 2181). Implementors are urged, but not required, to return the address record(s) in the Additional Data section. Unless and until permitted by future standards action, name compression is not to be used for this field. A Target of "." means that the service is decidedly not available at this domain. Need an example for testing? $ dig _imaps._tcp.gmail.com SRV ; <<>> DiG 9.8.3-P1 <<>> _imaps._tcp.gmail.com SRV ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 17898 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 4 ;; QUESTION SECTION: ;_imaps._tcp.gmail.com. IN SRV ;; ANSWER SECTION: _imaps._tcp.gmail.com. 86400 IN SRV 5 0 993 imap.gmail.com. ;; ADDITIONAL SECTION: imap.gmail.com. 96 IN A 74.125.20.108 imap.gmail.com. 96 IN A 74.125.20.109 imap.gmail.com. 96 IN AAAA 2607:f8b0:400e:c08::6d imap.gmail.com. 96 IN AAAA 2607:f8b0:400e:c08::6c Note that `dig gmail.com ANY` does NOT return the SRV records; you have to query for them specifically. This also means that the name to be resolved must be specifically constructed for SRV lookup! ftp+srv://example.com -> _ftp._tcp.example.com ftps+srv://example.com -> _ftp._tcp.example.com Fun -- watch what happens for different nameservers: # Here, we get the add'l records... $ dig _imaps._tcp.gmail.com SRV @75.75.75.75 ; <<>> DiG 9.8.3-P1 <<>> _imaps._tcp.gmail.com SRV @75.75.75.75 ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 9937 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 4 ;; QUESTION SECTION: ;_imaps._tcp.gmail.com. IN SRV ;; ANSWER SECTION: _imaps._tcp.gmail.com. 86400 IN SRV 5 0 993 imap.gmail.com. ;; ADDITIONAL SECTION: imap.gmail.com. 79 IN A 74.125.195.108 imap.gmail.com. 79 IN A 74.125.195.109 imap.gmail.com. 79 IN AAAA 2607:f8b0:400e:c04::6d imap.gmail.com. 79 IN AAAA 2607:f8b0:400e:c04::6c ;; Query time: 32 msec ;; SERVER: 75.75.75.75#53(75.75.75.75) ;; WHEN: Sat Nov 14 20:13:05 2020 ;; MSG SIZE rcvd: 161 # Here, we do NOT get the add'l records. Something to handle. $ dig _imaps._tcp.gmail.com SRV @8.8.8.8 ; <<>> DiG 9.8.3-P1 <<>> _imaps._tcp.gmail.com SRV @8.8.8.8 ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 24242 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;_imaps._tcp.gmail.com. IN SRV ;; ANSWER SECTION: _imaps._tcp.gmail.com. 21599 IN SRV 5 0 993 imap.gmail.com. ;; Query time: 30 msec ;; SERVER: 8.8.8.8#53(8.8.8.8) ;; WHEN: Sat Nov 14 20:13:59 2020 ;; MSG SIZE rcvd: 73 Means that some resolvers might helpfully fill in the add'l records. Need to find (or configure) multiple SRV records, pointing at different targets; what would the add'l section look like for that? Here's an example where the target does NOT match the domain: $ dig _sipfederationtls._tcp.outlook.com SRV ; <<>> DiG 9.8.3-P1 <<>> _sipfederationtls._tcp.outlook.com SRV ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 7910 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;_sipfederationtls._tcp.outlook.com. IN SRV ;; ANSWER SECTION: _sipfederationtls._tcp.outlook.com. 300 IN SRV 10 2 5061 federation.messenger.msn.com. ;; Query time: 38 msec ;; SERVER: 75.75.75.75#53(75.75.75.75) ;; WHEN: Sat Nov 14 20:20:46 2020 ;; MSG SIZE rcvd: 100 Another fun one -- note the LACK of add'l data this time! * found here: https://stackoverflow.com/questions/10138844/java-dns-lookup-for-srv-records $ dig _nicname._tcp.uk SRV ; <<>> DiG 9.8.3-P1 <<>> _nicname._tcp.uk SRV ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 49671 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;_nicname._tcp.uk. IN SRV ;; ANSWER SECTION: _nicname._tcp.uk. 172800 IN SRV 0 0 43 whois.nic.uk. ;; Query time: 42 msec ;; SERVER: 75.75.75.75#53(75.75.75.75) ;; WHEN: Sat Nov 14 20:41:50 2020 ;; MSG SIZE rcvd: 66 And here: $ dig _ldap._tcp.ru.ac.za SRV ; <<>> DiG 9.8.3-P1 <<>> _ldap._tcp.ru.ac.za SRV ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 56234 ;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;_ldap._tcp.ru.ac.za. IN SRV ;; ANSWER SECTION: _ldap._tcp.ru.ac.za. 21600 IN SRV 1 0 389 bushbaby.ru.ac.za. _ldap._tcp.ru.ac.za. 21600 IN SRV 2 0 389 jackal.ru.ac.za. _ldap._tcp.ru.ac.za. 21600 IN SRV 2 0 389 gecko.ru.ac.za. ;; Query time: 167 msec ;; SERVER: 75.75.75.75#53(75.75.75.75) ;; WHEN: Sun Nov 15 09:25:27 2020 ;; MSG SIZE rcvd: 143 What about sftp? What will the published service name be for that? ssh, or sftp? https://github.com/Crosse/sshsrv TXT records: one none multiple non-FTP URLs CLI: # For host, dig commands $ apt-get install -y bind9-host dnsutils $ dig _ftp._tcp.castaglia.local SRV ... ;; ANSWER SECTION: _ftp._tcp.castaglia.local. 0 IN SRV 3 0 2121 proxy.castaglia.org. $ dig castaglia.local TXT ... ;; ANSWER SECTION: castaglia.local. 0 IN TXT "foo bar baz" References: https://gist.github.com/ajdavis/e5f5ddbf50b5aecdc5e1d686d72a8a7a https://stackoverflow.com/questions/58845991/make-the-dns-server-of-docker-container-another-docker-container-running-dnsmasq https://alejandrocelaya.blog/2017/04/21/set-specific-ip-addresses-to-docker-containers-created-with-docker-compose/ https://oliver-kaestner.de/english-c-query-srv-dns-record-with-example/ https://tools.ietf.org/html/rfc2782 (DNS SRV) https://people.samba.org/bzr/jerry/slag/unix/query-srv.c https://docs.mongodb.com/manual/reference/connection-string/ https://jdebp.eu/FGA/dns-srv-record-use-by-clients.html http://dns.vanrein.org/srv/tools/ https://github.com/lavv17/lftp/blob/master/src/Resolver.cc https://github.com/systemmonkey42/libsrv/blob/master/src/libsrv.c https://tools.ietf.org/html/draft-andrews-http-srv-01 https://tools.ietf.org/html/draft-jennings-http-srv-05 Future: For A records: https://github.com/haproxy/haproxy/blob/master/src/dns.c#L971 For AAAA records: https://github.com/haproxy/haproxy/blob/master/src/dns.c#L1032 For CNAME records: https://github.com/haproxy/haproxy/blob/master/src/dns.c#L981 Tidbits: This is already handled automagically by res_query(3), but for reference: static int dns_check_response(ns_msg *msgh, const char *query_type) { int flag, res; flag = ns_msg_getflag(*msgh, ns_f_rcode); switch (flag) { case ns_r_noerror: res = 0; break; case ns_r_formerr: pr_trace_msg(trace_channel, 7, "received 'Format error' response code (%d) in %s answer", flag, query_type); errno = EINVAL; res = -1; break; case ns_r_servfail: pr_trace_msg(trace_channel, 7, "received 'Server failure' response code (%d) in %s answer", flag, query_type); errno = EPERM; res = -1; break; case ns_r_nxdomain: pr_trace_msg(trace_channel, 7, "received 'No such domain' response code (%d) in %s answer", flag, query_type); errno = ENOENT; res = -1; break; case ns_r_notimpl: pr_trace_msg(trace_channel, 7, "received 'Unimplemented' response code (%d) in %s answer", flag, query_type); errno = EPERM; res = -1; break; case ns_r_refused: pr_trace_msg(trace_channel, 7, "received 'Operation refused' response code (%d) in %s answer", flag, query_type); errno = EPERM; res = -1; break; default: pr_trace_msg(trace_channel, 7, "received unknown response code (%d) in %s answer", flag, query_type); errno = EPERM; res = -1; } if (res < 0) { return -1; } if (pr_trace_get_level(trace_channel) <= 7) { return res; } /* Log any other flags of interest. * * If the response was truncated, the libc resolver's default behavior is * to retry the query over TCP. Given that, the main flag of interest is * whether this answer was authoritative (from the authoritative nameserver) * or from some cache along the way. */ flag = ns_msg_getflag(*msgh, ns_f_aa); if (flag) { pr_trace_msg(trace_channel, 9, "received AUTHORITATIVE answer for %s query", query_type); } else { pr_trace_msg(trace_channel, 19, "received cached answer for %s query", query_type); } return res; } proftpd-mod_proxy-0.9.5/doc/NOTES.forward-proxy000066400000000000000000000052551475737016700214530ustar00rootroot00000000000000 Supported forward proxy method: USER user@dest-server[:port] PASS pass Chilkatsoft.com ChilkatFtp2.ProxyMethod = 2 Net::Config: ftp_firewall_type 1 USER user@remote.host[:port] PASS pass Complications: '@' symbol in username? Avoidable if the USER argument is parsed from end-to-beginning What if no '@' symbol appears in USER argument? What if single '@' symbol appears in USER argument, as part of actual username? What if USER is not first command? I.e. what if we see FEAT, SYST, etc? What if USER specifies a host that returns 5xx in the banner? Should the client be allowed another USER? If so, does that count against MaxLoginAttempts? IPv6 dest server? Support "USER user@[::1]:port". Easy enough. FTPS? If USER command is received over SSL/TLS, then connection to dest server will use SSL/TLS. Configurable? Connect, then send FEAT. If "AUTH SSL" or "AUTH TLS" appears in FEAT, AND mod_proxy+mod_tls is available, then automatically try to do FTPS. No "implicit" FTPS support? Connection to dest server opened later Benefits: use mod_proxy in forward mode to add "IPv6" capability, i.e. talk to IPv6 server, for IPv4-only client. use mod_proxy in forward mode to add SSL/TLS capability, i.e. to talk to FTPS server, for non-SSL/TLS clients? Supporting this means not connecting to the real server until pre-USER time; mod_proxy needs to be modified to deal with this case. It's work needed for USER-based backend selection in the reverse proxy case, anyway. Client configuration:
  • proxyuser,user@host FZ: https://www3.trustwave.com/support/kb/article.aspx?id=13497 See WinSCP this is what lftp calls "user" for its ftp:proxy-auth-type setting.
  • proxyuser@host,user Chrome: uses system proxy settings (on Mac, at least?) FF: http://www.wikihow.com/Enter-Proxy-Settings-in-Firefox FZ: https://www3.trustwave.com/support/kb/article.aspx?id=13497 See WinSCP this is what lftp calls "proxy-user@host" for its ftp:proxy-auth-type setting. Future methods: Net::Config: ftp_firewall_type 2 USER proxy-user PASS proxy-pass USER user@remote.host[:port] PASS pass ftp_firewall_type 6 USER proxy-user@remote.host[:port] PASS proxy-pass USER user PASS pass See also this, which has *9* methods: http://www.mcknight.de/jftpgw/config.html#loginstyle Limiting the proxied hosts: ProxyBlock: http://httpd.apache.org/docs/2.4/mod/mod_proxy.html#proxyblock proftpd-mod_proxy-0.9.5/doc/NOTES.health-checks000066400000000000000000000240511475737016700213260ustar00rootroot00000000000000 Health check (more properly "application server state" checks with applied interpretation/policy) metrics: TCP connect to port conect timeout number of retries retry interval FTP connect to port require 220 response code, or any? (Keep in mind UseProxyProtocol option) require specific response string? connect timeout number of retries retry interval FTP USER require prompt for PASS, or any? require specific response string? timeout number of retries retry interval FTP login require succesful login (usually via anonymous login) require specific response string? timeout number of retries retry interval TLS handshake SSH key exchange SSH login SFTP All of the metrics for a given health check strategy/type are summed up. But is it a simple sum (all metrics are equal), or do some metrics have more weight? If weighted, how are weights calculated? Examples: https://www.varnish-cache.org/trac/wiki/BackendPolling http://www.taiter.com/techlog/2012/09/ftp-load-balanced-through-haproxy.html Notes: Notion of "quarantined", where the server is no longer considered "healthy", BUT there should continue to be periodic checks on its state to see if it has recovered. Consider how something like roundRobin or leastConns would work for an "FTP accelerator" type of client, which might open several concurrent sessions? Will it matter if those connections go to different servers? "RoundRobin is suitable where all available servers are assumed to be largely similar in functionality. Weighed/ratio'd RoundRobin builds weights into the system to deal with heterogenous capacity of the servers. (This is harder, since the "weight" factor is admin-assigned, and how exactly is it computed? 2x CPU? 4x memory? 6x network?) Implementation Initially, we will want a simple implementation. Passive health checks, not active. Observe errors on existing traffic, mark unhealthy backends and skip them, for some period of time. This will only apply to reverse proxying, not forward proxying. What types of errors should we watch for? How many errors before a backend is unhealthy? How long to skip over unhealthy backends? What happens when all backends are unhealthy? Types of errors: DNS resolution errors TCP connect errors TLS handshake errors FTP connect/login errors (ignoring bad credentials!) SSH banner error (e.g. ill-formed SSH banner/version) Specifically, we want to track errors that indicate that that server is unavailable for service. Thus probably NOT FTP data transfer errors. Configuration: use passive health tracking: yes/no number of failures before unhealthy ("down") [default: 2-3] number of successes before healthy ("up") [default: 1] success implies active probes/health checks; this is NOT that unhealthy timeout (before unhealthy status expires) [default: 10s] ProxyReverseHealthPolicy none/off PassiveChecks failures N (2) expires time (30s) ProxyReverseHealthPolicy PassiveChecks failures 2 expires 30s ActiveChecks (not implemented) Retries: depth-first (retry same target multiple times _first_), or breadth-first (retry next target _first_, cycling through list until max retries count reached) NOTE: We only want to call _index_used() if we WANT to move to the next backend. If we want to retry THIS backend (due to transient/ignorable/non-fatal error), then we explicitly do NOT call _index_used. Subtle. Need to capture this in comments for my future self. int (*policy_used_backend)(pool *p, void *dsh, int policy_id, unsigned int vhost_id, int backend_id); int (*policy_update_backend)(pool *p, void *dsh, int policy_id, unsigned int vhost_id, int backend_id, int conn_incr, long connect_ms); Maybe we need now: int (*policy_unhealthy_backend)(pool *p, void *dsh, int health_policy_id, unsigned int vhost_id, int backend_id, int unhealthy_incr, long unhealthy_ms, const char *unhealthy_reason) Unhealthy Errors DNS Maybe log message should include hint for "Trace dns:20" for more info gai_strerror: EAI_AGAIN (ignore) EAI_FAIL EAI_SYSTEM (see errno) EAI_NONAME mapped to ENOENT EAI_FAMILY mapped to EAFNOSUPPORT h_errno: HOST_NOT_FOUND TRY_AGAIN (ignore) NO_RECOVERY NO_DATA TCP EADDRINUSE (local error, ignore?) EADDRNOTAVAIL (local error, ignore?) ENETDOWN ENETUNREACH EHOSTUNREACH ENETRESET (ignore?) ECONNABORTED ECONNRESET ECONNREFUSED ETIMEDOUT TLS any ignorable? Maybe log message should include hint for "Trace tls:20" for more info FTP non-220 greeting banner_ok = FALSE (non-2xx) in reverse_try_connect() Maybe log message should include hint for "Trace proxy.response:20" for more info SSH illegal SSH version/banner bad_proto = TRUE in lib/proxy/ssh.c#ssh_get_server_version Maybe log message should include hint for "Trace proxy.ssh2:20 ssh2:20" for more info note that lib/proxy/ssh.c will NOT have the db index as seen in lib/proxy/reverse.c; may need a way to get it. That said, lib/proxy/ssh.c#ssh_ssh2_auth_completed_ev() DOES call proxy_reverse_connect() and thus all of the above. So the lack of treatment of illegal SSH banner as "unhealthy" is the result; I think that's OK for now. We can handle this case as unhealthy in a later pass. Remember that _index_used is what advances the index, for _next_backend. lib/proxy/reverse/db.c schema does NOT currently have columns for unhealthy status; would need to bump schema version! unhealthy_count INT unhealthy_ms BIGINT unhealthy_reason TEXT need reverse_connect_index_unhealthy() function for recording "down" backends, it increments unheathy_count, updates _ms, records last _reason (e.g. "dns: host unknown", "tcp: connection refused") and in _next per-policy db functions, need to see if selected backend is unhealthy, or not. If unhealthy, LOG IT. If down status expired, log expiry (and clear unhealthy columns) and use selected entry. Otherwise, select NEXT backend. This is where the health policy failure count is honored/implemented, by examining unhealthy_count value for exceeding threshold. If all backend addresses discovered are marked as "unavailable", should mod_proxy try connecting to one of them anyway? These _next per-policy db functions thus need to handle this scenario, where all are unhealthy, none expired; watch for "wrapping" around the list of targets! This, in turn, means that callers of _next need to handle NULL/ENOENT returns. Some policies, like Shuffle or Random, may require a "read-then-write" approach. Hmm. These are currently implemented as "get index into list of conns", and idx is a single value, not any metadata associated with that backend/idx (such as unhealthy fields). Maybe, in policy_next_backend, it should be: get index per policy get backend metadata for that index exposing/adding a policy_get_backend feels like surfacing this unnecessarily; it should be an impl detail of policy_next_backend. However, if implemented at the datastore layer, then the datastore layer needs accessors to the configured health policy parameters. I guess this is where having an include/proxy/reverse/health.h API could come in handy. int proxy_reverse_health_sess_init(pool *p); does lookup of ProxyReverseHealthPolicy directive called by proxy_reverse_sess_init int proxy_reverse_health_is_healthy(unsigned int unhealthy_count, unsigned long unhealthy_ms, const char *unhealthy_reason); This way, the Health API knows which policy is configured, knows its parameters. It logs the fields, handles expiration, etc. If the result is TRUE, then we clear any unhealthy fields. check backend metadata for health if unhealthy mark index as used goto top TRACK starting index, to catch looping around to same index value again due to ALL backends being unhealthy. Consider also the case where a configured backend server is a DNS name/URL, which resolves to multiple IP addresses/ports. These resolved addresses/ports are not currently tracked in the SQLite database -- thus we currently have no state for them persisted/shareable outside of that session process. If we did persist these addresses in the db, how to clean them up? Consider dynamic backends whose IPs change a lot over time; the cruft would build up in the db. Hmm. https://www.haproxy.com/blog/using-haproxy-as-an-api-gateway-part-3-health-checks/ https://docs.nginx.com/nginx/admin-guide/load-balancer/http-health-check/#passive-health-checks > Note that if there is only a single server in a group, the fail_timeout > and max_fails parameters are ignored and the server is never marked > unavailable. https://docs.nginx.com/nginx/admin-guide/load-balancer/tcp-health-check/#passive-tcp-health-checks > If several health checks are configured for an upstream group, the failure > of any check is enough to consider the corresponding server unhealthy. https://docs.nginx.com/nginx/admin-guide/load-balancer/tcp-health-check/#fine-tuning-tcp-health-checks Defaults: interval=5s, passes=1, fails=1 Tests: No regressions (default health policy: none) health policy: passive checks DNS errors TCP errors TLS errors FTP errors count exceeded, not exceeded expired, not expired all backends unhealthy for a connect policy + vhost proftpd-mod_proxy-0.9.5/doc/NOTES.history000066400000000000000000000010671475737016700203260ustar00rootroot00000000000000 Why does proftpd have the netio readers for buffering up reads? Seems overengineered/too complex. Answer: See issues like this: http://www.greatcircle.com/firewalls/mhonarc/firewalls.199710/msg00056.html Where does the formatting for using "USER name@host" for forward proxying coming from? Or using two USER/PASS commands for doing forward proxying with proxy auth? Answer: From TIS' FWTK: http://www.fwtk.org/fwtk/docs/user_guide.pdf http://www.fwtk.org/fwtk/docs/manpages.pdf See the cited example FTP sessions for both types of logins. proftpd-mod_proxy-0.9.5/doc/NOTES.load-balancing000066400000000000000000000076311475737016700214630ustar00rootroot00000000000000 ProxyReverseConnectPolicy Random RoundRobin LeastConns PerUser PerHost on startup event, iterate through configured backend servers, mark which ones cannot be connected to as "dead". Means backend server list management, with attributes: server: connCount connectTime lastChecked Or, better: TWO lists: live list, dead list For tracking connectTime (time to connect, in millisecs): long timevaldiff(struct timeval *starttime, struct timeval *finishtime) { long msec; msec=(finishtime->tv_sec-starttime->tv_sec)*1000; msec+=(finishtime->tv_usec-starttime->tv_usec)/1000; return msec; } struct timeval start, finish; long msec; gettimeofday(&start, NULL); sleep(1); gettimeofday(&finish, NULL); msec = timevaldiff(&start, &finish); printf("Elapsed time for sleep(1) is: %d milliseconds.\n", msec); Some platforms have a timersub(3) macro for this, but it'll be more portable to do our own. Advanced balancing (selection at USER time): user-specific (lookup per user) Randomly select N backend servers for user if not already assigned? host-specific (lookup per HOST) Randomly select N backend servers for host if not already assigned? For stickiness, we have two cases: one where the backend(s) are administratively assigned, and one where they are randomly chosen/assigned. Assume the former is the more common case. Could do: ConnectPolicy PerUser/PerHost Servers ... with no specific assignments. In this case, the username/client IP is hashed into an index of one of the servers, and kept that way. Let's start with that as the first iteration. Next: ConnectPolicy PerUser/PerHost ReverseServers ... FAQ: Why no PerClass stickiness? A: It's the same as PerHost, with the added use of +ReverseServers FAQ: Why no per-SSL session/ticket/SNI stickiness? A: It's too late; TLS is hop-by-hop, and thus the SSL session on the frontend, with the client, has no relation to the SSL session on the backend. Thus any routing based on SSL session ID to a backend, based on the client's session ID, is too late -- and that client session is for the proxy anyway. We should to load balancing based on TLS protocol version, weak ciphers, etc, but that would be to a pool of backend servers, NOT "sticky". Complex/adaptive balancing: Client IP address/class (connect time selection) FTP USER (i.e. user affinity to specific server(s)) (USER time selection) lookup AND hashed (algo? Same algo used by Table API?) Backend response time (connect time selection) Balancing Versus Stickiness For reverse proxy configurations, there is a choice between "balancing" strategies and "sticky" strategies when it comes to selecting the backend server that will handle the incoming connection. Which should you use, and why? All of the "balancing" strategies are able to select the backend server *at connect time*. Most of the "sticky" strategies, on the other hand, require more knowledge about the user (e.g. USER name, HOST name, SSL session ID), thus backend server selection is delayed until that information is obtained. Balancing is best when all of your backend severs are identical with regard to the content they have, AND when it doesn't matter which server handles a particular client. Maybe all of your backend servers use a shared filesystem via NFS or similar, thus directory listings will be the same for a user no matter which backend server is used, and uploading files to one server means that those files can be downloaded/seen by the other servers. Stickiness is best when your backend servers are NOT identical, and some users/clients should only ever go to some particular set of backend servers. Thus the user/client needs to be "sticky" to a given backend server(s) -- that's when you need the sticky selection strategies. proftpd-mod_proxy-0.9.5/doc/NOTES.protocol-translation000066400000000000000000000102731475737016700230210ustar00rootroot00000000000000 FTP to SFTP (#1): https://www.bitvise.com/ftp-bridge https://enterprisedt.com/products/completeftp/doc/guide/html/gateway.html mindterm source (Java, FTPOverSFTP bridge code for mapping) For each FTP command, decompose it into SFTP request equivalents; note which FTP commands have no SFTP request equivalents. USER PASS USERAUTH ACCT n/a CWD XCWD CDUP XCUP n/a; will mod_proxy have to maintain some state about the FTP session for such directory traversal commands? Yuck. Maybe use with REALPATH? SMNT not implemented REIN not implemented QUIT CHANNEL_CLOSE PORT EPRT PASV EPSV this will be the most interesting; translating SFTP data transfers into frontend FTP transfers. TYPE n/a (binary only?) STRU n/a; always F MODE n/a; always S RANG REST RETR OPEN + READ + CLOSE RANG REST APPE STOR OPEN + WRITE + CLOSE STOU OPEN + WRITE + CLOSE; have mod_proxy generate the unique name for the backend SFTP file? Use O_CREAT|O_EXCL to force uniqueness, I guess... ALLO n/a RNFR RNTO RENAME ABOR n/a; maybe just stop the current data transfer, send CLOSE? DELE REMOVE MDTM STAT MKD XMKD MKDIR RMD XRMD RMDIR LIST MLSD MLST NLST OPENDIR + READDIR + CLOSE MFF FSETSTAT MFMT FSETSTAT PWD XPWD SITE n/a; support for specific SITE commands *may* be added later, e.g. SITE CHMOD = SETSTAT SITE CHGRP = SETSTAT SITE SYMLINK = SYMLINK (from mod_site_misc) SITE UTIME = SETSTAT (from mod_site_misc) SIZE STAT SYST n/a; always "215 UNIX Type: L8" STAT STAT HELP n/a? NOOP no backend equivalent; handle in proxy FEAT n/a (SFTP extensions?) OPTS LANG n/a HOST n/a CLNT n/a (would be part of SSH connect, but is too late in FTP protocol) AUTH PBSZ PROT n/a (provided by SSH by default!) For authentication, it will always be password authentication to the backend SSH server. (Or should this overridable, e.g. password authentication to the proxy, but hostbased authentication from the proxy to the backend server?) What does this look like, for an FTP forward proxy configuration to an SFTP backend? How would mod_proxy know that the destination server is an SFTP server? I suppose it could do a probe: make the initial TCP connection, see whether it gets the "220 Server Ready" FTP response, or the "ssh-..." SSH banner... Note: This would require that mod_proxy be built _without_ mod_sftp being present! This means that the logic regarding mod_sftp HOOKs would need to be revisited. Implementation: Implement a "protocol", which handles all of the above FTP commands. The default Protocol object will do what mod_proxy currently does for all of the commands; this will thus be a transparent change. These Protocol objects would then have to maintain/accumulate their own state, so as to implement/translate RNFR + RNTO = RENAME, *and* be responsible for translating the responses. Thus these would indeed be more than just codecs (or, for some value of "codec", very complicated codecs). Once that's done, we need to determine how to lookup a new Protocol object, and when do it, and when to register it. For cases where mod_proxy knows the backend URL at connect time, this is easier. What about for the auth-time (PerUser, PerGroup) URLs? FTP Implementation API: Suitable for plugging into mod_proxy's CMD C_ANY handler, *and* its POST_CMD C_PROT handler. Thus the API for the given impl input should be something like: MODRET (handle_cmd)(pool *p, cmd_rec *cmd, int cmd_phase); Consider, for example, a logging-only Implementation object, to demonstrate the concept? Note that this Impl API works for FTP, but NOT for SSH; SSH packets are not handled by the C_ANY handler. SFTP to FTP (#2): No forward proxying supported here, since SFTP doesn't have that notion; mod_proxy will know (via the ProxyReversServers URL schemes) which protocol to use for the backend server. SCP (#3?): subset of SFTP to FTP, with no directory listing support. proftpd-mod_proxy-0.9.5/doc/NOTES.proxy-datatransfer-policy000066400000000000000000000056621475737016700237640ustar00rootroot00000000000000 Client: C -> P: PORT 1,2,3,4,5,6 P -> S: PORT 9,8,7,6,5,4 C <- P: 200 OK P <- S: 200 OK C -> P: PASV P -> S: PASV C <- P: 227 (1,2,3,4,5,6) P <- S: 227 (9,8,7,6,5,4) Order of operations: C -> P: PORT Parse addr/port Check RFC1918 addr Check AllowForeignAddress Check high-numbered port Open local listening conn Format local listening addr for PORT command Send new PORT command to S Receive 200 OK response from S Send 200 OK response to C Set frontend_sess_flags = SF_PORT Set backend_sess_flags = SF_PORT C -> P: PASV Send PASV command to S Receive 227 response from S Parse addr/port Check addr against remote addr Check high-numbered port Open local listening conn Format local listening addr for PASV response Send new PASV response to C Set frontend_sess_flags = SF_PASSIVE Set backend_sess_flags = SF_PASSIVE Active: C -> P: PORT 1,2,3,4,5,6 P -> S: PORT 9,8,7,6,5,4 C <- P: 200 OK P <- S: 200 OK C -> P: PASV P -> S: PORT 9,8,7,6,5,4 C <- P: 227 (1,2,3,4,5,6) P <- S: 200 OK Order of operations: C -> P: PORT Parse addr/port Check RFC1918 addr Check AllowForeignAddress Check high-numbered port Open local listening conn Format local listening addr for PORT command Send new PORT command to S Receive 200 OK response from S Send 200 OK response to C Set frontend_sess_flags = SF_PORT Set backend_sess_flags = SF_PORT C -> P: PASV Open local listening conn Format local listening addr for PORT command Send new PORT command to S Receive 200 OK response from S Open local listening conn Format local listening addr for PASV response Send new PASV response to C Set frontend_sess_flags = SF_PASSIVE Set backend_sess_flags = SF_PORT Passive: C -> P: PORT 1,2,3,4,5,6 P -> S: PASV C <- P: 200 OK P <- S: 227 (9,8,7,6,5,4) C -> P: PASV P -> S: PASV C <- P: 227 (1,2,3,4,5,6) P <- S: 227 (9,8,7,6,5,4) Order of operations: C -> P: PORT Parse addr/port Check RFC1918 addr Check AllowForeignAddress Check high-numbered port Send new PASV command to S Receive 227 responses from S Parse addr/port Check addr against remote addr Check high-numbered port Send 200 OK response to C Set frontend_sess_flags = SF_PORT Set backend_sess_flags = SF_PASSIVE C -> P: PASV Send PASV command to S Receive 227 response from S Parse addr/port Check addr against remote addr Check high-numbered port Open local listening conn Format local listening addr for PASV response Send new PASV response to C Set frontend_sess_flags = SF_PASSIVE Set backend_sess_flags = SF_PASSIVE proftpd-mod_proxy-0.9.5/doc/NOTES.reverse-proxy000066400000000000000000000074351475737016700214640ustar00rootroot00000000000000Selection Strategies: Balancing vs Stickiness Balancing strategies: leastConns random roundRobin shuffle Sticky strategies: client IP USER HOST What's better for FTP, SFTP connections? RoundRobin or LeastConns? And why? The HAproxy docs for the 'balance' keyword say that 'leastconn' is best for long-lived connections: leastconn The server with the lowest number of connections receives the connection. Round-robin is performed within groups of servers of the same load to ensure that all servers will be used. Use of this algorithm is recommended where very long sessions are expected, such as LDAP, SQL, TSE, etc... but is not very well suited for protocols using short sessions such as HTTP. This algorithm is dynamic, which means that server weights may be adjusted on the fly for slow starts for instance. (from http://haproxy.1wt.eu/download/1.4/doc/configuration.txt) Is this true? Corroboration or statistics showing the relationship? Dangers of LeastConns Consider that you are bringing up a new server, or restarting a server. Being new, it will have no connections -- and the load balancer sees this. Thus in a short period of time, ALL new connections may end up slamming that server. If the server can handle this type of workload, then you are good. If, on the other hand, the server performs warm-up tasks (e.g. filling caches, performing discovery of necessary downstream resources, etc), then this is quite undesirable. This is why HAproxy has a "warm up" period. Links/References http://www.timgalyean.com/2011/03/load-balancing-with-haproxy-and-apache/ http://blog.loadbalancer.org/load-balancing-windows-terminal-server-haproxy-and-rdp-cookies/ http://blogs.citrix.com/2010/09/02/load-balancing-least-connections/ http://www.brocade.com/support/Product_Manuals/ServerIron_SLBGuide/health.4.19.html#49842 https://kb.wisc.edu/ns/page.php?id=13201 http://serverfault.com/questions/457506/ha-proxy-roundrobin-vs-leastconn http://blog.exceliance.fr/2011/08/03/layer-7-load-balancing-proxy-mode/ https://devcentral.f5.com/articles/intro-to-load-balancing-for-developers-ndash-the-algorithms#.UsPAPY3TNe4 Nice description of methods, and quite a variety of them. https://devcentral.f5.com/articles/intro-to-load-balancing-for-developers-ndash-how-they-work#.UsT6cI3TNe4 Reuse the JPG/pic there? https://devcentral.f5.com/articles/intro-load-balancing-for-developers-ndash-the-architect-rsquos-view#.UsT6dI3TNe4 https://devcentral.f5.com/articles/advanced-load-balancing-for-developers-ndash-adcs-what-rsquos-the-difference#.UsT6XY3TNe4 The 'Programmability' bit is a good point. mod_proxy might need some ftpdctl actions implemented for altering things on-the-fly. https://devcentral.f5.com/articles/advanced-load-balancers-for-developers-adcs-the-code#.UsT6Y43TNe4 http://www.onjava.com/pub/a/onjava/2001/09/26/load.html Nice discussion of DNS round robin http://www.javaworld.com/article/2077921/architecture-scalability/server-load-balancing-architectures--part-1--transport-level-load-balancing.html http://www.javaworld.com/article/2077922/architecture-scalability/server-load-balancing-architectures--part-2--application-level-load-balanci.html http://www.infoq.com/articles/scalability-principles http://www.infoq.com/articles/ebay-scalability-best-practices Note Best Practice #4, and how that pertains to Black Pearl (think intra-service messaging/notifications) Aside: Software Architect Role http://www.codingthearchitecture.com/2008/02/11/the_key_difference_between_developer_and_architect_roles.html http://www.infoq.com/presentations/Role-of-the-Architect Need to watch this! proftpd-mod_proxy-0.9.5/doc/NOTES.selection000066400000000000000000000013101475737016700206010ustar00rootroot00000000000000 Balancing vs Stickiness Balancing: random shuffle roundRobin leastConns Sticky: per USER per HOST Work on implementing just the balancing strategies, for now. Note: The state files used for the balancing strategies, especially once health checks are added, could be externally cached/managed, e.g. via memcache/redis, using JSON. Issues there: if each mod_proxy is doing its own health checks of the servers, it's possible for individual mod_proxy instances to disagree about the health of a given backend (think split-brain syndrome). When each mod_proxy instance is managing its own view, this is OK; when they all share that persisted view, it could be a problem. proftpd-mod_proxy-0.9.5/doc/NOTES.selection-roundrobin000066400000000000000000000156431475737016700227760ustar00rootroot00000000000000 Issues for ProxyBackendSelection 'roundRobin': 1. Selection strategy is per-vhost. 2. Selection lists are per-vhost. This means that a 'core.fork' event, before the vhost is known, would not work easily. We don't just want to increment some counter/index for all vhosts, as that would not actually be the expected "round robin" behavior. To implement round robin, we need: 1. An ordered list of backend servers, whose order is preferably stable. How would healthchecks, with servers moving into and out of the "live" list, affect this? What happens if the "previous selection" or "next selection" (whichever we persist) is not available in the "live" list for the next connection? 2. Knowledge of what the "next" server should be (or, conversely, what the previous server was) 3. Persistence of #2 in some storage accessible across connections to that vhost (i.e. vhost-specific storage). Possibilities: SysV shm, file, SQL, memcache, external process. What about an mmap'd file? Still needs locking (with retries?) Any of these would require a postparse (startup?) event listener, to see if any vhost has RoundRobin selection configured, to create/prep the shared storage area. What if there are THREE backend server lists: configured: conf/ live live/ dead dead/ The "configured" list would be static, wouldn't change, would have stable ordering. That could then be the reference server/index for round robin. proxy_select_backends: vhost_sid INTEGER, name TEXT, // e.g. "server1.example.com" backend_id INTEGER, // used for ordering healthy BOOLEAN // used for healthchecks nconns INTEGER // used for least conns SELECT name FROM proxy_backends WHERE vhost_sid = {...} ORDER BY position ASC; proxy_select_roundrobin: vhost_sid INTEGER, current_backend_id INTEGER (FK into proxy_select_backends.backend_id?) proxy_select_shuffle: insert rows for used backend IDs; delete them all on _reset() (OR insert rows for UNUSED backend IDs; delete as used) a selection policy object, with callbacks into the database Basic data structure: vhost1 index (into 'configured' list) of current/selected backend server max index value (i.e. length of 'configured' list minus 1) ... vhostN Could use SID to identify vhost. unsigned int idx; int proxy_roundrobin_get_index(main_server->sid, &idx); /* Get backend for index */ idx++; if (idx == max_idx) { idx = 0; } int proxy_roundrobin_set_index(main_server->sid, idx); OR: unsigned int idx; int proxy_roundrobin_incr_index(main_server->sid, &idx); This would "atomically" return the current index, and increment (with wraparound) the index for the next call. Callers, then, don't need to know about the max_idx. With this arrangement, an on-disk mmap'd file would have range locking, and a "row" would be: uint32_t sid uint32_t backend_server_count uint32_t backend_server_idx Alternatively, the entire selection database could be a single JSON file; the "locking" would be done on the basis of the entire file. OR, alternatively, the selection database could be a SQL database (e.g. SQLite), a la mod_auth_otp. Store these databases in a policy-specific, vhost-specific file (to reduce contention)? This would make it easy for each policy to have its own format, as needed. CONF_ROOT|CONF_VIRTUAL, NO . Leaning toward SQLite. SELECT ... INSERT sid, ... UPDATE ... Per Policy! Hrm. Maybe have mod_proxy create its own tables, etc. (just configure a path). That'd work nicely. On startup, delete table if it exists (warn about this!), create needed schema. Much less fuss for the admin. Make lib/db/sqlite.c file, use sqlite3 directly (NOT via mod_sql+mod_sql_sqlite). This would be the "select.dat" file/table, the basis for backend selection. For health checks: uint32_t sid; uint32_t backend_server_count uint32_t live_servers[backend_server_count] uint32_t dead_servers[backend_server_count] ... Note: Use big-endian values for all numeric values on disk, as if writing to the network, for file "reuse" on other servers? Note: Would be nice, given the above format, to NOT have to scan the entire file to find the vhost in question, given the variable length of the server lists per vhost. Could deal with that by having a header, which would be the offset of that SID into the file. I.e.: off_t sid_offs[vhost_count]; sid 1: off = 0 sid 2: off = 42 and remember that vhost SIDs start at 1! off_t sid_off = sid_offs[main_server->sid-1]; lseek(fd, sid_off, SEEK_SET); readv(...) readv(...) Note: have a version uint32_t as first value in value, for indicating file format version. Note: have ftpdctl proxy action for dumping out (as JSON) the contents of the select.dat file? Note: files/structure: select.c select-random.c select-roundrobin.c select-shuffle.c ... policy/ random/ {sid}.json roundrobin/ {sid}.json shuffle/ {sid}.json When writing out the file initially, scan each vhost, figure out its position, then write out header, then vhost entry. And, to support the leastConns, equalConns, and leastResponseTime policies, we'll need a separate table (also mmap'd): unsigned int idx unsigned long curr_conns unsigned long curr_response_ms OR, even better, to handle the leastConns/leastResponseTime etc strategies, use a fourth list: configured ranked live dead Where "ranked" is a list of indices (into the configured list) in preference/ranked order. In the case of least conns, this ranking is done by number of current connections. Hmm, no, we'd still need to know that number of current connections somewhere, not just the relative rank of that server versus others -- consider that the number of connections may change, changing the relative ranking, and we'd need to know the number of current connections in order to do the re-ranking. To make this more general (e.g. for other selection policies), maybe: int proxy_reverse_select_index_next(main_server->sid, &idx, policy); Where policy could be: POLICY_RANDOM POLICY_ROUND_ROBIN POLICY_LEAST_CONNS POLICY_EQUAL_CONNS POLICY_LOWEST_RESPONSE_TIME If that backend is chosen/used successfully, then: int policy_reverse_select_index_used(main_server->sid, idx, response_ms); Note when we would need to maintain an fd on the database until the session ended (e.g. for leastConns), so that we could decrement the number of connections to a given SID when that connection ended. This is not necessary for roundRobin, which doesn't care about number of current connections per sid, only next SID to index. LeastConns, on the other hand, DOES care about number of current connections. proftpd-mod_proxy-0.9.5/doc/NOTES.tests000066400000000000000000000004551475737016700177670ustar00rootroot00000000000000 Note that to run the API tests for mod_proxy, you must: $ ./configure --enable-tests ... Then: $ cd contrib/mod_proxy/t/ $ make api-tests Available external FTP sites for testing: ftp.microsoft.com ftp.cisco.com* ftp.kernel.org ftp.freebsd.org ftp.seagate.com* (*) denotes FTPS proftpd-mod_proxy-0.9.5/doc/NOTES.tls000066400000000000000000000001051475737016700174170ustar00rootroot00000000000000 ProxyTLSOptions IgnoreFEAT UseCCC Passphrase provider support? proftpd-mod_proxy-0.9.5/doc/NOTES.todo000066400000000000000000000002201475737016700175600ustar00rootroot00000000000000 default low (1 sec) ConnectTimeout; consider many slow backends MODE Z proxying (mod_deflate) setjmp/longjmp for -X command-line option proftpd-mod_proxy-0.9.5/doc/REFERENCES000066400000000000000000000065261475737016700173640ustar00rootroot00000000000000 Stateful Inspection Advantage (Checkpoint): http://www.checkpoint.com/smb/help/z100g/7.5/7082.htm Linux Load Balancing with HAProxy + Heartbeat: https://wiki.gogrid.com/wiki/index.php/Customer:Linux_Load_Balancing_with_HAProxy+Heartbeat Load Balancing OpenSSH SFTP with HAProxy: http://jpmorris-iso.blogspot.com/2013/01/load-balancing-openssh-sftp-with-haproxy.html HAproxy PROXY protocol http://haproxy.1wt.eu/download/1.5/doc/proxy-protocol.txt FTP Load Balancing? http://www.formilux.org/archives/haproxy/0805/0976.html http://www.formilux.org/archives/haproxy/0805/0977.html Best way to use HAProxy for FTP? http://permalink.gmane.org/gmane.comp.web.haproxy/2486 Load Balancing FTP: http://ben.timby.com/?page_id=210 FTP Load-balanced through haproxy: http://www.taiter.com/techlog/2012/09/ftp-load-balanced-through-haproxy.html Network Load Balancing Services (MSFT): http://en.wikipedia.org/wiki/Network_Load_Balancing_Services Network Load Balancing: http://en.wikipedia.org/wiki/Network_Load_Balancing NcFTP.com "Why PASV Poses Problems for FTP Servers behind Load-Balancing Routers" http://www.ncftp.com/ncftpd/doc/misc/ftp_and_firewalls.html#PASVLBProblems ftpcluster: http://www.awk-scripting.de/cluster/ Brane Dump: Load Balancing FTP servers http://hezmatt.org/~mpalmer/blog/2008/10/22/load-balancing-ftp-servers.html Opensource load balancer projects: http://www.inlab.de/articles/free-and-open-source-load-balancing-software-and-projects.html http://sourceforge.net/projects/balance/ FTP ALG: http://www.amaranten.com/support/user%20guide/Application_Layer_Gateways/FTP_ALG.htm http://www.ietf.org/id/draft-tsou-behave-ftp46-00.txt Breaking through FTP ALGs: possible? http://seclists.org/bugtraq/2000/Feb/176 http://seclists.org/vuln-dev/2000/Feb/82 https://listserv.icsalabs.com/pipermail/firewall-wizards/2000-March/008251.html AWS ELB for FTP: https://forums.aws.amazon.com/thread.jspa?messageID=139143 Trilent FTP Proxy: http://trilent-ftp-proxy.en.softonic.com/ ServerFault: Reverse Proxy FTP traffic: http://serverfault.com/questions/122636/reverse-proxy-ftp-traffic Search on an FTP server: http://serverfault.com/questions/28568/using-the-find-command-on-the-ftp-server?rq=1 FTP Load balancer: http://serverfault.com/questions/139342/ftp-load-balancer?rq=1 Possible to load balance FTP? http://serverfault.com/questions/113456/is-it-possible-to-load-balance-an-ftp-server frox as reverse proxy? http://serverfault.com/questions/390043/frox-as-reverse-proxy proxy and reverse proxy on same server http://serverfault.com/questions/65133/proxy-and-reverse-proxy-on-same-server?rq=1 FTP/SFTP/FTPS proxying http://serverfault.com/questions/117899/ftp-sftp-ftps-proxying?rq=1 X-Forwarded-For http://stackoverflow.com/questions/11891185/x-forward-for-over-ssl-using-load-balancers?rq=1 Reverse proxy capabilities: http://www.rhinosoft.com/pressarchive/PressRelease2012-05-10.asp?prod=rs Diagrams: http://pic.dhe.ibm.com/infocenter/ssp/v3r4/index.jsp?topic=%2Fcom.ibm.help.sspftpreverseproxy.doc%2FSSP_FTS_FTPRProxCfg.html http://pic.dhe.ibm.com/infocenter/ssp/v3r4/index.jsp?topic=%2Fcom.ibm.help.sspoverview.doc%2FSSP_Diag_FinishedFTPRev.html http://pic.dhe.ibm.com/infocenter/ssp/v3r4/index.jsp?topic=%2Fcom.ibm.help.sspoverview.doc%2FSSP_Diag_FinishSFTPRev.html proftpd-mod_proxy-0.9.5/include/000077500000000000000000000000001475737016700166655ustar00rootroot00000000000000proftpd-mod_proxy-0.9.5/include/proxy/000077500000000000000000000000001475737016700200465ustar00rootroot00000000000000proftpd-mod_proxy-0.9.5/include/proxy/conn.h000066400000000000000000000047261475737016700211650ustar00rootroot00000000000000/* * ProFTPD - mod_proxy conn API * Copyright (c) 2012-2020 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_CONN_H #define MOD_PROXY_CONN_H #include "mod_proxy.h" #include "proxy/session.h" struct proxy_conn; void proxy_conn_clear_username(const struct proxy_conn *pconn); void proxy_conn_clear_password(const struct proxy_conn *pconn); int proxy_conn_connect_timeout_cb(CALLBACK_FRAME); const struct proxy_conn *proxy_conn_create(pool *p, const char *uri, unsigned int flags); #define PROXY_CONN_CREATE_FL_USE_DNS_TTL 0x0001 const pr_netaddr_t *proxy_conn_get_addr(const struct proxy_conn *, array_header **); int proxy_conn_get_dns_ttl(const struct proxy_conn *pconn); const char *proxy_conn_get_host(const struct proxy_conn *pconn); const char *proxy_conn_get_hostport(const struct proxy_conn *pconn); int proxy_conn_get_port(const struct proxy_conn *pconn); conn_t *proxy_conn_get_server_conn(pool *p, struct proxy_session *proxy_sess, const pr_netaddr_t *remote_addr); const char *proxy_conn_get_uri(const struct proxy_conn *pconn); const char *proxy_conn_get_username(const struct proxy_conn *pconn); const char *proxy_conn_get_password(const struct proxy_conn *pconn); int proxy_conn_get_tls(const struct proxy_conn *pconn); int proxy_conn_use_dns_srv(const struct proxy_conn *pconn); int proxy_conn_use_dns_txt(const struct proxy_conn *pconn); int proxy_conn_send_proxy_v1(pool *p, conn_t *conn); int proxy_conn_send_proxy_v2(pool *p, conn_t *conn); void proxy_conn_free(const struct proxy_conn *pconn); #endif /* MOD_PROXY_CONN_H */ proftpd-mod_proxy-0.9.5/include/proxy/db.h000066400000000000000000000061231475737016700206060ustar00rootroot00000000000000/* * ProFTPD - mod_proxy database API * Copyright (c) 2015-2021 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_DB_H #define MOD_PROXY_DB_H #include "mod_proxy.h" struct proxy_dbh; int proxy_db_init(pool *p); int proxy_db_free(void); /* Create/prepare the database (with the given schema name) at the given path */ struct proxy_dbh *proxy_db_open(pool *p, const char *table_path, const char *schema_name); /* Create/prepare the database (with the given schema name) at the given path. * If the database/schema already exists, check that its schema version is * greater than or equal to the given minimum version. If not, delete that * database and create a new one. */ struct proxy_dbh *proxy_db_open_with_version(pool *p, const char *table_path, const char *schema_name, unsigned int schema_version, int flags); #define PROXY_DB_OPEN_FL_SCHEMA_VERSION_CHECK 0x001 #define PROXY_DB_OPEN_FL_ERROR_ON_SCHEMA_VERSION_SKEW 0x002 #define PROXY_DB_OPEN_FL_INTEGRITY_CHECK 0x004 #define PROXY_DB_OPEN_FL_VACUUM 0x008 #define PROXY_DB_OPEN_FL_SKIP_VACUUM 0x010 /* Close the database. */ int proxy_db_close(pool *p, struct proxy_dbh *dbh); int proxy_db_prepare_stmt(pool *p, struct proxy_dbh *dbh, const char *stmt); int proxy_db_finish_stmt(pool *p, struct proxy_dbh *dbh, const char *stmt); int proxy_db_bind_stmt(pool *p, struct proxy_dbh *dbh, const char *stmt, int idx, int type, void *data, int datalen); #define PROXY_DB_BIND_TYPE_INT 1 #define PROXY_DB_BIND_TYPE_LONG 2 #define PROXY_DB_BIND_TYPE_TEXT 3 #define PROXY_DB_BIND_TYPE_BLOB 4 #define PROXY_DB_BIND_TYPE_NULL 5 /* Executes the given statement. Assumes that the caller is not using a SELECT, * and/or is uninterested in the statement results. */ int proxy_db_exec_stmt(pool *p, struct proxy_dbh *dbh, const char *stmt, const char **errstr); /* Executes the given statement as a previously prepared statement. */ array_header *proxy_db_exec_prepared_stmt(pool *p, struct proxy_dbh *dbh, const char *stmt, const char **errstr); /* Rebuild the named index. */ int proxy_db_reindex(pool *p, struct proxy_dbh *dbh, const char *index_name, const char **errstr); #endif /* MOD_PROXY_DB_H */ proftpd-mod_proxy-0.9.5/include/proxy/dns.h000066400000000000000000000035261475737016700210110ustar00rootroot00000000000000/* * ProFTPD - mod_proxy DNS API * Copyright (c) 2020 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_DNS_H #define MOD_PROXY_DNS_H #include "mod_proxy.h" typedef enum { PROXY_DNS_UNKNOWN, PROXY_DNS_A, PROXY_DNS_AAAA, PROXY_DNS_SRV, PROXY_DNS_TXT } proxy_dns_type_e; /* Resolves a given `name` to a list of textual response lines, based on the * given DNS record type. * * For A records, the responses will be IPv4 addresses. * For AAAA records, the responses will be IPv6 addresses. * For SRV records, the responses will be the returned address/ports, in * priority order. * For TXT records, the responses will be the returned textual lines. * * If a `ttl` pointer is provided, the shortest TTL on retrieved records * retrieved is returned. */ int proxy_dns_resolve(pool *p, const char *name, proxy_dns_type_e dns_type, array_header **resp, uint32_t *ttl); #endif /* MOD_PROXY_DNS_H */ proftpd-mod_proxy-0.9.5/include/proxy/forward.h000066400000000000000000000044511475737016700216670ustar00rootroot00000000000000/* * ProFTPD - mod_proxy forward-proxy API * Copyright (c) 2012-2022 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_FORWARD_H #define MOD_PROXY_FORWARD_H #include "mod_proxy.h" #include "proxy/session.h" #define PROXY_FORWARD_ENABLED_NOTE "mod_proxy.forward-enabled" int proxy_forward_init(pool *p, const char *tables_dir); int proxy_forward_free(pool *p); int proxy_forward_sess_init(pool *p, const char *tables_dir, struct proxy_session *proxy_sess); int proxy_forward_sess_free(pool *p, struct proxy_session *proxy_sess); int proxy_forward_have_authenticated(cmd_rec *cmd); int proxy_forward_handle_user(cmd_rec *cmd, struct proxy_session *proxy_sess, int *successful, int *block_responses); int proxy_forward_handle_pass(cmd_rec *cmd, struct proxy_session *proxy_sess, int *successful, int *block_responses); /* Forward proxy method API */ #define PROXY_FORWARD_METHOD_USER_WITH_PROXY_AUTH 1 #define PROXY_FORWARD_METHOD_USER_NO_PROXY_AUTH 2 #define PROXY_FORWARD_METHOD_PROXY_USER_WITH_PROXY_AUTH 3 #define PROXY_FORWARD_METHOD_USER_SNI_NO_PROXY_AUTH 4 /* Return the method ID for the given string, or -1 if the given method * is not recognized/supported. */ int proxy_forward_get_method(const char *); /* Returns TRUE if the Forward API is using proxy auth, FALSE otherwise. */ int proxy_forward_use_proxy_auth(void); #endif /* MOD_PROXY_FORWARD_H */ proftpd-mod_proxy-0.9.5/include/proxy/ftp/000077500000000000000000000000001475737016700206375ustar00rootroot00000000000000proftpd-mod_proxy-0.9.5/include/proxy/ftp/conn.h000066400000000000000000000027341475737016700217530ustar00rootroot00000000000000/* * ProFTPD - mod_proxy FTP connection API * Copyright (c) 2013-2016 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_FTP_CONN_H #define MOD_PROXY_FTP_CONN_H #include "mod_proxy.h" conn_t *proxy_ftp_conn_accept(pool *p, conn_t *data_conn, conn_t *ctrl_conn, int frontend_data); conn_t *proxy_ftp_conn_connect(pool *p, const pr_netaddr_t *local_addr, const pr_netaddr_t *remote_addr, int frontend_data); conn_t *proxy_ftp_conn_listen(pool *p, const pr_netaddr_t *bind_addr, int frontend_data); #endif /* MOD_PROXY_FTP_CONN_H */ proftpd-mod_proxy-0.9.5/include/proxy/ftp/ctrl.h000066400000000000000000000033771475737016700217660ustar00rootroot00000000000000/* * ProFTPD - mod_proxy FTP control conn API * Copyright (c) 2012-2023 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_FTP_CTRL_H #define MOD_PROXY_FTP_CTRL_H #include "mod_proxy.h" /* Note: this flag is only used for testing. */ #define PROXY_FTP_CTRL_FL_IGNORE_EOF 0x0001 #define PROXY_FTP_CTRL_FL_IGNORE_BLANK_RESP 0x0002 int proxy_ftp_ctrl_handle_async(pool *p, conn_t *backend_conn, conn_t *frontend_conn, int flags); pr_response_t *proxy_ftp_ctrl_recv_resp(pool *p, conn_t *ctrl_conn, unsigned int *resp_nlines, int flags); int proxy_ftp_ctrl_send_abort(pool *p, conn_t *ctrl_conn, cmd_rec *cmd); int proxy_ftp_ctrl_send_cmd(pool *p, conn_t *ctrl_conn, cmd_rec *cmd); int proxy_ftp_ctrl_send_resp(pool *p, conn_t *ctrl_conn, pr_response_t *resp, unsigned int resp_nlines); #endif /* MOD_PROXY_FTP_CTRL_H */ proftpd-mod_proxy-0.9.5/include/proxy/ftp/data.h000066400000000000000000000025011475737016700217170ustar00rootroot00000000000000/* * ProFTPD - mod_proxy FTP data conn API * Copyright (c) 2012-2016 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_FTP_DATA_H #define MOD_PROXY_FTP_DATA_H #include "mod_proxy.h" pr_buffer_t *proxy_ftp_data_recv(pool *p, conn_t *conn, int frontend_data); int proxy_ftp_data_send(pool *p, conn_t *conn, pr_buffer_t *pbuf, int frontend_data); #endif /* MOD_PROXY_FTP_DATA_H */ proftpd-mod_proxy-0.9.5/include/proxy/ftp/dirlist.h000066400000000000000000000047201475737016700224650ustar00rootroot00000000000000/* * ProFTPD - mod_proxy FTP dirlist API * Copyright (c) 2020 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_FTP_DIRLIST_H #define MOD_PROXY_FTP_DIRLIST_H #include "mod_proxy.h" #include "proxy/session.h" int proxy_ftp_dirlist_init(pool *p, struct proxy_session *proxy_sess); int proxy_ftp_dirlist_finish(struct proxy_session *proxy_sess); struct proxy_dirlist_fileinfo { pool *pool; struct stat *st; unsigned char have_uid, have_gid; struct tm *tm; const char *user; const char *group; const char *type; const char *perm; const char *path; }; #define PROXY_FTP_DIRLIST_OPT_USE_SLINK 0x0001 struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_dos(pool *p, const char *text, size_t textlen, unsigned long opts); struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_unix(pool *p, const char *text, size_t textlen, struct tm *tm, unsigned long opts); struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_text(pool *p, const char *text, size_t textlen, struct tm *tm, void *user_data, unsigned long opts); const char *proxy_ftp_dirlist_fileinfo_to_facts(pool *p, const struct proxy_dirlist_fileinfo *pdf, size_t *textlen); /* Given a buffer of (possibly incomplete) dirlist data, return the text * to give to the client. Note that there may be enough data accumulated * yet to provide text to the client. */ int proxy_ftp_dirlist_to_text(pool *p, char *buf, size_t buflen, size_t max_textsz, char **text, size_t *textlen, void *user_data); #endif /* MOD_PROXY_FTP_DIRLIST_H */ proftpd-mod_proxy-0.9.5/include/proxy/ftp/facts.h000066400000000000000000000034151475737016700221130ustar00rootroot00000000000000/* * ProFTPD - mod_proxy FTP Facts API * Copyright (c) 2020 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_FTP_FACTS_H #define MOD_PROXY_FTP_FACTS_H #include "mod_proxy.h" /* RFC 3659 Facts */ #define PROXY_FTP_FACTS_OPT_SHOW_MODIFY 0x00001 #define PROXY_FTP_FACTS_OPT_SHOW_PERM 0x00002 #define PROXY_FTP_FACTS_OPT_SHOW_SIZE 0x00004 #define PROXY_FTP_FACTS_OPT_SHOW_TYPE 0x00008 #define PROXY_FTP_FACTS_OPT_SHOW_UNIQUE 0x00010 #define PROXY_FTP_FACTS_OPT_SHOW_UNIX_GROUP 0x00020 #define PROXY_FTP_FACTS_OPT_SHOW_UNIX_MODE 0x00040 #define PROXY_FTP_FACTS_OPT_SHOW_UNIX_OWNER 0x00080 #define PROXY_FTP_FACTS_OPT_SHOW_UNIX_OWNER_NAME 0x00100 #define PROXY_FTP_FACTS_OPT_SHOW_UNIX_GROUP_NAME 0x00200 unsigned long proxy_ftp_facts_get_opts(void); void proxy_ftp_facts_parse_opts(char *facts); #endif /* MOD_PROXY_FTP_FACTS_H */ proftpd-mod_proxy-0.9.5/include/proxy/ftp/msg.h000066400000000000000000000036301475737016700216000ustar00rootroot00000000000000/* * ProFTPD - mod_proxy FTP message API * Copyright (c) 2013-2021 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_FTP_MSG_H #define MOD_PROXY_FTP_MSG_H #include "mod_proxy.h" /* Format a string containg the address for use in a PORT command or a * PASV response. */ const char *proxy_ftp_msg_fmt_addr(pool *, const pr_netaddr_t *, unsigned short, int); /* Format a string containg the address for use in an EPRT command or an * EPSV response. */ const char *proxy_ftp_msg_fmt_ext_addr(pool *, const pr_netaddr_t *, unsigned short, int, int); /* Parse the address/port out of a string, e.g. from a PORT command or from * a PASV response. */ const pr_netaddr_t *proxy_ftp_msg_parse_addr(pool *, const char *, int); /* Parse the address/port out of a string, e.g. from an EPRT command or from * an EPSV response. */ const pr_netaddr_t *proxy_ftp_msg_parse_ext_addr(pool *, const char *, const pr_netaddr_t *, int, const char *); #endif /* MOD_PROXY_FTP_MSG_H */ proftpd-mod_proxy-0.9.5/include/proxy/ftp/sess.h000066400000000000000000000033411475737016700217660ustar00rootroot00000000000000/* * ProFTPD - mod_proxy FTP session API * Copyright (c) 2015-2021 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_FTP_SESS_H #define MOD_PROXY_FTP_SESS_H #include "mod_proxy.h" #include "proxy/session.h" /* ProxyTLSTransferProtectionPolicy values */ #define PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_REQUIRED 1 #define PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_CLIENT 0 #define PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_CLEAR -1 int proxy_ftp_sess_get_feat(pool *, const struct proxy_session *proxy_sess); int proxy_ftp_sess_send_auth_tls(pool *p, const struct proxy_session *proxy_sess); int proxy_ftp_sess_send_host(pool *, const struct proxy_session *proxy_sess); int proxy_ftp_sess_send_pbsz_prot(pool *p, const struct proxy_session *proxy_sess); #endif /* MOD_PROXY_FTP_SESS_H */ proftpd-mod_proxy-0.9.5/include/proxy/ftp/xfer.h000066400000000000000000000026151475737016700217600ustar00rootroot00000000000000/* * ProFTPD - mod_proxy FTP data transfer API * Copyright (c) 2013-2016 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_FTP_XFER_H #define MOD_PROXY_FTP_XFER_H #include "mod_proxy.h" #include "proxy/session.h" int proxy_ftp_xfer_prepare_active(int, cmd_rec *, const char *, struct proxy_session *, int); const pr_netaddr_t *proxy_ftp_xfer_prepare_passive(int, cmd_rec *, const char *, struct proxy_session *, int); #endif /* MOD_PROXY_FTP_XFER_H */ proftpd-mod_proxy-0.9.5/include/proxy/inet.h000066400000000000000000000032201475737016700211530ustar00rootroot00000000000000/* * ProFTPD - mod_proxy Inet API * Copyright (c) 2015-2016 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_INET_H #define MOD_PROXY_INET_H #include "mod_proxy.h" /* Proxied versions of the core Inet API functions; see include/inet.h. */ conn_t *proxy_inet_accept(pool *p, conn_t *data_conn, conn_t *ctrl_conn, int rfd, int wfd, int resolve); void proxy_inet_close(pool *p, conn_t *conn); int proxy_inet_connect(pool *p, conn_t *conn, const pr_netaddr_t *addr, int port); int proxy_inet_listen(pool *p, conn_t *conn, int backlog, int flags); conn_t *proxy_inet_openrw(pool *p, conn_t *conn, const pr_netaddr_t *addr, int strm_type, int fd, int rfd, int wfd, int resolve); #endif /* MOD_PROXY_INET_H */ proftpd-mod_proxy-0.9.5/include/proxy/netio.h000066400000000000000000000046431475737016700213440ustar00rootroot00000000000000/* * ProFTPD - mod_proxy NetIO API * Copyright (c) 2015-2016 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_NETIO_H #define MOD_PROXY_NETIO_H #include "mod_proxy.h" pr_netio_t *proxy_netio_unset(int strm_type, const char *fn); int proxy_netio_set(int strm_type, pr_netio_t *netio); /* Tells the Proxy NetIO API to use the given netio for the given stream * type, when proxy_netio_unset() and proxy_netio_set() are called on that * stream type. */ int proxy_netio_use(int strm_type, pr_netio_t *netio); /* Returns the netio that the Proxy NetIO API is using for a given stream * type, if any. */ int proxy_netio_using(int strm_type, pr_netio_t **netio); /* Proxied versions of the core NetIO API functions; see include/netio.h. */ pr_netio_stream_t *proxy_netio_open(pool *p, int strm_type, int fd, int mode); int proxy_netio_close(pr_netio_stream_t *nstrm); int proxy_netio_postopen(pr_netio_stream_t *nstrm); int proxy_netio_printf(pr_netio_stream_t *nstrm, const char *fmt, ...); int proxy_netio_poll(pr_netio_stream_t *nstrm); int proxy_netio_postopen(pr_netio_stream_t *nstrm); int proxy_netio_read(pr_netio_stream_t *nstrm, char *buf, size_t bufsz, int bufmin); void proxy_netio_reset_poll_interval(pr_netio_stream_t *nstrm); void proxy_netio_set_poll_interval(pr_netio_stream_t *nstrm, unsigned int secs); int proxy_netio_shutdown(pr_netio_stream_t *nstrm, int how); int proxy_netio_write(pr_netio_stream_t *nstrm, char *buf, size_t bufsz); #endif /* MOD_PROXY_NETIO_H */ proftpd-mod_proxy-0.9.5/include/proxy/random.h000066400000000000000000000024641475737016700215050ustar00rootroot00000000000000/* * ProFTPD - mod_proxy random number API * Copyright (c) 2013-2016 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_RANDOM_H #define MOD_PROXY_RANDOM_H #include "mod_proxy.h" int proxy_random_init(void); /* Return the next random number between the given min/max numbers, inclusive. */ long proxy_random_next(long min, long max); #endif /* MOD_PROXY_RANDOM_H */ proftpd-mod_proxy-0.9.5/include/proxy/reverse.h000066400000000000000000000102111475737016700216650ustar00rootroot00000000000000/* * ProFTPD - mod_proxy reverse-proxy API * Copyright (c) 2012-2022 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_REVERSE_H #define MOD_PROXY_REVERSE_H #include "mod_proxy.h" #include "proxy/session.h" int proxy_reverse_init(pool *p, const char *tables_dir, int flags); int proxy_reverse_free(pool *p); int proxy_reverse_have_authenticated(cmd_rec *cmd); int proxy_reverse_sess_init(pool *p, const char *tables_dir, struct proxy_session *proxy_sess, int flags); int proxy_reverse_sess_free(pool *p, struct proxy_session *proxy_sess); int proxy_reverse_sess_exit(pool *p); int proxy_reverse_handle_user(cmd_rec *cmd, struct proxy_session *proxy_sess, int *successful, int *block_responses); int proxy_reverse_handle_pass(cmd_rec *cmd, struct proxy_session *proxy_sess, int *successful, int *block_responses); array_header *proxy_reverse_json_parse_uris(pool *p, const char *path, unsigned int flags); /* Connect policy API */ #define PROXY_REVERSE_CONNECT_POLICY_RANDOM 1 #define PROXY_REVERSE_CONNECT_POLICY_ROUND_ROBIN 2 #define PROXY_REVERSE_CONNECT_POLICY_LEAST_CONNS 3 #define PROXY_REVERSE_CONNECT_POLICY_LEAST_RESPONSE_TIME 4 #define PROXY_REVERSE_CONNECT_POLICY_SHUFFLE 5 #define PROXY_REVERSE_CONNECT_POLICY_PER_USER 6 #define PROXY_REVERSE_CONNECT_POLICY_PER_GROUP 7 #define PROXY_REVERSE_CONNECT_POLICY_PER_HOST 8 /* Returns the configured connect policy ID. */ int proxy_reverse_get_connect_policy(void); /* Return the policy ID for the given string, or -1 if the given policy * is not recognized/supported. */ int proxy_reverse_connect_get_policy_id(const char *policy); /* Returns TRUE if the given policy ID is a "sticky" policy, i.e. one of * PerUser, PerGroup, or PerHost. */ int proxy_reverse_policy_is_sticky(int policy_id); /* Returns a textual name for the given policy ID. */ const char *proxy_reverse_policy_name(int policy_id); /* Returns the per-user/group backends for the given name. */ array_header *proxy_reverse_pername_backends(pool *p, const char *name, int per_user); /* Look up, and connect to, the selected backend server. */ int proxy_reverse_connect(pool *p, struct proxy_session *proxy_sess, const void *connect_data); /* Returns TRUE if the Reverse API is using proxy auth, FALSE otherwise. */ int proxy_reverse_use_proxy_auth(void); /* Defines the datastore interface. */ struct proxy_reverse_datastore { /* Policy callbacks */ int (*policy_init)(pool *p, void *dsh, int policy_id, unsigned int vhost_id, array_header *backends, unsigned long opts); const struct proxy_conn *(*policy_next_backend)(pool *p, void *dsh, int policy_id, unsigned int vhost_id, array_header *default_backends, const void *policy_data, int *backend_id); int (*policy_used_backend)(pool *p, void *dsh, int policy_id, unsigned int vhost_id, int backend_id); int (*policy_update_backend)(pool *p, void *dsh, int policy_id, unsigned int vhost_id, int backend_id, int conn_incr, long connect_ms); void *(*init)(pool *p, const char *path, int flags); void *(*open)(pool *p, const char *path, array_header *backends); int (*close)(pool *p, void *dsh); /* Datastore handle returned by the open callback. */ void *dsh; int backend_id; }; #endif /* MOD_PROXY_REVERSE_H */ proftpd-mod_proxy-0.9.5/include/proxy/reverse/000077500000000000000000000000001475737016700215215ustar00rootroot00000000000000proftpd-mod_proxy-0.9.5/include/proxy/reverse/db.h000066400000000000000000000024461475737016700222650ustar00rootroot00000000000000/* * ProFTPD - mod_proxy Reverse Database API * Copyright (c) 2017 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_REVERSE_DB_H #define MOD_PROXY_REVERSE_DB_H #include "mod_proxy.h" #include "proxy/reverse.h" int proxy_reverse_db_as_datastore(struct proxy_reverse_datastore *ds, void *ds_data, size_t ds_datasz); #endif /* MOD_PROXY_REVERSE_DB_H */ proftpd-mod_proxy-0.9.5/include/proxy/reverse/redis.h000066400000000000000000000025251475737016700230040ustar00rootroot00000000000000/* * ProFTPD - mod_proxy Reverse Redis API * Copyright (c) 2017-2020 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_REVERSE_REDIS_H #define MOD_PROXY_REVERSE_REDIS_H #include "mod_proxy.h" #include "proxy/reverse.h" #include "proxy/reverse/redis.h" int proxy_reverse_redis_as_datastore(struct proxy_reverse_datastore *ds, void *ds_data, size_t ds_datasz); #endif /* MOD_PROXY_REVERSE_REDIS_H */ proftpd-mod_proxy-0.9.5/include/proxy/session.h000066400000000000000000000057471475737016700217170ustar00rootroot00000000000000/* * ProFTPD - mod_proxy sessions * Copyright (c) 2012-2021 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_SESSION_H #define MOD_PROXY_SESSION_H #include "mod_proxy.h" struct proxy_conn; struct proxy_session { struct pool_rec *pool; int connect_timeout; int connect_timerno; int linger_timeout; /* Frontend connection */ conn_t *frontend_ctrl_conn; conn_t *frontend_data_conn; volatile int frontend_sess_flags; const pr_netaddr_t *frontend_data_addr; /* Backend connection */ conn_t *backend_ctrl_conn; conn_t *backend_data_conn; volatile int backend_sess_flags; const pr_netaddr_t *backend_data_addr; /* Address for connections to/from destination server. May be null. */ const pr_netaddr_t *src_addr; const struct proxy_conn *dst_pconn; /* Address of the destination server. May be null. */ const pr_netaddr_t *dst_addr; array_header *other_addrs; /* Which protocol are we proxying? */ int use_ftp, use_ssh; /* Features supported by backend server. */ pr_table_t *backend_features; /* Data transfer pool, for things like the frontend/backend address * objects. */ pool *dataxfer_pool; /* Data transfer policy: PASV, EPSV, PORT, EPRT, or client. */ int dataxfer_policy; /* Directory list policy: LIST, or client. */ int dirlist_policy; unsigned long dirlist_opts; void *dirlist_ctx; }; /* Zero indicates "do what the client does". */ #define PROXY_SESS_DATA_TRANSFER_POLICY_DEFAULT 0 #define PROXY_SESS_DIRECTORY_LIST_POLICY_DEFAULT 0 #define PROXY_SESS_DIRECTORY_LIST_POLICY_LIST 1 /* Default MaxLoginAttempts */ #define PROXY_SESS_MAX_LOGIN_ATTEMPTS 3 const struct proxy_session *proxy_session_alloc(pool *p); int proxy_session_free(pool *p, const struct proxy_session *proxy_sess); int proxy_session_reset_dataxfer(struct proxy_session *proxy_sess); int proxy_session_check_password(pool *p, const char *user, const char *passwd); int proxy_session_setup_env(pool *p, const char *user, int flags); #define PROXY_SESSION_FL_CHECK_LOGIN_ACL 0x00001 #endif /* MOD_PROXY_SESSION_H */ proftpd-mod_proxy-0.9.5/include/proxy/ssh.h000066400000000000000000000051751475737016700210240ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH API * Copyright (c) 2021-2023 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_SSH_H #define MOD_PROXY_SSH_H #include "mod_proxy.h" #include "proxy/session.h" /* ProxySFTPOptions values. NOTE: Make sure these do NOT collide with existing * PROXY_OPT_ values defined in mod_proxy.h. */ #define PROXY_OPT_SSH_PESSIMISTIC_KEXINIT 0x0100 #define PROXY_OPT_SSH_OLD_PROTO_COMPAT 0x0200 #define PROXY_OPT_SSH_ALLOW_WEAK_DH 0x0400 #define PROXY_OPT_SSH_ALLOW_WEAK_SECURITY 0x0800 #define PROXY_OPT_SSH_NO_EXT_INFO 0x1000 #define PROXY_OPT_SSH_NO_HOSTKEY_ROTATION 0x2000 #define PROXY_OPT_SSH_NO_STRICT_KEX 0x4000 int proxy_ssh_init(pool *p, const char *tables_dir, int flags); int proxy_ssh_free(pool *p); int proxy_ssh_sess_init(pool *p, struct proxy_session *proxy_sess, int flags); int proxy_ssh_sess_free(pool *p); /* Defines the datastore interface. */ struct proxy_ssh_datastore { /* Keystore callbacks */ int (*hostkey_add)(pool *p, void *dsh, unsigned int vhost_id, const char *backend_uri, const char *algo, const unsigned char *hostkey_data, uint32_t hostkey_datalen); const unsigned char *(*hostkey_get)(pool *p, void *dsh, unsigned int vhost_id, const char *backend_uri, const char **algo, uint32_t *hostkey_datalen); int (*hostkey_update)(pool *p, void *dsh, unsigned int vhost_id, const char *backend_uri, const char *algo, const unsigned char *hostkey_data, uint32_t hostkey_datalen); int (*init)(pool *p, const char *path, int flags); void *(*open)(pool *p, const char *path, unsigned long opts); int (*close)(pool *p, void *dsh); /* Datastore handle returned by the open callback. */ void *dsh; }; #endif /* MOD_PROXY_SSH_H */ proftpd-mod_proxy-0.9.5/include/proxy/ssh/000077500000000000000000000000001475737016700206435ustar00rootroot00000000000000proftpd-mod_proxy-0.9.5/include/proxy/ssh/agent.h000066400000000000000000000032021475737016700221070ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH agent API * Copyright (c) 2021 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_SSH_AGENT_H #define MOD_PROXY_SSH_AGENT_H #include "mod_proxy.h" #if defined(PR_USE_OPENSSL) struct agent_key { unsigned char *key_data; uint32_t key_datalen; const char *agent_path; }; int proxy_ssh_agent_get_keys(pool *p, const char *, array_header *); const unsigned char *proxy_ssh_agent_sign_data(pool *, const char *, const unsigned char *, uint32_t, const unsigned char *, uint32_t, uint32_t *, int); #define PROXY_SSH_AGENT_SIGN_FL_USE_RSA_SHA256 0x001 #define PROXY_SSH_AGENT_SIGN_FL_USE_RSA_SHA512 0x002 #endif /* PR_USE_OPENSSL */ #endif /* MOD_PROXY_SSH_AGENT_H */ proftpd-mod_proxy-0.9.5/include/proxy/ssh/auth.h000066400000000000000000000033161475737016700217600ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH auth API * Copyright (c) 2021-2022 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_SSH_AUTH_H #define MOD_PROXY_SSH_AUTH_H #include "mod_proxy.h" #include "proxy/session.h" #include "proxy/ssh/packet.h" #if defined(PR_USE_OPENSSL) int proxy_ssh_auth_init(pool *p); int proxy_ssh_auth_sess_init(pool *p, const struct proxy_session *proxy_sess); /* Returns 1 for successfully completed authentication, 0 if the client * needs to make another authentication attempt, and -1 on error. */ int proxy_ssh_auth_handle(struct proxy_ssh_packet *pkt, const struct proxy_session *proxy_sess); int proxy_ssh_auth_set_frontend_success_handle(pool *p, int (*cb)(pool *p, const char *user)); #endif /* PR_USE_OPENSSL */ #endif /* MOD_PROXY_SSH_AUTH_H */ proftpd-mod_proxy-0.9.5/include/proxy/ssh/bcrypt.h000066400000000000000000000026661475737016700223310ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH bcrypt PBKDF2 * Copyright (c) 2021 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_SSH_BCRYPT_H #define MOD_PROXY_SSH_BCRYPT_H #include "mod_proxy.h" #if defined(PR_USE_OPENSSL) #define PROXY_SSH_BCRYPT_DIGEST_LEN 32 int proxy_ssh_bcrypt_pbkdf2(pool *p, const char *passphrase, size_t passphrase_len, unsigned char *salt, uint32_t salt_len, uint32_t rounds, unsigned char *key, uint32_t key_len); #endif /* PR_USE_OPENSSL */ #endif /* MOD_PROXY_SSH_BCRYPT_H */ proftpd-mod_proxy-0.9.5/include/proxy/ssh/cipher.h000066400000000000000000000053221475737016700222700ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH cipher API * Copyright (c) 2021-2022 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_SSH_CIPHER_H #define MOD_PROXY_SSH_CIPHER_H #include "mod_proxy.h" #if defined(PR_USE_OPENSSL) int proxy_ssh_cipher_init(void); int proxy_ssh_cipher_free(void); /* Returns the cipher block size, or 8, whichever is larger. This value is * used when reading in the first bytes of a packet in order to determine * the packet length. See RFC4253, Section 6, "Binary Packet Protocol". */ size_t proxy_ssh_cipher_get_read_block_size(void); size_t proxy_ssh_cipher_get_write_block_size(void); void proxy_ssh_cipher_set_read_block_size(size_t); void proxy_ssh_cipher_set_write_block_size(size_t); /* Returns the cipher authenticated data size, or zero. */ size_t proxy_ssh_cipher_get_read_auth_size(void); size_t proxy_ssh_cipher_get_read_auth_size2(void); size_t proxy_ssh_cipher_get_write_auth_size(void); size_t proxy_ssh_cipher_get_write_auth_size2(void); const char *proxy_ssh_cipher_get_read_algo(void); int proxy_ssh_cipher_set_read_algo(pool *p, const char *algo); int proxy_ssh_cipher_set_read_key(pool *p, const EVP_MD *md, const unsigned char *k, uint32_t klen, const char *h, uint32_t hlen, int role); int proxy_ssh_cipher_read_data(struct proxy_ssh_packet *pkt, unsigned char *data, uint32_t data_len, unsigned char **buf, uint32_t *buflen); const char *proxy_ssh_cipher_get_write_algo(void); int proxy_ssh_cipher_set_write_algo(pool *p, const char *algo); int proxy_ssh_cipher_set_write_key(pool *p, const EVP_MD *md, const unsigned char *k, uint32_t klen, const char *h, uint32_t hlen, int role); int proxy_ssh_cipher_write_data(struct proxy_ssh_packet *pkt, unsigned char *buf, size_t *bufsz); #endif /* PR_USE_OPENSSL */ #endif /* MOD_PROXY_SSH_CIPHER_H */ proftpd-mod_proxy-0.9.5/include/proxy/ssh/compress.h000066400000000000000000000034141475737016700226510ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH compression API * Copyright (c) 2021 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_SSH_COMPRESS_H #define MOD_PROXY_SSH_COMPRESS_H #include "mod_proxy.h" #include "proxy/ssh/packet.h" #if defined(PR_USE_OPENSSL) #define PROXY_SSH_COMPRESS_FL_NEW_KEY 1 #define PROXY_SSH_COMPRESS_FL_AUTHENTICATED 2 int proxy_ssh_compress_init_read(int); const char *proxy_ssh_compress_get_read_algo(void); int proxy_ssh_compress_set_read_algo(pool *p, const char *algo); int proxy_ssh_compress_read_data(struct proxy_ssh_packet *); int proxy_ssh_compress_init_write(int); const char *proxy_ssh_compress_get_write_algo(void); int proxy_ssh_compress_set_write_algo(pool *p, const char *algo); int proxy_ssh_compress_write_data(struct proxy_ssh_packet *); #endif /* PR_USE_OPENSSL */ #endif /* MOD_PROXY_SSH_COMPRESS_H */ proftpd-mod_proxy-0.9.5/include/proxy/ssh/crypto.h000066400000000000000000000033041475737016700223340ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH crypto API * Copyright (c) 2021-2022 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_SSH_CRYPTO_H #define MOD_PROXY_SSH_CRYPTO_H #include "mod_proxy.h" #if defined(PR_USE_OPENSSL) #include void proxy_ssh_crypto_free(int flags); const EVP_CIPHER *proxy_ssh_crypto_get_cipher(const char *algo, size_t *key_len, size_t *auth_len, size_t *discard_len); const EVP_MD *proxy_ssh_crypto_get_digest(const char *algo, uint32_t *mac_len); const char *proxy_ssh_crypto_get_kexinit_cipher_list(pool *p); const char *proxy_ssh_crypto_get_kexinit_digest_list(pool *p); const char *proxy_ssh_crypto_get_errors(void); size_t proxy_ssh_crypto_get_size(size_t, size_t); #endif /* PR_USE_OPENSSL */ #endif /* MOD_PROXY_SSH_CRYPTO_H */ proftpd-mod_proxy-0.9.5/include/proxy/ssh/db.h000066400000000000000000000025021475737016700214000ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH Database API * Copyright (c) 2021 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_SSH_DB_H #define MOD_PROXY_SSH_DB_H #include "mod_proxy.h" #include "proxy/ssh.h" #if defined(PR_USE_OPENSSL) int proxy_ssh_db_as_datastore(struct proxy_ssh_datastore *ds, void *ds_data, size_t ds_datasz); #endif /* PR_USE_OPENSSL */ #endif /* MOD_PROXY_SSH_DB_H */ proftpd-mod_proxy-0.9.5/include/proxy/ssh/disconnect.h000066400000000000000000000041461475737016700231520ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH disconnect API * Copyright (c) 2021 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_SSH_DISCONNECT_H #define MOD_PROXY_SSH_DISCONNECT_H #include "mod_proxy.h" #if defined(PR_USE_OPENSSL) void proxy_ssh_disconnect_conn(conn_t *, uint32_t, const char *, const char *, int, const char *); void proxy_ssh_disconnect_send(pool *, conn_t *, uint32_t, const char *, const char *, int, const char *); /* Given a disconnect reason code from a server, return a string explaining * that code. */ const char *proxy_ssh_disconnect_get_text(uint32_t); /* Deal with the fact that __FUNCTION__ is a gcc extension. Sun's compilers * (e.g. SunStudio) like __func__. */ # if defined(__FUNCTION__) #define PROXY_SSH_DISCONNECT_CONN(c, n, m) \ proxy_ssh_disconnect_conn((c), (n), (m), __FILE__, __LINE__, __FUNCTION__) # elif defined(__func__) #define PROXY_SSH_DISCONNECT_CONN(c, n, m) \ proxy_ssh_disconnect_conn((c), (n), (m), __FILE__, __LINE__, __func__) # else #define PROXY_SSH_DISCONNECT_CONN(c, n, m) \ proxy_ssh_disconnect_conn((c), (n), (m), __FILE__, __LINE__, "") # endif #endif /* PR_USE_OPENSSL */ #endif /* MOD_PROXY_SSH_DISCONNECT_H */ proftpd-mod_proxy-0.9.5/include/proxy/ssh/interop.h000066400000000000000000000071721475737016700225030ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH interop API * Copyright (c) 2021 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_SSH_INTEROP_H #define MOD_PROXY_SSH_INTEROP_H #include "mod_proxy.h" #include "proxy/session.h" #if defined(PR_USE_OPENSSL) /* For servers which do not support IGNORE packets */ #define PROXY_SSH_FEAT_IGNORE_MSG 0x0001 /* For servers which always truncate the HMAC len to 16 bits, regardless * of the actual HMAC len. */ #define PROXY_SSH_FEAT_MAC_LEN 0x0002 /* For servers which do not include K when deriving cipher keys. */ #define PROXY_SSH_FEAT_CIPHER_USE_K 0x0004 /* For servers which do not support rekeying */ #define PROXY_SSH_FEAT_REKEYING 0x0008 /* For servers which do not support USERAUTH_BANNER packets */ #define PROXY_SSH_FEAT_USERAUTH_BANNER 0x0010 /* For servers which do not send a string indicating the public key * algorithm in their publickey authentication requests. This also * includes servers which do not use the string "publickey", and the * string for the public key algorithm, in the public key signature * (as dictated by Section 7 of RFC4252). */ #define PROXY_SSH_FEAT_HAVE_PUBKEY_ALGO 0x0020 /* For servers whose publickey signatures always use a service name of * "ssh-userauth", regardless of the actual service name included in the * USERAUTH_REQUEST packet. */ #define PROXY_SSH_FEAT_SERVICE_IN_PUBKEY_SIG 0x0040 /* For servers whose DSA publickey signatures do not include the string * "ssh-dss". */ #define PROXY_SSH_FEAT_HAVE_PUBKEY_ALGO_IN_DSA_SIG 0x0080 /* For servers whose hostbased signatures always use a service name of * "ssh-userauth", regardless of the actual service name included in the * USERAUTH_REQUEST packet. */ #define PROXY_SSH_FEAT_SERVICE_IN_HOST_SIG 0x0100 /* For servers that want the client to pessimistically send its NEWKEYS message * after they send their NEWKEYS message. */ #define PROXY_SSH_FEAT_PESSIMISTIC_NEWKEYS 0x0200 /* For servers which cannot/do not tolerate non-kex related packets after a * client has requested rekeying. */ #define PROXY_SSH_FEAT_NO_DATA_WHILE_REKEYING 0x0400 /* For servers which do not support/implement RFC 4419 DH group exchange. */ #define PROXY_SSH_FEAT_DH_NEW_GEX 0x0800 /* Compares the given server version string against a table of known server * versions and their interoperability/compatibility issues. */ int proxy_ssh_interop_handle_version(pool *, const struct proxy_session *, const char *); /* Returns TRUE if the server supports the requested feature, FALSE * otherwise. */ int proxy_ssh_interop_supports_feature(int); int proxy_ssh_interop_init(void); int proxy_ssh_interop_free(void); #endif /* PR_USE_OPENSSL */ #endif /* MOD_PROXY_SSH_INTEROP_H */ proftpd-mod_proxy-0.9.5/include/proxy/ssh/kex.h000066400000000000000000000034011475737016700216010ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH kex API * Copyright (c) 2021 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_SSH_KEX_H #define MOD_PROXY_SSH_KEX_H #include "mod_proxy.h" #include "proxy/session.h" #include "proxy/ssh.h" #if defined(PR_USE_OPENSSL) int proxy_ssh_kex_handle(struct proxy_ssh_packet *pkt, const struct proxy_session *proxy_sess); int proxy_ssh_kex_init(pool *p, const char *client_version, const char *server_version); int proxy_ssh_kex_free(void); int proxy_ssh_kex_sess_init(pool *p, struct proxy_ssh_datastore *ds, int verify_hostkeys); int proxy_ssh_kex_sess_free(void); int proxy_ssh_kex_send_first_kexinit(pool *p, const struct proxy_session *proxy_sess); #define PROXY_SSH_KEX_DH_GROUP_MIN 1024 #define PROXY_SSH_KEX_DH_GROUP_MAX 8192 #endif /* PR_USE_OPENSSL */ #endif /* MOD_PROXY_SSH_KEX_H */ proftpd-mod_proxy-0.9.5/include/proxy/ssh/keys.h000066400000000000000000000060521475737016700217720ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH keys API * Copyright (c) 2021-2022 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_SSH_KEYS_H #define MOD_PROXY_SSH_KEYS_H #include "mod_proxy.h" #if defined(PR_USE_OPENSSL) #include enum proxy_ssh_key_type_e { PROXY_SSH_KEY_UNKNOWN = 0, PROXY_SSH_KEY_DSA, PROXY_SSH_KEY_RSA, PROXY_SSH_KEY_RSA_SHA256, PROXY_SSH_KEY_RSA_SHA512, PROXY_SSH_KEY_ECDSA_256, PROXY_SSH_KEY_ECDSA_384, PROXY_SSH_KEY_ECDSA_521, PROXY_SSH_KEY_ED25519, PROXY_SSH_KEY_ED448 }; /* Returns a string of colon-separated lowercase hex characters, representing * the key "fingerprint" which has been run through the specified digest * algorithm. * * As per draft-ietf-secsh-fingerprint-00, only MD5 fingerprints are currently * supported. */ const char *proxy_ssh_keys_get_fingerprint(pool *, unsigned char *, uint32_t, int); #define PROXY_SSH_KEYS_FP_DIGEST_MD5 1 #define PROXY_SSH_KEYS_FP_DIGEST_SHA1 2 #define PROXY_SSH_KEYS_FP_DIGEST_SHA256 3 enum proxy_ssh_key_type_e proxy_ssh_keys_get_key_type(const char *algo); const char *proxy_ssh_keys_get_key_type_desc(enum proxy_ssh_key_type_e); void proxy_ssh_keys_free(void); int proxy_ssh_keys_have_hostkey(enum proxy_ssh_key_type_e); int proxy_ssh_keys_get_hostkey(pool *p, const char *); const unsigned char *proxy_ssh_keys_get_hostkey_data(pool *, enum proxy_ssh_key_type_e, uint32_t *); void proxy_ssh_keys_get_passphrases(void); int proxy_ssh_keys_set_passphrase_provider(const char *); const unsigned char *proxy_ssh_keys_sign_data(pool *, enum proxy_ssh_key_type_e, const unsigned char *, size_t, size_t *); #if defined(PR_USE_OPENSSL_ECC) int proxy_ssh_keys_validate_ecdsa_params(const EC_GROUP *, const EC_POINT *); #endif /* PR_USE_OPENSSL_ECC */ int proxy_ssh_keys_verify_pubkey_type(pool *, unsigned char *, uint32_t, enum proxy_ssh_key_type_e); int proxy_ssh_keys_verify_signed_data(pool *p, const char *pubkey_algo, unsigned char *pubkey_data, uint32_t pubkey_datalen, unsigned char *signature, uint32_t signaturelen, unsigned char *sig_data, size_t sig_datalen); #endif /* PR_USE_OPENSSL */ #endif /* MOD_PROXY_SSH_KEYS_H */ proftpd-mod_proxy-0.9.5/include/proxy/ssh/mac.h000066400000000000000000000042511475737016700215560ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH MAC API * Copyright (c) 2021-2022 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_SSH_MAC_H #define MOD_PROXY_SSH_MAC_H #include "mod_proxy.h" #include "proxy/ssh/packet.h" #if defined(PR_USE_OPENSSL) int proxy_ssh_mac_init(void); int proxy_ssh_mac_free(void); /* Returns the block size of the negotiated MAC algorithm, or 0 if no MAC * has been negotiated yet. */ size_t proxy_ssh_mac_get_block_size(void); void proxy_ssh_mac_set_block_size(size_t blocksz); const char *proxy_ssh_mac_get_read_algo(void); int proxy_ssh_mac_is_read_etm(void); int proxy_ssh_mac_set_read_algo(pool *p, const char *algo); int proxy_ssh_mac_set_read_key(pool *p, const EVP_MD *md, const unsigned char *k, uint32_t klen, const char *h, uint32_t hlen, int role); int proxy_ssh_mac_read_data(struct proxy_ssh_packet *pkt); const char *proxy_ssh_mac_get_write_algo(void); int proxy_ssh_mac_is_write_etm(void); int proxy_ssh_mac_set_write_algo(pool *p, const char *algo); int proxy_ssh_mac_set_write_key(pool *p, const EVP_MD *md, const unsigned char *k, uint32_t klen, const char *h, uint32_t hlen, int role); int proxy_ssh_mac_write_data(struct proxy_ssh_packet *pkt); #endif /* PR_USE_OPENSSL */ #endif /* MOD_PROXY_SSH_MAC_H */ proftpd-mod_proxy-0.9.5/include/proxy/ssh/misc.h000066400000000000000000000025531475737016700217540ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH miscellany API * Copyright (c) 2021 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_SSH_MISC_H #define MOD_PROXY_SSH_MISC_H #include "mod_proxy.h" #if defined(PR_USE_OPENSSL) int proxy_ssh_misc_namelist_contains(pool *, const char *, const char *); const char *proxy_ssh_misc_namelist_shared(pool *, const char *, const char *); #endif /* PR_USE_OPENSSL */ #endif /* MOD_PROXY_SSH_MISC_H */ proftpd-mod_proxy-0.9.5/include/proxy/ssh/msg.h000066400000000000000000000061121475737016700216020ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH message API * Copyright (c) 2021 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_SSH_MSG_H #define MOD_PROXY_SSH_MSG_H #include "mod_proxy.h" #if defined(PR_USE_OPENSSL) #if defined(PR_USE_OPENSSL_ECC) # include # include #endif /* PR_USE_OPENSSL_ECC */ uint32_t proxy_ssh_msg_read_byte(pool *p, unsigned char **buf, uint32_t *buflen, unsigned char *msg); uint32_t proxy_ssh_msg_read_bool(pool *p, unsigned char **buf, uint32_t *buflen, int *msg); uint32_t proxy_ssh_msg_read_data(pool *p, unsigned char **buf, uint32_t *buflen, size_t msglen, unsigned char **msg); #if defined(PR_USE_OPENSSL_ECC) uint32_t proxy_ssh_msg_read_ecpoint(pool *p, unsigned char **buf, uint32_t *buflen, const EC_GROUP *ec_group, EC_POINT **msg); #endif /* PR_USE_OPENSSL_ECC */ uint32_t proxy_ssh_msg_read_int(pool *p, unsigned char **buf, uint32_t *buflen, uint32_t *msg); uint32_t proxy_ssh_msg_read_long(pool *p, unsigned char **buf, uint32_t *buflen, uint64_t *msg); uint32_t proxy_ssh_msg_read_mpint(pool *p, unsigned char **buf, uint32_t *buflen, const BIGNUM **msg); uint32_t proxy_ssh_msg_read_string(pool *p, unsigned char **buf, uint32_t *buflen, char **msg); uint32_t proxy_ssh_msg_write_byte(unsigned char **buf, uint32_t *buflen, unsigned char msg); uint32_t proxy_ssh_msg_write_bool(unsigned char **buf, uint32_t *buflen, unsigned char msg); uint32_t proxy_ssh_msg_write_data(unsigned char **buf, uint32_t *buflen, const unsigned char *msg, size_t msglen, int include_len); #if defined(PR_USE_OPENSSL_ECC) uint32_t proxy_ssh_msg_write_ecpoint(unsigned char **buf, uint32_t *buflen, const EC_GROUP *ec_group, const EC_POINT *ec_point); #endif /* PR_USE_OPENSSL_ECC */ uint32_t proxy_ssh_msg_write_int(unsigned char **buf, uint32_t *buflen, uint32_t msg); uint32_t proxy_ssh_msg_write_long(unsigned char **buf, uint32_t *buflen, uint64_t msg); uint32_t proxy_ssh_msg_write_mpint(unsigned char **buf, uint32_t *buflen, const BIGNUM *msg); uint32_t proxy_ssh_msg_write_string(unsigned char **buf, uint32_t *buflen, const char *msg); #endif /* PR_USE_OPENSSL */ #endif /* MOD_PROXY_SSH_MSG_H */ proftpd-mod_proxy-0.9.5/include/proxy/ssh/packet.h000066400000000000000000000132131475737016700222630ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH packet API * Copyright (c) 2021-2023 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_SSH_PACKET_H #define MOD_PROXY_SSH_PACKET_H #include "mod_proxy.h" #include "proxy/session.h" #if defined(PR_USE_OPENSSL) /* From RFC 4253, Section 6 */ /* NOTE: This struct MUST be kept in sync with the struct used in mod_sftp; * failure to do so WILL lead to inexplicable and hard-to-diagnose errors! */ struct proxy_ssh_packet { pool *pool; /* Module that created this packet. */ module *m; /* Length of the packet, not including mac or packet_len field itself. */ uint32_t packet_len; /* Length of the padding field. */ unsigned char padding_len; unsigned char *payload; uint32_t payload_len; /* Must be at least 4 bytes of padding, with a maximum of 255 bytes. */ unsigned char *padding; /* Additional Authenticated Data (AAD). */ unsigned char *aad; uint32_t aad_len; /* Message Authentication Code. */ unsigned char *mac; uint32_t mac_len; /* Packet sequence number. */ uint32_t seqno; }; #define PROXY_SSH_MIN_PADDING_LEN 4 #define PROXY_SSH_MAX_PADDING_LEN 255 /* From the SFTP Draft, Section 4. */ struct proxy_sftp_packet { uint32_t packet_len; unsigned char packet_type; uint32_t request_id; }; struct proxy_ssh_packet *proxy_ssh_packet_create(pool *p); char proxy_ssh_packet_get_msg_type(struct proxy_ssh_packet *pkt); char proxy_ssh_packet_peek_msg_type(const struct proxy_ssh_packet *pkt); const char *proxy_ssh_packet_get_msg_type_desc(unsigned char msg_type); void proxy_ssh_packet_log_cmd(struct proxy_ssh_packet *pkt, int from_frontend); #define PROXY_SSH_PACKET_IO_READ 5 #define PROXY_SSH_PACKET_IO_WRITE 7 int proxy_ssh_packet_conn_poll(conn_t *conn, int io); /* Similar to `proxy_ssh_packet_conn_poll`, but we poll multiple connections. * 0 is returned if the frontend connection has data, 1 is returned if the * backend connection has data, and -1 on error/timeout. */ int proxy_ssh_packet_conn_mpoll(conn_t *frontend_conn, conn_t *backend_conn, int io); int proxy_ssh_packet_conn_read(conn_t *conn, void *buf, size_t reqlen, int flags); int proxy_ssh_packet_read(conn_t *conn, struct proxy_ssh_packet *pkt); /* This proxy_ssh_packet_conn_read() flag is used to tell the function to * read in as many of the requested length of data as it can, but to NOT * keep polling until that length has been acquired (i.e. to read the * requested length pessimistically, assuming that it will not all appear). */ #define PROXY_SSH_PACKET_READ_FL_PESSIMISTIC 0x001 int proxy_ssh_packet_send(conn_t *conn, struct proxy_ssh_packet *pkt); /* Wrapper function around proxy_ssh_packet_send() which handles the sending * of messages and buffering of messages for network efficiency. */ int proxy_ssh_packet_write(conn_t *conn, struct proxy_ssh_packet *pkt); int proxy_ssh_packet_write_frontend(conn_t *conn, struct proxy_ssh_packet *pkt); /* Proxy the packet from frontend-to-backend, or backend-to-frontend. */ int proxy_ssh_packet_proxied(const struct proxy_session *proxy_sess, struct proxy_ssh_packet *pkt, int from_frontend); /* This function reads in an SSH2 packet from the socket, and dispatches * the packet to various handlers. */ int proxy_ssh_packet_process(pool *p, const struct proxy_session *proxy_sess); /* Handle any SSH2 packet. */ int proxy_ssh_packet_handle(void *pkt); /* These specialized functions are for handling the additional message types * defined in RFC 4253, Section 11, e.g. during KEX. */ void proxy_ssh_packet_handle_debug(struct proxy_ssh_packet *pkt); void proxy_ssh_packet_handle_disconnect(struct proxy_ssh_packet *pkt); void proxy_ssh_packet_handle_ext_info(struct proxy_ssh_packet *pkt); void proxy_ssh_packet_handle_ignore(struct proxy_ssh_packet *pkt); void proxy_ssh_packet_handle_unimplemented(struct proxy_ssh_packet *pkt); /* These are used for implementing the "strict KEX" mitigations of the Terrapin * attack (Issue 257). */ uint32_t proxy_ssh_packet_get_server_seqno(void); void proxy_ssh_packet_reset_client_seqno(void); void proxy_ssh_packet_reset_server_seqno(void); int proxy_ssh_packet_set_version(const char *client_version); int proxy_ssh_packet_send_version(conn_t *conn); int proxy_ssh_packet_get_poll_attempts(unsigned int *nattempts); int proxy_ssh_packet_set_poll_attempts(unsigned int nattempts); int proxy_ssh_packet_get_poll_timeout(int *secs, unsigned long *ms); int proxy_ssh_packet_set_poll_timeout(int secs, unsigned long ms); int proxy_ssh_packet_set_server_alive(unsigned int, unsigned int); int proxy_ssh_packet_set_frontend_packet_handle(pool *p, int (*cb)(void *pkt)); void proxy_ssh_packet_set_frontend_packet_write(int (*cb)(int fd, void *pkt)); #endif /* PR_USE_OPENSSL */ #endif /* MOD_PROXY_SSH_PACKET_H */ proftpd-mod_proxy-0.9.5/include/proxy/ssh/redis.h000066400000000000000000000024231475737016700221230ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH Redis API * Copyright (c) 2022 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_SSH_REDIS_H #define MOD_PROXY_SSH_REDIS_H #include "mod_proxy.h" #include "proxy/ssh.h" int proxy_ssh_redis_as_datastore(struct proxy_ssh_datastore *ds, void *ds_data, size_t ds_datasz); #endif /* MOD_PROXY_SSH_REDIS_H */ proftpd-mod_proxy-0.9.5/include/proxy/ssh/service.h000066400000000000000000000025511475737016700224570ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH service API * Copyright (c) 2021 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_SSH_SERVICE_H #define MOD_PROXY_SSH_SERVICE_H #include "mod_proxy.h" #include "proxy/session.h" #include "proxy/ssh/packet.h" #if defined(PR_USE_OPENSSL) int proxy_ssh_service_handle(struct proxy_ssh_packet *, const struct proxy_session *); #endif /* PR_USE_OPENSSL */ #endif /* MOD_PROXY_SSH_SERVICE_H */ proftpd-mod_proxy-0.9.5/include/proxy/ssh/session.h000066400000000000000000000027341475737016700225050ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH session API * Copyright (c) 2021 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_SSH_SESSION_H #define MOD_PROXY_SSH_SESSION_H #include "mod_proxy.h" #if defined(PR_USE_OPENSSL) uint32_t proxy_ssh_session_get_id(const unsigned char **); /* Note that the provided pool must have the same lifetime as that of the * entire SSH session, e.g. proxy_pool or similar. */ int proxy_ssh_session_set_id(pool *p, const unsigned char *, uint32_t); #endif /* PR_USE_OPENSSL */ #endif /* MOD_PROXY_SSH_SESSION_H */ proftpd-mod_proxy-0.9.5/include/proxy/ssh/ssh2.h000066400000000000000000000106151475737016700216760ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH2 constants * Copyright (c) 2021 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_SSH_SSH2_H #define MOD_PROXY_SSH_SSH2_H /* As per RFC 4253, Section 6.1, we MUST be able to handle a packet whose * length is 35000 bytes; we SHOULD be able to handle larger packets. We * impose a maximum size here to prevent overly-large packets from being * used by attackers. The maximum size is a bit arbitrary. */ #define PROXY_SSH_MAX_PACKET_LEN (1024 * 256) /* SSH2 message types */ #define PROXY_SSH_MSG_DISCONNECT 1 #define PROXY_SSH_MSG_IGNORE 2 #define PROXY_SSH_MSG_UNIMPLEMENTED 3 #define PROXY_SSH_MSG_DEBUG 4 #define PROXY_SSH_MSG_SERVICE_REQUEST 5 #define PROXY_SSH_MSG_SERVICE_ACCEPT 6 #define PROXY_SSH_MSG_EXT_INFO 7 #define PROXY_SSH_MSG_KEXINIT 20 #define PROXY_SSH_MSG_NEWKEYS 21 /* Key exchange message types */ #define PROXY_SSH_MSG_KEX_DH_INIT 30 #define PROXY_SSH_MSG_KEX_DH_REPLY 31 #define PROXY_SSH_MSG_KEX_DH_GEX_REQUEST_OLD 30 #define PROXY_SSH_MSG_KEX_DH_GEX_GROUP 31 #define PROXY_SSH_MSG_KEX_DH_GEX_INIT 32 #define PROXY_SSH_MSG_KEX_DH_GEX_REPLY 33 #define PROXY_SSH_MSG_KEX_DH_GEX_REQUEST 34 #define PROXY_SSH_MSG_KEXRSA_PUBKEY 30 #define PROXY_SSH_MSG_KEXRSA_SECRET 31 #define PROXY_SSH_MSG_KEXRSA_DONE 32 #define PROXY_SSH_MSG_KEX_ECDH_INIT 30 #define PROXY_SSH_MSG_KEX_ECDH_REPLY 31 /* User authentication message types */ #define PROXY_SSH_MSG_USER_AUTH_REQUEST 50 #define PROXY_SSH_MSG_USER_AUTH_FAILURE 51 #define PROXY_SSH_MSG_USER_AUTH_SUCCESS 52 #define PROXY_SSH_MSG_USER_AUTH_BANNER 53 #define PROXY_SSH_MSG_USER_AUTH_PUBKEY 60 #define PROXY_SSH_MSG_USER_AUTH_PK_OK 60 #define PROXY_SSH_MSG_USER_AUTH_PASSWD 60 #define PROXY_SSH_MSG_USER_AUTH_INFO_REQ 60 #define PROXY_SSH_MSG_USER_AUTH_INFO_RESP 61 /* Request types */ #define PROXY_SSH_MSG_GLOBAL_REQUEST 80 #define PROXY_SSH_MSG_REQUEST_SUCCESS 81 #define PROXY_SSH_MSG_REQUEST_FAILURE 82 /* Channel message types */ #define PROXY_SSH_MSG_CHANNEL_OPEN 90 #define PROXY_SSH_MSG_CHANNEL_OPEN_CONFIRMATION 91 #define PROXY_SSH_MSG_CHANNEL_OPEN_FAILURE 92 #define PROXY_SSH_MSG_CHANNEL_WINDOW_ADJUST 93 #define PROXY_SSH_MSG_CHANNEL_DATA 94 #define PROXY_SSH_MSG_CHANNEL_EXTENDED_DATA 95 #define PROXY_SSH_MSG_CHANNEL_EOF 96 #define PROXY_SSH_MSG_CHANNEL_CLOSE 97 #define PROXY_SSH_MSG_CHANNEL_REQUEST 98 #define PROXY_SSH_MSG_CHANNEL_SUCCESS 99 #define PROXY_SSH_MSG_CHANNEL_FAILURE 100 /* Channel extended data types */ #define PROXY_SSH_MSG_CHANNEL_EXTENDED_DATA_TYPE_STDERR 1 /* SSH Disconnect reason codes */ #define PROXY_SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT 1 #define PROXY_SSH_DISCONNECT_PROTOCOL_ERROR 2 #define PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED 3 #define PROXY_SSH_DISCONNECT_RESERVED 4 #define PROXY_SSH_DISCONNECT_MAC_ERROR 5 #define PROXY_SSH_DISCONNECT_COMPRESSION_ERROR 6 #define PROXY_SSH_DISCONNECT_SERVICE_NOT_AVAILABLE 7 #define PROXY_SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED 8 #define PROXY_SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE 9 #define PROXY_SSH_DISCONNECT_CONNECTION_LOST 10 #define PROXY_SSH_DISCONNECT_BY_APPLICATION 11 #define PROXY_SSH_DISCONNECT_TOO_MANY_CONNECTIONS 12 #define PROXY_SSH_DISCONNECT_AUTH_CANCELLED_BY_USER 13 #define PROXY_SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE 14 #define PROXY_SSH_DISCONNECT_ILLEGAL_USER_NAME 15 #define PROXY_SSH_ID_PREFIX "SSH-2.0-" #define PROXY_SSH_ID_DEFAULT_STRING PROXY_SSH_ID_PREFIX MOD_PROXY_VERSION #endif /* MOD_PROXY_SSH_SSH2_H */ proftpd-mod_proxy-0.9.5/include/proxy/ssh/umac.h000066400000000000000000000042201475737016700217370ustar00rootroot00000000000000/* ----------------------------------------------------------------------- * * umac.h -- C Implementation UMAC Message Authentication * * Version 0.93a of rfc4418.txt -- 2006 July 14 * * For a full description of UMAC message authentication see the UMAC * world-wide-web page at http://www.cs.ucdavis.edu/~rogaway/umac * Please report bugs and suggestions to the UMAC webpage. * * Copyright (c) 1999-2004 Ted Krovetz * * Permission to use, copy, modify, and distribute this software and * its documentation for any purpose and with or without fee, is hereby * granted provided that the above copyright notice appears in all copies * and in supporting documentation, and that the name of the copyright * holder not be used in advertising or publicity pertaining to * distribution of the software without specific, written prior permission. * * Comments should be directed to Ted Krovetz (tdk@acm.org) * * ---------------------------------------------------------------------- */ #ifndef MOD_PROXY_SSH_UMAC_H #define MOD_PROXY_SSH_UMAC_H struct umac_ctx *proxy_ssh_umac_alloc(void); struct umac_ctx *proxy_ssh_umac_new(const unsigned char key[]); void proxy_ssh_umac_init(struct umac_ctx *ctx, const unsigned char key[]); int proxy_ssh_umac_reset(struct umac_ctx *ctx); int proxy_ssh_umac_update(struct umac_ctx *ctx, const unsigned char *input, long len); int proxy_ssh_umac_final(struct umac_ctx *ctx, unsigned char tag[], const unsigned char nonce[8]); int proxy_ssh_umac_delete(struct umac_ctx *ctx); struct umac_ctx *proxy_ssh_umac128_alloc(void); struct umac_ctx *proxy_ssh_umac128_new(const unsigned char key[]); void proxy_ssh_umac128_init(struct umac_ctx *ctx, const unsigned char key[]); int proxy_ssh_umac128_reset(struct umac_ctx *ctx); int proxy_ssh_umac128_update(struct umac_ctx *ctx, const unsigned char *input, long len); int proxy_ssh_umac128_final(struct umac_ctx *ctx, unsigned char tag[], const unsigned char nonce[8]); int proxy_ssh_umac128_delete(struct umac_ctx *ctx); #endif /* MOD_PROXY_SSH_UMAC_H */ proftpd-mod_proxy-0.9.5/include/proxy/ssh/utf8.h000066400000000000000000000027751475737016700217150ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH UTF8 API * Copyright (c) 2021 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_SSH_UTF8_H #define MOD_PROXY_SSH_UTF8_H #if defined(PR_USE_OPENSSL) char *proxy_ssh_utf8_decode_text(pool *p, const char *text); char *proxy_ssh_utf8_encode_text(pool *p, const char *text); /* Set the local charset to use explicitly, rather than relying on * nl_langinfo(3). */ int proxy_ssh_utf8_set_charset(const char *charset); int proxy_ssh_utf8_init(void); int proxy_ssh_utf8_free(void); #endif /* PR_USE_OPENSSL */ #endif /* MOD_PROXY_SSH_UTF8_H */ proftpd-mod_proxy-0.9.5/include/proxy/str.h000066400000000000000000000023031475737016700210250ustar00rootroot00000000000000/* * ProFTPD - mod_proxy String API * Copyright (c) 2020 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_STR_H #define MOD_PROXY_STR_H #include "mod_proxy.h" char *proxy_strnstr(const char *s1, const char *s2, size_t len); #endif /* MOD_PROXY_STR_H */ proftpd-mod_proxy-0.9.5/include/proxy/tls.h000066400000000000000000000105741475737016700210300ustar00rootroot00000000000000/* * ProFTPD - mod_proxy TLS API * Copyright (c) 2015-2024 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_TLS_H #define MOD_PROXY_TLS_H #include "mod_proxy.h" #include "proxy/session.h" #ifdef PR_USE_OPENSSL # include # include # include # include # include # include # include # include # include # if OPENSSL_VERSION_NUMBER > 0x000907000L # if defined(PR_USE_OPENSSL_ENGINE) # include # endif /* PR_USE_OPENSSL_ENGINE */ # include # endif # ifdef PR_USE_OPENSSL_ECC # include # include # endif /* PR_USE_OPENSSL_ECC */ #endif /* ProxyTLSEngine values */ #define PROXY_TLS_ENGINE_ON 1 #define PROXY_TLS_ENGINE_OFF 2 #define PROXY_TLS_ENGINE_AUTO 3 #define PROXY_TLS_ENGINE_IMPLICIT 4 #define PROXY_TLS_ENGINE_MATCH_CLIENT 5 #define PROXY_TLS_IMPLICIT_FTPS_PORT 990 /* ProxyTLSOptions values. NOTE: Make sure these do NOT collide with existing * PROXY_OPT_ values defined in mod_proxy.h. */ #define PROXY_TLS_OPT_ENABLE_DIAGS 0x0100 #define PROXY_TLS_OPT_NO_SESSION_CACHE 0x0200 #define PROXY_TLS_OPT_NO_SESSION_TICKETS 0x0400 #define PROXY_TLS_OPT_ALLOW_WEAK_SECURITY 0x0800 /* ProxyTLSProtocol handling */ #define PROXY_TLS_PROTO_SSL_V3 0x0001 #define PROXY_TLS_PROTO_TLS_V1 0x0002 #define PROXY_TLS_PROTO_TLS_V1_1 0x0004 #define PROXY_TLS_PROTO_TLS_V1_2 0x0008 #define PROXY_TLS_PROTO_TLS_V1_3 0x0010 #if defined(PR_USE_OPENSSL) && \ OPENSSL_VERSION_NUMBER >= 0x10001000L # if defined(TLS1_3_VERSION) # define PROXY_TLS_PROTO_DEFAULT (PROXY_TLS_PROTO_TLS_V1|PROXY_TLS_PROTO_TLS_V1_1|PROXY_TLS_PROTO_TLS_V1_2|PROXY_TLS_PROTO_TLS_V1_3) # else # define PROXY_TLS_PROTO_DEFAULT (PROXY_TLS_PROTO_TLS_V1|PROXY_TLS_PROTO_TLS_V1_1|PROXY_TLS_PROTO_TLS_V1_2) # endif /* TLS1_3_VERSION */ #else # define PROXY_TLS_PROTO_DEFAULT (PROXY_TLS_PROTO_TLS_V1) #endif /* OpenSSL 1.0.1 or later */ /* This is used for e.g. "ProxyTLSProtocol ALL -SSLv3 ...". */ #define PROXY_TLS_PROTO_ALL (PROXY_TLS_PROTO_SSL_V3|PROXY_TLS_PROTO_TLS_V1|PROXY_TLS_PROTO_TLS_V1_1|PROXY_TLS_PROTO_TLS_V1_2|PROXY_TLS_PROTO_TLS_V1_3) const char *proxy_tls_get_errors(void); int proxy_tls_init(pool *p, const char *tables_dir, int flags); int proxy_tls_free(pool *p); int proxy_tls_sess_init(pool *p, struct proxy_session *proxy_sess, int flags); int proxy_tls_sess_free(pool *p); /* Set whether data transfers require TLS protection, based on e.g. clients' * PROT commands. */ int proxy_tls_set_data_prot(int); /* Programmatically set the ProxyTLSEngine value. */ int proxy_tls_set_tls(int); /* Returns the ProxyTLSEngine value; see above. */ int proxy_tls_using_tls(void); /* Implements the ProxyTLSEngine MatchClient functionality. */ int proxy_tls_match_client_tls(void); /* Defines the datastore interface. */ struct proxy_tls_datastore { #ifdef PR_USE_OPENSSL int (*add_sess)(pool *p, void *dsh, const char *key, SSL_SESSION *sess); int (*remove_sess)(pool *p, void *dsh, const char *key); SSL_SESSION *(*get_sess)(pool *p, void *dsh, const char *key); int (*count_sess)(pool *p, void *dsh); #endif /* PR_USE_OPENSSL */ int (*init)(pool *p, const char *path, int flags); void *(*open)(pool *p, const char *path, unsigned long opts); int (*close)(pool *p, void *dsh); /* Datastore handle returned by the open callback. */ void *dsh; }; #endif /* MOD_PROXY_TLS_H */ proftpd-mod_proxy-0.9.5/include/proxy/tls/000077500000000000000000000000001475737016700206505ustar00rootroot00000000000000proftpd-mod_proxy-0.9.5/include/proxy/tls/db.h000066400000000000000000000024121475737016700214050ustar00rootroot00000000000000/* * ProFTPD - mod_proxy TLS Database API * Copyright (c) 2017 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_TLS_DB_H #define MOD_PROXY_TLS_DB_H #include "mod_proxy.h" #include "proxy/tls.h" int proxy_tls_db_as_datastore(struct proxy_tls_datastore *ds, void *ds_data, size_t ds_datasz); #endif /* MOD_PROXY_TLS_DB_H */ proftpd-mod_proxy-0.9.5/include/proxy/tls/redis.h000066400000000000000000000024231475737016700221300ustar00rootroot00000000000000/* * ProFTPD - mod_proxy TLS Redis API * Copyright (c) 2017 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_TLS_REDIS_H #define MOD_PROXY_TLS_REDIS_H #include "mod_proxy.h" #include "proxy/tls.h" int proxy_tls_redis_as_datastore(struct proxy_tls_datastore *ds, void *ds_data, size_t ds_datasz); #endif /* MOD_PROXY_TLS_REDIS_H */ proftpd-mod_proxy-0.9.5/include/proxy/uri.h000066400000000000000000000024071475737016700210210ustar00rootroot00000000000000/* * ProFTPD - mod_proxy URI API * Copyright (c) 2012-2016 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_URI_H #define MOD_PROXY_URI_H #include "mod_proxy.h" int proxy_uri_parse(pool *p, const char *uri, char **scheme, char **host, unsigned int *port, char **username, char **password); #endif /* MOD_PROXY_URI_H */ proftpd-mod_proxy-0.9.5/install-sh000077500000000000000000000324641475737016700172570ustar00rootroot00000000000000#!/bin/sh # install - install a program, script, or datafile scriptversion=2006-12-25.00 # This originates from X11R5 (mit/util/scripts/install.sh), which was # later released in X11R6 (xc/config/util/install.sh) with the # following copyright and license. # # Copyright (C) 1994 X Consortium # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to # deal in the Software without restriction, including without limitation the # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or # sell copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN # AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC- # TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # # Except as contained in this notice, the name of the X Consortium shall not # be used in advertising or otherwise to promote the sale, use or other deal- # ings in this Software without prior written authorization from the X Consor- # tium. # # # FSF changes to this file are in the public domain. # # Calling this script install-sh is preferred over install.sh, to prevent # `make' implicit rules from creating a file called install from it # when there is no Makefile. # # This script is compatible with the BSD install script, but was written # from scratch. nl=' ' IFS=" "" $nl" # set DOITPROG to echo to test this script # Don't use :- since 4.3BSD and earlier shells don't like it. doit=${DOITPROG-} if test -z "$doit"; then doit_exec=exec else doit_exec=$doit fi # Put in absolute file names if you don't have them in your path; # or use environment vars. chgrpprog=${CHGRPPROG-chgrp} chmodprog=${CHMODPROG-chmod} chownprog=${CHOWNPROG-chown} cmpprog=${CMPPROG-cmp} cpprog=${CPPROG-cp} mkdirprog=${MKDIRPROG-mkdir} mvprog=${MVPROG-mv} rmprog=${RMPROG-rm} stripprog=${STRIPPROG-strip} posix_glob='?' initialize_posix_glob=' test "$posix_glob" != "?" || { if (set -f) 2>/dev/null; then posix_glob= else posix_glob=: fi } ' posix_mkdir= # Desired mode of installed file. mode=0755 chgrpcmd= chmodcmd=$chmodprog chowncmd= mvcmd=$mvprog rmcmd="$rmprog -f" stripcmd= src= dst= dir_arg= dst_arg= copy_on_change=false no_target_directory= usage="\ Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE or: $0 [OPTION]... SRCFILES... DIRECTORY or: $0 [OPTION]... -t DIRECTORY SRCFILES... or: $0 [OPTION]... -d DIRECTORIES... In the 1st form, copy SRCFILE to DSTFILE. In the 2nd and 3rd, copy all SRCFILES to DIRECTORY. In the 4th, create DIRECTORIES. Options: --help display this help and exit. --version display version info and exit. -c (ignored) -C install only if different (preserve the last data modification time) -d create directories instead of installing files. -g GROUP $chgrpprog installed files to GROUP. -m MODE $chmodprog installed files to MODE. -o USER $chownprog installed files to USER. -s $stripprog installed files. -t DIRECTORY install into DIRECTORY. -T report an error if DSTFILE is a directory. Environment variables override the default commands: CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG RMPROG STRIPPROG " while test $# -ne 0; do case $1 in -c) ;; -C) copy_on_change=true;; -d) dir_arg=true;; -g) chgrpcmd="$chgrpprog $2" shift;; --help) echo "$usage"; exit $?;; -m) mode=$2 case $mode in *' '* | *' '* | *' '* | *'*'* | *'?'* | *'['*) echo "$0: invalid mode: $mode" >&2 exit 1;; esac shift;; -o) chowncmd="$chownprog $2" shift;; -s) stripcmd=$stripprog;; -t) dst_arg=$2 shift;; -T) no_target_directory=true;; --version) echo "$0 $scriptversion"; exit $?;; --) shift break;; -*) echo "$0: invalid option: $1" >&2 exit 1;; *) break;; esac shift done if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then # When -d is used, all remaining arguments are directories to create. # When -t is used, the destination is already specified. # Otherwise, the last argument is the destination. Remove it from $@. for arg do if test -n "$dst_arg"; then # $@ is not empty: it contains at least $arg. set fnord "$@" "$dst_arg" shift # fnord fi shift # arg dst_arg=$arg done fi if test $# -eq 0; then if test -z "$dir_arg"; then echo "$0: no input file specified." >&2 exit 1 fi # It's OK to call `install-sh -d' without argument. # This can happen when creating conditional directories. exit 0 fi if test -z "$dir_arg"; then trap '(exit $?); exit' 1 2 13 15 # Set umask so as not to create temps with too-generous modes. # However, 'strip' requires both read and write access to temps. case $mode in # Optimize common cases. *644) cp_umask=133;; *755) cp_umask=22;; *[0-7]) if test -z "$stripcmd"; then u_plus_rw= else u_plus_rw='% 200' fi cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;; *) if test -z "$stripcmd"; then u_plus_rw= else u_plus_rw=,u+rw fi cp_umask=$mode$u_plus_rw;; esac fi for src do # Protect names starting with `-'. case $src in -*) src=./$src;; esac if test -n "$dir_arg"; then dst=$src dstdir=$dst test -d "$dstdir" dstdir_status=$? else # Waiting for this to be detected by the "$cpprog $src $dsttmp" command # might cause directories to be created, which would be especially bad # if $src (and thus $dsttmp) contains '*'. if test ! -f "$src" && test ! -d "$src"; then echo "$0: $src does not exist." >&2 exit 1 fi if test -z "$dst_arg"; then echo "$0: no destination specified." >&2 exit 1 fi dst=$dst_arg # Protect names starting with `-'. case $dst in -*) dst=./$dst;; esac # If destination is a directory, append the input filename; won't work # if double slashes aren't ignored. if test -d "$dst"; then if test -n "$no_target_directory"; then echo "$0: $dst_arg: Is a directory" >&2 exit 1 fi dstdir=$dst dst=$dstdir/`basename "$src"` dstdir_status=0 else # Prefer dirname, but fall back on a substitute if dirname fails. dstdir=` (dirname "$dst") 2>/dev/null || expr X"$dst" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$dst" : 'X\(//\)[^/]' \| \ X"$dst" : 'X\(//\)$' \| \ X"$dst" : 'X\(/\)' \| . 2>/dev/null || echo X"$dst" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q' ` test -d "$dstdir" dstdir_status=$? fi fi obsolete_mkdir_used=false if test $dstdir_status != 0; then case $posix_mkdir in '') # Create intermediate dirs using mode 755 as modified by the umask. # This is like FreeBSD 'install' as of 1997-10-28. umask=`umask` case $stripcmd.$umask in # Optimize common cases. *[2367][2367]) mkdir_umask=$umask;; .*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;; *[0-7]) mkdir_umask=`expr $umask + 22 \ - $umask % 100 % 40 + $umask % 20 \ - $umask % 10 % 4 + $umask % 2 `;; *) mkdir_umask=$umask,go-w;; esac # With -d, create the new directory with the user-specified mode. # Otherwise, rely on $mkdir_umask. if test -n "$dir_arg"; then mkdir_mode=-m$mode else mkdir_mode= fi posix_mkdir=false case $umask in *[123567][0-7][0-7]) # POSIX mkdir -p sets u+wx bits regardless of umask, which # is incompatible with FreeBSD 'install' when (umask & 300) != 0. ;; *) tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$ trap 'ret=$?; rmdir "$tmpdir/d" "$tmpdir" 2>/dev/null; exit $ret' 0 if (umask $mkdir_umask && exec $mkdirprog $mkdir_mode -p -- "$tmpdir/d") >/dev/null 2>&1 then if test -z "$dir_arg" || { # Check for POSIX incompatibilities with -m. # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or # other-writeable bit of parent directory when it shouldn't. # FreeBSD 6.1 mkdir -m -p sets mode of existing directory. ls_ld_tmpdir=`ls -ld "$tmpdir"` case $ls_ld_tmpdir in d????-?r-*) different_mode=700;; d????-?--*) different_mode=755;; *) false;; esac && $mkdirprog -m$different_mode -p -- "$tmpdir" && { ls_ld_tmpdir_1=`ls -ld "$tmpdir"` test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1" } } then posix_mkdir=: fi rmdir "$tmpdir/d" "$tmpdir" else # Remove any dirs left behind by ancient mkdir implementations. rmdir ./$mkdir_mode ./-p ./-- 2>/dev/null fi trap '' 0;; esac;; esac if $posix_mkdir && ( umask $mkdir_umask && $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir" ) then : else # The umask is ridiculous, or mkdir does not conform to POSIX, # or it failed possibly due to a race condition. Create the # directory the slow way, step by step, checking for races as we go. case $dstdir in /*) prefix='/';; -*) prefix='./';; *) prefix='';; esac eval "$initialize_posix_glob" oIFS=$IFS IFS=/ $posix_glob set -f set fnord $dstdir shift $posix_glob set +f IFS=$oIFS prefixes= for d do test -z "$d" && continue prefix=$prefix$d if test -d "$prefix"; then prefixes= else if $posix_mkdir; then (umask=$mkdir_umask && $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break # Don't fail if two instances are running concurrently. test -d "$prefix" || exit 1 else case $prefix in *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;; *) qprefix=$prefix;; esac prefixes="$prefixes '$qprefix'" fi fi prefix=$prefix/ done if test -n "$prefixes"; then # Don't fail if two instances are running concurrently. (umask $mkdir_umask && eval "\$doit_exec \$mkdirprog $prefixes") || test -d "$dstdir" || exit 1 obsolete_mkdir_used=true fi fi fi if test -n "$dir_arg"; then { test -z "$chowncmd" || $doit $chowncmd "$dst"; } && { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } && { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false || test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1 else # Make a couple of temp file names in the proper directory. dsttmp=$dstdir/_inst.$$_ rmtmp=$dstdir/_rm.$$_ # Trap to clean up those temp files at exit. trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0 # Copy the file name to the temp name. (umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") && # and set any options; do chmod last to preserve setuid bits. # # If any of these fail, we abort the whole thing. If we want to # ignore errors from any of these, just make sure not to ignore # errors from the above "$doit $cpprog $src $dsttmp" command. # { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } && { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } && { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } && { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } && # If -C, don't bother to copy if it wouldn't change the file. if $copy_on_change && old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` && new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` && eval "$initialize_posix_glob" && $posix_glob set -f && set X $old && old=:$2:$4:$5:$6 && set X $new && new=:$2:$4:$5:$6 && $posix_glob set +f && test "$old" = "$new" && $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1 then rm -f "$dsttmp" else # Rename the file to the real destination. $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null || # The rename failed, perhaps because mv can't rename something else # to itself, or perhaps because mv is so ancient that it does not # support -f. { # Now remove or move aside any old file at destination location. # We try this two ways since rm can't unlink itself on some # systems and the destination file might be busy for other # reasons. In this case, the final cleanup might fail but the new # file should still install successfully. { test ! -f "$dst" || $doit $rmcmd -f "$dst" 2>/dev/null || { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null && { $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; } } || { echo "$0: cannot unlink or rename $dst" >&2 (exit 1); exit 1 } } && # Now rename the file to the real destination. $doit $mvcmd "$dsttmp" "$dst" } fi || exit 1 trap '' 0 fi done # Local variables: # eval: (add-hook 'write-file-hooks 'time-stamp) # time-stamp-start: "scriptversion=" # time-stamp-format: "%:y-%02m-%02d.%02H" # time-stamp-end: "$" # End: proftpd-mod_proxy-0.9.5/lib/000077500000000000000000000000001475737016700160105ustar00rootroot00000000000000proftpd-mod_proxy-0.9.5/lib/proxy/000077500000000000000000000000001475737016700171715ustar00rootroot00000000000000proftpd-mod_proxy-0.9.5/lib/proxy/conn.c000066400000000000000000001520541475737016700203010ustar00rootroot00000000000000/* * ProFTPD - mod_proxy conn implementation * Copyright (c) 2012-2025 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #ifdef HAVE_SYS_UIO_H # include #endif /* HAVE_SYS_UIO_H */ #include "proxy/conn.h" #include "proxy/dns.h" #include "proxy/netio.h" #include "proxy/inet.h" #include "proxy/session.h" #include "proxy/tls.h" #include "proxy/uri.h" struct proxy_conn { pool *pconn_pool; const char *pconn_uri; const char *pconn_proto; const char *pconn_host; const char *pconn_hostport; int pconn_port; int pconn_tls; int pconn_use_dns_srv; int pconn_use_dns_txt; /* These are only used for DNS SRV, DNS TXT URLs. */ int pconn_dns_ttl; int pconn_dns_timer_id; /* Note that these are deliberately NOT 'const', so that they can be * scrubbed in the per-session memory space, once backend authentication * has occurred. */ char *pconn_username; char *pconn_password; const pr_netaddr_t *pconn_addr; array_header *pconn_addrs; }; static const char *supported_protocols[] = { "ftp", "ftp+srv", "ftp+txt", "ftps", "ftps+srv", "ftps+txt", "sftp", "sftp+srv", "sftp+txt", NULL }; /* PROXY protocol V2 */ #define PROXY_PROTOCOL_V2_SIGLEN 12 #define PROXY_PROTOCOL_V2_HDRLEN 16 #define PROXY_PROTOCOL_V2_TRANSPORT_STREAM 0x01 #define PROXY_PROTOCOL_V2_FAMILY_INET 0x10 #define PROXY_PROTOCOL_V2_FAMILY_INET6 0x20 #define PROXY_PROTOCOL_V2_ADDRLEN_INET (4 + 4 + 2 + 2) #define PROXY_PROTOCOL_V2_ADDRLEN_INET6 (16 + 16 + 2 + 2) static uint8_t proxy_protocol_v2_sig[PROXY_PROTOCOL_V2_SIGLEN] = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"; #define PROXY_PROTOCOL_V2_TLV_ALPN 0x01 #define PROXY_PROTOCOL_V2_TLV_AUTHORITY 0x02 #define PROXY_PROTOCOL_V2_TLV_UNIQUE_ID 0x05 #define PROXY_PROTOCOL_V2_TLV_TLS 0x20 #define PROXY_PROTOCOL_V2_TLV_TLS_VERSION 0x21 #define PROXY_PROTOCOL_V2_TLV_TLS_CN 0x22 #define PROXY_PROTOCOL_V2_TLV_TLS_CIPHER 0x23 #define PROXY_PROTOCOL_V2_TLV_TLS_SIG_ALGO 0x24 #define PROXY_PROTOCOL_V2_TLV_TLS_KEY_ALGO 0x25 static const char *trace_channel = "proxy.conn"; static int supported_protocol(const char *proto) { register unsigned int i; for (i = 0; supported_protocols[i] != NULL; i++) { if (strcmp(proto, supported_protocols[i]) == 0) { return 0; } } errno = ENOENT; return -1; } int proxy_conn_connect_timeout_cb(CALLBACK_FRAME) { const struct proxy_session *proxy_sess; const pr_netaddr_t *server_addr; proxy_sess = pr_table_get(session.notes, "mod_proxy.proxy-session", NULL); server_addr = pr_table_get(session.notes, "mod_proxy.proxy-connect-address", NULL); if (proxy_sess == NULL || server_addr == NULL) { /* Do not restart the timer. */ return 0; } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "timed out connecting to %s:%d after %d %s", pr_netaddr_get_ipstr(server_addr), ntohs(pr_netaddr_get_port(server_addr)), proxy_sess->connect_timeout, proxy_sess->connect_timeout != 1 ? "seconds" : "second"); pr_event_generate("mod_proxy.timeout-connect", NULL); #if 0 /* XXX We might not want to disconnect the frontend client here, right? */ pr_log_pri(PR_LOG_NOTICE, "%s", "Connect timed out, disconnected"); pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_TIMEOUT, "ProxyTimeoutConnect"); #endif /* Do not restart the timer. */ return 0; } static struct proxy_conn *proxy_conn_get_addrs(pool *p, const char *uri, struct proxy_conn *pconn) { pr_netaddr_t *pconn_addr; pconn_addr = (pr_netaddr_t *) pr_netaddr_get_addr(pconn->pconn_pool, pconn->pconn_host, &(pconn->pconn_addrs)); if (pconn_addr == NULL) { pr_trace_msg(trace_channel, 2, "unable to resolve '%s' from URI '%s': %s", pconn->pconn_host, uri, strerror(errno)); (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to resolve '%s' from URI '%s'", pconn->pconn_host, uri); errno = EINVAL; return NULL; } if (pr_netaddr_set_port2(pconn_addr, pconn->pconn_port) < 0) { int xerrno = errno; pr_trace_msg(trace_channel, 3, "unable to set port %d from URI '%s': %s", pconn->pconn_port, uri, strerror(xerrno)); (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to set port %d from URI '%s': %s", pconn->pconn_port, uri, strerror(xerrno)); errno = EINVAL; return NULL; } pconn->pconn_addr = pconn_addr; if (pconn->pconn_addrs != NULL) { register unsigned int i; pr_netaddr_t **elts; elts = pconn->pconn_addrs->elts; for (i = 0; i < pconn->pconn_addrs->nelts; i++) { pr_netaddr_t *elt; elt = elts[i]; if (pr_netaddr_set_port2(elt, pconn->pconn_port) < 0) { pr_trace_msg(trace_channel, 3, "unable to set port %d from URI '%s': %s", pconn->pconn_port, uri, strerror(errno)); } } } return pconn; } static struct proxy_conn *proxy_conn_use_dns_srv_addrs(pool *p, const char *uri, struct proxy_conn *pconn, unsigned int flags) { int res; const char *name; proxy_dns_type_e dns_type = PROXY_DNS_SRV; array_header *resp = NULL; uint32_t srv_ttl = 0; name = pconn->pconn_host; res = proxy_dns_resolve(pconn->pconn_pool, name, dns_type, &resp, &srv_ttl); if (res > 0) { pr_netaddr_t **elts, *first_addr; elts = resp->elts; /* Slightly naughty way to pop the first address of the array. */ first_addr = elts[0]; resp->elts = &(elts[1]); resp->nelts--; pconn->pconn_addr = first_addr; pconn->pconn_port = ntohs(pr_netaddr_get_port(first_addr)); pconn->pconn_addrs = resp; pconn->pconn_dns_ttl = (int) srv_ttl; if (flags & PROXY_CONN_CREATE_FL_USE_DNS_TTL) { /* XXX TODO: Schedule timer for re-resolving URL on TTL. * * The existing Timer API does not provide room for custom "user data" * pointers; need to fix that. In the mean time, we'll just need to track * things ourselves with a lookup table: timer ID -> pconn. * * This has the advantage of providing a way to iterate through the table, * removing all timer IDs (then destroying the table) in a session * process. * * What memory pool should be used for this table, that would be available * at startup time? proxy_pool? * * pconn->pconn_dns_timer_id = pr_timer_add(pconn->pconn_dns_ttl, -1, * &proxy_module, proxy_conn_resolve_cb, ...); */ } return pconn; } /* Always fall back to normal name resolution. */ return proxy_conn_get_addrs(p, uri, pconn); } static struct proxy_conn *proxy_conn_use_dns_txt_addrs(pool *p, const char *uri, struct proxy_conn *pconn, unsigned int flags) { int res; const char *name; proxy_dns_type_e dns_type = PROXY_DNS_TXT; array_header *resp = NULL; name = pconn->pconn_host; res = proxy_dns_resolve(p, name, dns_type, &resp, NULL); if (res > 0) { register unsigned int i; const char **elts; elts = resp->elts; for (i = 0; i < resp->nelts; i++) { const char *elt; char *scheme, *host; unsigned int port; int str_flags = PR_STR_FL_IGNORE_CASE; struct proxy_conn *elt_pconn; elt = elts[i]; /* Many domains have multiple TXT records, for SPF, domain validation, * etc. So we are only interested in any TXT records are that valid * (to us) URLs. */ res = proxy_uri_parse(p, elt, &scheme, &host, &port, NULL, NULL); if (res < 0) { pr_trace_msg(trace_channel, 19, "skipping non-URL TXT record '%s' discovered for '%s'", elt, uri); continue; } /* If the URL found in a TXT record itself uses a DNS SRV or TXT * variant, skip it. That way lies circular madness. */ if (pr_strnrstr(scheme, 0, "+srv", 0, str_flags) == TRUE || pr_strnrstr(scheme, 0, "+txt", 0, str_flags) == TRUE) { pr_trace_msg(trace_channel, 19, "skipping URL TXT record '%s' discovered for '%s'", elt, uri); continue; } elt_pconn = (struct proxy_conn *) proxy_conn_create(p, elt, 0); if (elt_pconn != NULL) { destroy_pool(pconn->pconn_pool); return elt_pconn; } } } /* Always fall back to normal name resolution. */ return proxy_conn_get_addrs(p, uri, pconn); } const struct proxy_conn *proxy_conn_create(pool *p, const char *uri, unsigned int flags) { int res, xerrno; int use_dns_srv = FALSE, use_dns_txt = FALSE, use_tls = PROXY_TLS_ENGINE_AUTO; char *ptr = NULL; char hostport[512], *proto, *remote_host, *username = NULL, *password = NULL; unsigned int remote_port; struct proxy_conn *pconn, *pconn2; pool *pconn_pool; if (p == NULL || uri == NULL) { errno = EINVAL; return NULL; } res = proxy_uri_parse(p, uri, &proto, &remote_host, &remote_port, &username, &password); if (res < 0) { return NULL; } if (supported_protocol(proto) < 0) { pr_trace_msg(trace_channel, 4, "unsupported protocol '%s' in URI '%.100s'", proto, uri); errno = EPERM; return NULL; } if (strcmp(proto, "ftps") == 0 || strncmp(proto, "ftps+", 5) == 0) { /* If the 'ftps' scheme is used, then FTPS is REQUIRED for connections * to this server. */ use_tls = PROXY_TLS_ENGINE_ON; /* We automatically (and only) use implicit FTPS for port 990. Note that * we do NOT support implicit FTPS for URLs using DNS SRV, TXT. */ if (strcmp(proto, "ftps") == 0 && remote_port == PROXY_TLS_IMPLICIT_FTPS_PORT) { use_tls = PROXY_TLS_ENGINE_IMPLICIT; } } else if (strcmp(proto, "sftp") == 0 || strncmp(proto, "sftp+", 5) == 0) { /* As might be obvious, do not try to use TLS against an SSH2/SFTP * server. */ use_tls = PROXY_TLS_ENGINE_OFF; } if (pr_strnrstr(proto, 0, "+srv", 0, PR_STR_FL_IGNORE_CASE) == TRUE) { use_dns_srv = TRUE; } if (pr_strnrstr(proto, 0, "+txt", 0, PR_STR_FL_IGNORE_CASE) == TRUE) { use_dns_txt = TRUE; } memset(hostport, '\0', sizeof(hostport)); snprintf(hostport, sizeof(hostport)-1, "%s:%u", remote_host, remote_port); pconn_pool = pr_pool_create_sz(p, 128); pr_pool_tag(pconn_pool, "proxy connection pool"); pconn = pcalloc(pconn_pool, sizeof(struct proxy_conn)); pconn->pconn_pool = pconn_pool; pconn->pconn_host = pstrdup(pconn_pool, remote_host); pconn->pconn_port = remote_port; pconn->pconn_hostport = pstrdup(pconn_pool, hostport); pconn->pconn_uri = pstrdup(pconn_pool, uri); pconn->pconn_tls = use_tls; pconn->pconn_use_dns_srv = use_dns_srv; pconn->pconn_use_dns_txt = use_dns_txt; /* Adjust the proto (scheme, actually) to account for possible DNS SRV, * TXT usage. */ ptr = strchr(proto, '+'); if (ptr != NULL) { pconn->pconn_proto = pstrndup(pconn_pool, proto, ptr - proto); } else { pconn->pconn_proto = pstrdup(pconn_pool, proto); } if (username != NULL) { pconn->pconn_username = pstrdup(pconn_pool, username); } if (password != NULL) { pconn->pconn_password = pstrdup(pconn_pool, password); } /* Here is where we discover the addresses for this URI. We might use * DNS SRV, DNS TXT, or normal DNS A/AAAA records. */ if (use_dns_srv == TRUE || use_dns_txt == TRUE) { pr_trace_msg(trace_channel, 5, "ignoring port %u from URI '%.100s' since port will be discovered " "from %s DNS records", remote_port, uri, use_dns_srv ? "SRV" : "TXT"); (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "ignoring port %u from URI '%.100s' since port will be discovered " "from %s DNS records", remote_port, uri, use_dns_srv ? "SRV" : "TXT"); } if (use_dns_srv == TRUE) { pconn2 = proxy_conn_use_dns_srv_addrs(p, uri, pconn, flags); xerrno = errno; } else if (use_dns_txt == TRUE) { pconn2 = proxy_conn_use_dns_txt_addrs(p, uri, pconn, flags); xerrno = errno; } else { pconn2 = proxy_conn_get_addrs(p, uri, pconn); xerrno = errno; } if (pconn2 == NULL) { destroy_pool(pconn->pconn_pool); errno = xerrno; return NULL; } return pconn2; } void proxy_conn_free(const struct proxy_conn *pconn) { if (pconn == NULL) { return; } destroy_pool(pconn->pconn_pool); } const pr_netaddr_t *proxy_conn_get_addr(const struct proxy_conn *pconn, array_header **addrs) { if (pconn == NULL) { errno = EINVAL; return NULL; } if (addrs != NULL) { *addrs = pconn->pconn_addrs; } return pconn->pconn_addr; } int proxy_conn_get_dns_ttl(const struct proxy_conn *pconn) { if (pconn == NULL) { errno = EINVAL; return -1; } /* We really only care about/honor DNS TTLs for the DNS SRV. */ if (pconn->pconn_use_dns_srv == FALSE) { errno = EPERM; return -1; } if (pconn->pconn_dns_ttl <= 0) { errno = ENOENT; return -1; } return pconn->pconn_dns_ttl; } const char *proxy_conn_get_host(const struct proxy_conn *pconn) { if (pconn == NULL) { errno = EINVAL; return NULL; } return pconn->pconn_host; } const char *proxy_conn_get_hostport(const struct proxy_conn *pconn) { if (pconn == NULL) { errno = EINVAL; return NULL; } return pconn->pconn_hostport; } int proxy_conn_get_port(const struct proxy_conn *pconn) { if (pconn == NULL) { errno = EINVAL; return -1; } return pconn->pconn_port; } void proxy_conn_clear_username(const struct proxy_conn *pconn) { size_t len; struct proxy_conn *conn; if (pconn == NULL) { return; } if (pconn->pconn_username == NULL) { return; } len = strlen(pconn->pconn_username); conn = (struct proxy_conn *) pconn; pr_memscrub(conn->pconn_username, len); conn->pconn_username = NULL; } const char *proxy_conn_get_username(const struct proxy_conn *pconn) { if (pconn == NULL) { errno = EINVAL; return NULL; } return pconn->pconn_username; } void proxy_conn_clear_password(const struct proxy_conn *pconn) { size_t len; struct proxy_conn *conn; if (pconn == NULL) { return; } if (pconn->pconn_password == NULL) { return; } len = strlen(pconn->pconn_password); conn = (struct proxy_conn *) pconn; pr_memscrub(conn->pconn_password, len); conn->pconn_password = NULL; } const char *proxy_conn_get_password(const struct proxy_conn *pconn) { if (pconn == NULL) { errno = EINVAL; return NULL; } return pconn->pconn_password; } int proxy_conn_get_tls(const struct proxy_conn *pconn) { if (pconn == NULL) { errno = EINVAL; return -1; } return pconn->pconn_tls; } int proxy_conn_use_dns_srv(const struct proxy_conn *pconn) { if (pconn == NULL) { errno = EINVAL; return -1; } return pconn->pconn_use_dns_srv; } int proxy_conn_use_dns_txt(const struct proxy_conn *pconn) { if (pconn == NULL) { errno = EINVAL; return -1; } return pconn->pconn_use_dns_txt; } /* Borrowed from proftpd/src/netaddr.c. */ static int addr_ncmp(const unsigned char *aptr, const unsigned char *bptr, unsigned int masklen) { unsigned char nbits, nbytes; int res; nbytes = masklen / 8; nbits = masklen % 8; res = memcmp(aptr, bptr, nbytes); if (res != 0) { return -1; } if (nbits > 0) { unsigned char abyte, bbyte, mask; abyte = aptr[nbytes]; bbyte = bptr[nbytes]; mask = (0xff << (8 - nbits)) & 0xff; if ((abyte & mask) > (bbyte & mask)) { return 1; } if ((abyte & mask) < (bbyte & mask)) { return -1; } } return 0; } static int is_127_xxx_addr(uint32_t addrno) { uint32_t rfc1918_addrno; rfc1918_addrno = htonl(0x7f000000); return addr_ncmp((const unsigned char *) &addrno, (const unsigned char *) &rfc1918_addrno, 8); } static int netaddr_is_private(const pr_netaddr_t *addr) { if (pr_netaddr_is_rfc1918(addr) == TRUE) { return TRUE; } switch (pr_netaddr_get_family(addr)) { case AF_INET: { uint32_t addrno; addrno = pr_netaddr_get_addrno(addr); if (is_127_xxx_addr(addrno) == 0) { return TRUE; } break; } #if defined(PR_USE_IPV6) case AF_INET6: if (pr_netaddr_is_v4mappedv6(addr) == TRUE) { pool *tmp_pool; pr_netaddr_t *v4addr; int res; tmp_pool = make_sub_pool(proxy_pool); v4addr = pr_netaddr_v6tov4(tmp_pool, addr); res = netaddr_is_private(v4addr); destroy_pool(tmp_pool); return res; } break; #endif /* PR_USE_IPV6 */ } return FALSE; } conn_t *proxy_conn_get_server_conn(pool *p, struct proxy_session *proxy_sess, const pr_netaddr_t *remote_addr) { const pr_netaddr_t *bind_addr = NULL, *local_addr = NULL; const char *remote_ipstr = NULL; unsigned int remote_port; conn_t *server_conn, *ctrl_conn; int res, default_inet_family = 0, xerrno; if (proxy_sess->connect_timeout > 0) { const char *notes_key = "mod_proxy.proxy-connect-address"; proxy_sess->connect_timerno = pr_timer_add(proxy_sess->connect_timeout, -1, &proxy_module, proxy_conn_connect_timeout_cb, "ProxyTimeoutConnect"); (void) pr_table_remove(session.notes, notes_key, NULL); if (pr_table_add(session.notes, notes_key, remote_addr, sizeof(pr_netaddr_t)) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error stashing proxy connect address note: %s", strerror(errno)); } } remote_ipstr = pr_netaddr_get_ipstr(remote_addr); remote_port = ntohs(pr_netaddr_get_port(remote_addr)); /* Check the family of the retrieved address vs what we'll be using * to connect. If there's a mismatch, we need to get an addr with the * matching family. */ if (pr_netaddr_get_family(session.c->local_addr) == pr_netaddr_get_family(remote_addr)) { local_addr = session.c->local_addr; } else { /* In this scenario, the proxy has an IPv6 socket, but the remote/backend * server has an IPv4 (or IPv4-mapped IPv6) address. OR it's the proxy * which has an IPv4 socket, and the remote/backend server has an IPv6 * address. */ if (pr_netaddr_get_family(session.c->local_addr) == AF_INET) { char *ip_str; /* Convert the local address from an IPv4 to an IPv6 addr. */ ip_str = pcalloc(p, INET6_ADDRSTRLEN + 1); snprintf(ip_str, INET6_ADDRSTRLEN, "::ffff:%s", pr_netaddr_get_ipstr(session.c->local_addr)); local_addr = pr_netaddr_get_addr(p, ip_str, NULL); } else { local_addr = pr_netaddr_v6tov4(p, session.c->local_addr); if (local_addr == NULL) { pr_trace_msg(trace_channel, 4, "error converting IPv6 local address %s to IPv4 address: %s", pr_netaddr_get_ipstr(session.c->local_addr), strerror(errno)); if (proxy_sess->src_addr == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "local address '%s' is an IPv6 address, and remote address " "'%s' is an IPv4 address; consider using ProxySourceAddress " "directive to configure an IPv4 address", pr_netaddr_get_ipstr(session.c->local_addr), pr_netaddr_get_ipstr(remote_addr)); } } else { pr_trace_msg(trace_channel, 9, "converted IPv6 local address %s to IPv4 address %s", pr_netaddr_get_ipstr(session.c->local_addr), pr_netaddr_get_ipstr(local_addr)); } } if (local_addr == NULL) { local_addr = session.c->local_addr; } } bind_addr = proxy_sess->src_addr; /* We need to set the default inet family to use for the local address of * our socket. We do NOT want to just use the family of the local address of * our control connection, since we could be listening on an IPv6 address * and want to connect to a backend IPv4 address, or vice versa; see * Issue #272. */ if (bind_addr == NULL) { int remote_family; remote_family = pr_netaddr_get_family(remote_addr); pr_trace_msg(trace_channel, 9, "using %s family for socket local address", remote_family == AF_INET ? "IPv4" : "IPv6"); default_inet_family = pr_inet_set_default_family(p, remote_family); } /* Note: IF mod_proxy is running on localhost, and the connection to be * made is to a public IP address, then this connect(2) attempt would most * likely fail with ENETUNREACH, since localhost is a loopback network, * and of course not reachable from a public IP. Thus we check for this * edge case (which happens often for development). */ if (bind_addr != NULL && pr_netaddr_is_loopback(bind_addr) == TRUE && pr_netaddr_is_loopback(remote_addr) != TRUE) { const char *local_name; const pr_netaddr_t *new_local_addr; local_name = pr_netaddr_get_localaddr_str(p); new_local_addr = pr_netaddr_get_addr(p, local_name, NULL); if (new_local_addr != NULL) { int local_family, remote_family; /* We need to make sure our local address family matches that * of the remote address. */ local_family = pr_netaddr_get_family(new_local_addr); remote_family = pr_netaddr_get_family(remote_addr); if (local_family != remote_family) { pr_netaddr_t *new_addr = NULL; #if defined(PR_USE_IPV6) if (local_family == AF_INET) { new_addr = pr_netaddr_v4tov6(p, new_local_addr); } else { new_addr = pr_netaddr_v6tov4(p, new_local_addr); } #endif /* PR_USE_IPV6 */ if (new_addr != NULL) { new_local_addr = new_addr; } } pr_trace_msg(trace_channel, 14, "%s is a loopback address, and unable to reach %s; using %s instead", pr_netaddr_get_ipstr(bind_addr), remote_ipstr, pr_netaddr_get_ipstr(new_local_addr)); bind_addr = new_local_addr; } } server_conn = pr_inet_create_conn(p, -1, bind_addr, INPORT_ANY, FALSE); xerrno = errno; /* Restore the previous default inet family if necessary. */ if (bind_addr == NULL) { (void) pr_inet_set_default_family(p, default_inet_family); } if (server_conn == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error creating connection to %s: %s", pr_netaddr_get_ipstr(bind_addr), strerror(xerrno)); pr_timer_remove(proxy_sess->connect_timerno, &proxy_module); errno = xerrno; return NULL; } pr_trace_msg(trace_channel, 12, "connecting to backend address %s#%u from %s#%u", remote_ipstr, remote_port, pr_netaddr_get_ipstr(server_conn->local_addr), server_conn->local_port); res = pr_inet_connect_nowait(p, server_conn, remote_addr, ntohs(pr_netaddr_get_port(remote_addr))); if (res < 0) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error starting connect to %s#%u: %s", remote_ipstr, remote_port, strerror(xerrno)); /* If there is a mismatch in public/private addresses for our * source/destination addresses, it might cause a connection error. Check * for those particular errors, and if so, log a suggestion to explicitly * configure an appropriate ProxySourceAddress (Issue #213). */ if (pr_netaddr_get_family(bind_addr) == pr_netaddr_get_family(remote_addr)) { if (netaddr_is_private(bind_addr) == TRUE) { if (netaddr_is_private(remote_addr) != TRUE) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "local address '%s' is a private network address, and remote " "address '%s' is a public address; consider using " "ProxySourceAddress directive to configure a public local address", pr_netaddr_get_ipstr(bind_addr), remote_ipstr); } } else { if (netaddr_is_private(remote_addr) == TRUE) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "local address '%s' is a public address, and remote address '%s' " "is a private network address; consider using ProxySourceAddress " "directive to configure a private local address", pr_netaddr_get_ipstr(bind_addr), remote_ipstr); } } } pr_timer_remove(proxy_sess->connect_timerno, &proxy_module); errno = xerrno; return NULL; } if (res == 0) { pr_netio_stream_t *nstrm; int connected = FALSE, nstrm_mode = PR_NETIO_IO_RD, use_tls; if ((proxy_opts & PROXY_OPT_USE_PROXY_PROTOCOL_V1) || (proxy_opts & PROXY_OPT_USE_PROXY_PROTOCOL_V2)) { /* Rather than waiting for the stream to be readable (because the * other end sent us something), wait for the stream to be writable * so that we can send something to the other end). */ nstrm_mode = PR_NETIO_IO_WR; } use_tls = proxy_tls_using_tls(); if (use_tls == PROXY_TLS_ENGINE_IMPLICIT) { /* For implicit FTPS connections, we will be initiating the TLS * handshake, and thus we need to wait for the stream to be writable. */ nstrm_mode = PR_NETIO_IO_WR; } /* Not yet connected. */ nstrm = proxy_netio_open(p, PR_NETIO_STRM_OTHR, server_conn->listen_fd, nstrm_mode); if (nstrm == NULL) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error opening stream to %s#%u: %s", remote_ipstr, remote_port, strerror(xerrno)); pr_timer_remove(proxy_sess->connect_timerno, &proxy_module); pr_inet_close(p, server_conn); errno = xerrno; return NULL; } proxy_netio_set_poll_interval(nstrm, 1); while (connected == FALSE) { int polled; pr_signals_handle(); polled = proxy_netio_poll(nstrm); switch (polled) { case 1: { /* Aborted, timed out. Note that we shouldn't reach here. */ xerrno = ETIMEDOUT; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error connecting to %s#%u: %s", remote_ipstr, remote_port, strerror(xerrno)); pr_timer_remove(proxy_sess->connect_timerno, &proxy_module); proxy_netio_close(nstrm); pr_inet_close(p, server_conn); errno = xerrno; return NULL; } case -1: { /* Error */ xerrno = nstrm->strm_errno; if (xerrno == 0) { xerrno = errno; } if (xerrno == EINTR) { /* Treat this as a timeout. */ xerrno = ETIMEDOUT; } else if (xerrno == EOF) { xerrno = ECONNREFUSED; } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error connecting to %s#%u: %s", remote_ipstr, remote_port, strerror(xerrno)); pr_timer_remove(proxy_sess->connect_timerno, &proxy_module); proxy_netio_close(nstrm); pr_inet_close(p, server_conn); errno = xerrno; return NULL; } default: { /* Connected */ server_conn->mode = CM_OPEN; pr_timer_remove(proxy_sess->connect_timerno, &proxy_module); pr_table_remove(session.notes, "mod_proxy.proxy-connect-addr", NULL); res = pr_inet_get_conn_info(server_conn, server_conn->listen_fd); if (res < 0) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error obtaining local socket info on fd %d: %s", server_conn->listen_fd, strerror(xerrno)); proxy_netio_close(nstrm); pr_inet_close(p, server_conn); errno = xerrno; return NULL; } proxy_netio_reset_poll_interval(nstrm); connected = TRUE; break; } } } } pr_trace_msg(trace_channel, 5, "successfully connected to %s#%u from %s#%d", remote_ipstr, remote_port, pr_netaddr_get_ipstr(server_conn->local_addr), ntohs(pr_netaddr_get_port(server_conn->local_addr))); ctrl_conn = proxy_inet_openrw(p, server_conn, NULL, PR_NETIO_STRM_CTRL, -1, -1, -1, FALSE); if (ctrl_conn == NULL) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to open control connection to %s#%u: %s", remote_ipstr, remote_port, strerror(xerrno)); pr_inet_close(p, server_conn); errno = xerrno; return NULL; } /* Remember that pr_inet_openrw() makes a copy of the input connection; * we thus do not need server_conn now. */ pr_inet_close(p, server_conn); pr_pool_tag(ctrl_conn->pool, "proxy backend ctrl conn pool"); return ctrl_conn; } const char *proxy_conn_get_uri(const struct proxy_conn *pconn) { if (pconn == NULL) { errno = EINVAL; return NULL; } return pconn->pconn_uri; } int proxy_conn_send_proxy_v1(pool *p, conn_t *conn) { int res, src_port, dst_port; const char *proto, *src_ipstr, *dst_ipstr; pool *sub_pool = NULL; if (p == NULL || conn == NULL) { errno = EINVAL; return -1; } /* "PROXY" "TCP4"|"TCP6"|"UNKNOWN" * session.c->remote_addr session.c->local_addr * session.c->remote_port, session.c->local_port "\r\n" */ if (pr_netaddr_get_family(session.c->remote_addr) == AF_INET && pr_netaddr_get_family(session.c->local_addr) == AF_INET) { proto = "TCP4"; src_ipstr = pr_netaddr_get_ipstr(session.c->remote_addr); src_port = session.c->remote_port; dst_ipstr = pr_netaddr_get_ipstr(session.c->local_addr); dst_port = session.c->local_port; } else { proto = "TCP6"; sub_pool = make_sub_pool(p); if (pr_netaddr_get_family(session.c->remote_addr) == AF_INET) { const char *ipstr; ipstr = pr_netaddr_get_ipstr(session.c->remote_addr); src_ipstr = pstrcat(sub_pool, "::ffff:", ipstr, NULL); } else { src_ipstr = pr_netaddr_get_ipstr(session.c->remote_addr); } src_port = session.c->remote_port; if (pr_netaddr_get_family(session.c->local_addr) == AF_INET) { const char *ipstr; ipstr = pr_netaddr_get_ipstr(session.c->local_addr); dst_ipstr = pstrcat(sub_pool, "::ffff:", ipstr, NULL); } else { dst_ipstr = pr_netaddr_get_ipstr(session.c->local_addr); } dst_port = session.c->local_port; /* What should we do if the entire frontend connection is IPv6, but the * backend server is IPv4? Sending "PROXY TCP6" there may not work as * expected, e.g. the backend server may not want to handle IPv6 addresses * (even though it does not have to); should that be handled using * "PROXY UNKNOWN"? */ if (pr_netaddr_get_family(conn->remote_addr) == AF_INET) { proto = "UNKNOWN"; pr_trace_msg(trace_channel, 9, "client address '%s' and local address '%s' are both IPv6, " "but backend address '%s' is IPv4, using '%s' proto", src_ipstr, dst_ipstr, pr_netaddr_get_ipstr(conn->remote_addr), proto); } } pr_trace_msg(trace_channel, 9, "sending PROXY protocol V1 message: 'PROXY %s %s %s %d %d' to backend", proto, src_ipstr, dst_ipstr, src_port, dst_port); res = proxy_netio_printf(conn->outstrm, "PROXY %s %s %s %d %d\r\n", proto, src_ipstr, dst_ipstr, src_port, dst_port); if (sub_pool != NULL) { destroy_pool(sub_pool); } return res; } static int writev_conn(conn_t *conn, const struct iovec *iov, int iov_count) { int res, xerrno; if (pr_netio_poll(conn->outstrm) < 0) { return -1; } res = writev(conn->wfd, iov, iov_count); xerrno = errno; while (res <= 0) { if (res < 0) { if (xerrno == EINTR) { pr_signals_handle(); if (pr_netio_poll(conn->outstrm) < 0) { return -1; } res = writev(conn->wfd, iov, iov_count); xerrno = errno; continue; } pr_trace_msg(trace_channel, 16, "error writing to client (fd %d): %s", conn->wfd, strerror(xerrno)); errno = errno; return -1; } } session.total_raw_out += res; return res; } static const char *get_v2_tlv_alpn(pool *p) { const char *alpn = NULL; /* Note that in a proxy chain, we will want to also honor * and preserve any ALPN TLV sent via PROXY protocol. */ alpn = pr_table_get(session.notes, "mod_proxy_protocol.alpn", NULL); if (alpn == NULL) { alpn = pstrdup(p, pr_session_get_protocol(0)); } pr_trace_msg(trace_channel, 22, "adding ALPN V2 TLV: '%s'", alpn); return alpn; } static uint16_t add_v2_tlv_alpn(pool *p, struct iovec *v2_iov, unsigned int *v2_niov) { uint8_t *tlv_type; uint16_t *tlv_len, total_len; const char *tlv_val; size_t tlv_valsz = 0; unsigned int niov; tlv_type = pcalloc(p, sizeof(uint8_t)); *tlv_type = PROXY_PROTOCOL_V2_TLV_ALPN; tlv_val = get_v2_tlv_alpn(p); tlv_valsz = strlen(tlv_val); tlv_len = pcalloc(p, sizeof(uint16_t)); *tlv_len = htons(tlv_valsz); niov = *v2_niov; v2_iov[niov].iov_base = (void *) tlv_type; v2_iov[niov].iov_len = sizeof(uint8_t); niov++; v2_iov[niov].iov_base = (void *) tlv_len; v2_iov[niov].iov_len = sizeof(uint16_t); niov++; v2_iov[niov].iov_base = (void *) tlv_val; v2_iov[niov].iov_len = tlv_valsz; /* Make sure to increment niov one more, for the next TLV. */ *v2_niov = niov + 1; total_len = sizeof(uint8_t) + sizeof(uint16_t) + tlv_valsz; return total_len; } static const void *get_v2_tlv_authority(pool *p) { const void *authority = NULL; /* Add the Authority TLV if the client sent an FTP HOST command, or * used TLS SNI. Note that in a proxy chain, we will want to also honor * and preserve any Authority TLV sent via PROXY protocol. */ authority = pr_table_get(session.notes, "mod_proxy_protocol.authority", NULL); if (authority == NULL) { authority = pr_table_get(session.notes, "mod_core.host", NULL); } if (authority == NULL) { authority = pr_table_get(session.notes, "mod_tls.sni", NULL); } return authority; } static uint16_t add_v2_tlv_authority(pool *p, struct iovec *v2_iov, unsigned int *v2_niov) { uint8_t *tlv_type; uint16_t *tlv_len, total_len; const char *tlv_val; size_t tlv_valsz = 0; unsigned int niov; const void *val = NULL; val = get_v2_tlv_authority(p); if (val == NULL) { return 0; } pr_trace_msg(trace_channel, 22, "adding Authority V2 TLV: '%s'", (const char *) val); tlv_type = pcalloc(p, sizeof(uint8_t)); *tlv_type = PROXY_PROTOCOL_V2_TLV_AUTHORITY; tlv_val = pstrdup(p, val); tlv_valsz = strlen(tlv_val); tlv_len = pcalloc(p, sizeof(uint16_t)); *tlv_len = htons(tlv_valsz); niov = *v2_niov; v2_iov[niov].iov_base = (void *) tlv_type; v2_iov[niov].iov_len = sizeof(uint8_t); niov++; v2_iov[niov].iov_base = (void *) tlv_len; v2_iov[niov].iov_len = sizeof(uint16_t); niov++; v2_iov[niov].iov_base = (void *) tlv_val; v2_iov[niov].iov_len = tlv_valsz; /* Make sure to increment niov one more, for the next TLV. */ *v2_niov = niov + 1; total_len = sizeof(uint8_t) + sizeof(uint16_t) + tlv_valsz; return total_len; } static const char *get_v2_tlv_tls_version(pool *p) { const char *tls_version = NULL; tls_version = pr_table_get(session.notes, "mod_proxy_protocol.tls.version", NULL); if (tls_version == NULL) { tls_version = pr_table_get(session.notes, "TLS_PROTOCOL", NULL); } if (tls_version != NULL) { pr_trace_msg(trace_channel, 22, "adding TLS V2 TLV: TLS version '%s'", tls_version); } return tls_version; } static const char *get_v2_tlv_tls_common_name(pool *p) { const char *tls_common_name = NULL; tls_common_name = pr_table_get(session.notes, "mod_proxy_protoocl.tls.common-name", NULL); if (tls_common_name == NULL) { tls_common_name = pr_table_get(session.notes, "TLS_CLIENT_S_DN_CN", NULL); } if (tls_common_name != NULL) { pr_trace_msg(trace_channel, 22, "adding TLS V2 TLV: TLS Common Name '%s'", tls_common_name); } return tls_common_name; } static const char *get_v2_tlv_tls_cipher(pool *p) { const char *tls_cipher = NULL; tls_cipher = pr_table_get(session.notes, "mod_proxy_protocol.tls.cipher", NULL); if (tls_cipher == NULL) { tls_cipher = pr_table_get(session.notes, "TLS_CIPHER", NULL); } if (tls_cipher != NULL) { pr_trace_msg(trace_channel, 22, "adding TLS V2 TLV: TLS Cipher '%s'", tls_cipher); } return tls_cipher; } static const char *get_v2_tlv_tls_sig_algo(pool *p) { const char *tls_sig_algo = NULL; tls_sig_algo = pr_table_get(session.notes, "mod_proxy_protocol.tls.signature-algo", NULL); if (tls_sig_algo == NULL) { tls_sig_algo = pr_table_get(session.notes, "TLS_CLIENT_A_SIG", NULL); } if (tls_sig_algo != NULL) { pr_trace_msg(trace_channel, 22, "adding TLS V2 TLV: TLS Signature Algorithm '%s'", tls_sig_algo); } return tls_sig_algo; } static const char *get_v2_tlv_tls_key_algo(pool *p) { const char *tls_key_algo = NULL; tls_key_algo = pr_table_get(session.notes, "mod_proxy_protocol.tls.key-algo", NULL); if (tls_key_algo == NULL) { tls_key_algo = pr_table_get(session.notes, "TLS_CLIENT_A_KEY", NULL); } if (tls_key_algo != NULL) { pr_trace_msg(trace_channel, 22, "adding TLS V2 TLV: TLS Key Algorithm '%s'", tls_key_algo); } return tls_key_algo; } static uint16_t add_v2_tlv_tls(pool *p, struct iovec *v2_iov, unsigned int *v2_niov) { uint8_t *tlv_type, client = 0; uint16_t *tlv_len, total_len; uint32_t verify; char *tlv_ptr; void *tlv_val; size_t tlv_valsz = 0, valsz = 0; unsigned int niov; const char *tls_version, *tls_common_name, *tls_cipher, *tls_sig_algo, *tls_key_algo; /* Even if FTPS is not in use right now for us, the original client may * have used FTPS at the sort of a proxy chain. Thus we'll add any TLS * TLVs, if they happen to be present. */ tlv_type = pcalloc(p, sizeof(uint8_t)); *tlv_type = PROXY_PROTOCOL_V2_TLV_TLS; /* This is more complicated, due to the nested nature of TLS sub-TLVs. */ tls_version = get_v2_tlv_tls_version(p); tls_common_name = get_v2_tlv_tls_common_name(p); tls_cipher = get_v2_tlv_tls_cipher(p); tls_sig_algo = get_v2_tlv_tls_sig_algo(p); tls_key_algo = get_v2_tlv_tls_key_algo(p); valsz = sizeof(client) + sizeof(verify); /* If any of these TLS settings is present, then we set client to 1 to * indicate that TLS was in fact used. */ if (tls_version != NULL) { client = 0x01; valsz += (sizeof(uint8_t) + sizeof(uint16_t) + strlen(tls_version)); } if (tls_common_name != NULL) { client = 0x01; valsz += (sizeof(uint8_t) + sizeof(uint16_t) + strlen(tls_common_name)); } if (tls_cipher != NULL) { client = 0x01; valsz += (sizeof(uint8_t) + sizeof(uint16_t) + strlen(tls_cipher)); } if (tls_sig_algo != NULL) { client = 0x01; valsz += (sizeof(uint8_t) + sizeof(uint16_t) + strlen(tls_sig_algo)); } if (tls_key_algo != NULL) { client = 0x01; valsz += (sizeof(uint8_t) + sizeof(uint16_t) + strlen(tls_key_algo)); } tlv_ptr = tlv_val = pcalloc(p, valsz); tlv_valsz = valsz; /* Client field: 0x01 to indicate TLS was used, 0x02 when a client cert * was also presented. */ if (tls_common_name != NULL) { client = 0x02; } memcpy(tlv_ptr, &client, sizeof(client)); tlv_ptr += sizeof(client); /* Verify field; 0 if a client cert was presented, otherwise non-zero. */ verify = htonl(1); if (tls_common_name != NULL) { verify = 0; } memcpy(tlv_ptr, &verify, sizeof(verify)); tlv_ptr += sizeof(verify); if (tls_version != NULL) { uint8_t tlv_subtype; uint16_t tlv_sublen; size_t tlv_subvalsz; tlv_subtype = PROXY_PROTOCOL_V2_TLV_TLS_VERSION; tlv_subvalsz = strlen(tls_version); tlv_sublen = htons(tlv_subvalsz); memcpy(tlv_ptr, &tlv_subtype, sizeof(tlv_subtype)); tlv_ptr += sizeof(tlv_subtype); memcpy(tlv_ptr, &tlv_sublen, sizeof(tlv_sublen)); tlv_ptr += sizeof(tlv_sublen); memcpy(tlv_ptr, tls_version, tlv_subvalsz); tlv_ptr += tlv_subvalsz; } if (tls_common_name != NULL) { uint8_t tlv_subtype; uint16_t tlv_sublen; size_t tlv_subvalsz; tlv_subtype = PROXY_PROTOCOL_V2_TLV_TLS_CN; tlv_subvalsz = strlen(tls_common_name); tlv_sublen = htons(tlv_subvalsz); memcpy(tlv_ptr, &tlv_subtype, sizeof(tlv_subtype)); tlv_ptr += sizeof(tlv_subtype); memcpy(tlv_ptr, &tlv_sublen, sizeof(tlv_sublen)); tlv_ptr += sizeof(tlv_sublen); memcpy(tlv_ptr, tls_common_name, tlv_subvalsz); tlv_ptr += tlv_subvalsz; } if (tls_cipher != NULL) { uint8_t tlv_subtype; uint16_t tlv_sublen; size_t tlv_subvalsz; tlv_subtype = PROXY_PROTOCOL_V2_TLV_TLS_CIPHER; tlv_subvalsz = strlen(tls_cipher); tlv_sublen = htons(tlv_subvalsz); memcpy(tlv_ptr, &tlv_subtype, sizeof(tlv_subtype)); tlv_ptr += sizeof(tlv_subtype); memcpy(tlv_ptr, &tlv_sublen, sizeof(tlv_sublen)); tlv_ptr += sizeof(tlv_sublen); memcpy(tlv_ptr, tls_cipher, tlv_subvalsz); tlv_ptr += tlv_subvalsz; } if (tls_sig_algo != NULL) { uint8_t tlv_subtype; uint16_t tlv_sublen; size_t tlv_subvalsz; tlv_subtype = PROXY_PROTOCOL_V2_TLV_TLS_SIG_ALGO; tlv_subvalsz = strlen(tls_sig_algo); tlv_sublen = htons(tlv_subvalsz); memcpy(tlv_ptr, &tlv_subtype, sizeof(tlv_subtype)); tlv_ptr += sizeof(tlv_subtype); memcpy(tlv_ptr, &tlv_sublen, sizeof(tlv_sublen)); tlv_ptr += sizeof(tlv_sublen); memcpy(tlv_ptr, tls_sig_algo, tlv_subvalsz); tlv_ptr += tlv_subvalsz; } if (tls_key_algo != NULL) { uint8_t tlv_subtype; uint16_t tlv_sublen; size_t tlv_subvalsz; tlv_subtype = PROXY_PROTOCOL_V2_TLV_TLS_KEY_ALGO; tlv_subvalsz = strlen(tls_sig_algo); tlv_sublen = htons(tlv_subvalsz); memcpy(tlv_ptr, &tlv_subtype, sizeof(tlv_subtype)); tlv_ptr += sizeof(tlv_subtype); memcpy(tlv_ptr, &tlv_sublen, sizeof(tlv_sublen)); tlv_ptr += sizeof(tlv_sublen); memcpy(tlv_ptr, tls_key_algo, tlv_subvalsz); tlv_ptr += tlv_subvalsz; } tlv_len = pcalloc(p, sizeof(uint16_t)); *tlv_len = htons(tlv_valsz); niov = *v2_niov; v2_iov[niov].iov_base = (void *) tlv_type; v2_iov[niov].iov_len = sizeof(uint8_t); niov++; v2_iov[niov].iov_base = (void *) tlv_len; v2_iov[niov].iov_len = sizeof(uint16_t); niov++; v2_iov[niov].iov_base = (void *) tlv_val; v2_iov[niov].iov_len = tlv_valsz; /* Make sure to increment niov one more, for the next TLV. */ *v2_niov = niov + 1; total_len = sizeof(uint8_t) + sizeof(uint16_t) + tlv_valsz; return total_len; } static const char *get_v2_tlv_unique_id(pool *p) { const char *unique_id = NULL; /* Only add the Unique ID TLV if mod_unique_id generated one. * Note that in a proxy chain, we will want to also honor * and preserve any Unique ID TLV sent via PROXY protocol. */ unique_id = pr_table_get(session.notes, "mod_proxy_protocol.unique-id", NULL); if (unique_id == NULL) { const void *val; val = pr_table_get(session.notes, "UNIQUE_ID", NULL); if (val == NULL) { return NULL; } unique_id = pstrdup(p, val); } return unique_id; } static uint16_t add_v2_tlv_unique_id(pool *p, struct iovec *v2_iov, unsigned int *v2_niov) { uint8_t *tlv_type; uint16_t *tlv_len, total_len; const char *tlv_val; size_t tlv_valsz = 0; unsigned int niov; tlv_val = get_v2_tlv_unique_id(p); if (tlv_val == NULL) { return 0; } pr_trace_msg(trace_channel, 22, "adding Unique ID V2 TLV: '%s'", (const char *) tlv_val); tlv_type = pcalloc(p, sizeof(uint8_t)); *tlv_type = PROXY_PROTOCOL_V2_TLV_UNIQUE_ID; tlv_valsz = strlen(tlv_val); tlv_len = pcalloc(p, sizeof(uint16_t)); *tlv_len = htons(tlv_valsz); niov = *v2_niov; v2_iov[niov].iov_base = (void *) tlv_type; v2_iov[niov].iov_len = sizeof(uint8_t); niov++; v2_iov[niov].iov_base = (void *) tlv_len; v2_iov[niov].iov_len = sizeof(uint16_t); niov++; v2_iov[niov].iov_base = (void *) tlv_val; v2_iov[niov].iov_len = tlv_valsz; /* Make sure to increment niov one more, for the next TLV. */ *v2_niov = niov + 1; total_len = sizeof(uint8_t) + sizeof(uint16_t) + tlv_valsz; return total_len; } static uint16_t add_v2_tlv_aws(pool *p, struct iovec *v2_iov, unsigned int *v2_niov) { uint8_t *tlv_type, tlv_subtype; uint16_t *tlv_len, tlv_sublen, total_len; char *tlv_ptr; void *tlv_val; const void *tlv_subval; size_t tlv_valsz = 0, tlv_subvalsz = 0, valsz = 0; unsigned int niov; tlv_subval = pr_table_get(session.notes, "mod_proxy_protocol.aws.vpc-endpoint-id", &tlv_subvalsz); if (tlv_subval == NULL) { return 0; } pr_trace_msg(trace_channel, 22, "adding AWS V2 TLV: VPC Endpoint ID '%s'", (const char *) tlv_subval); /* mod_proxy_protocol treats its notes as NUL-terminated strings, but the * AWS TLVs may not actually be strings. So subtract one for the NUL that * mod_proxy_protocol adds. */ tlv_subvalsz -= 1; tlv_type = pcalloc(p, sizeof(uint8_t)); /* AWS custom type for its TLVs. */ *tlv_type = 0xEA; /* This is more complicated, due to the nested nature of sub-TLVs. */ valsz = (sizeof(uint8_t) + sizeof(uint16_t) + tlv_subvalsz); tlv_ptr = tlv_val = pcalloc(p, valsz); tlv_valsz = valsz; /* VPC Endpoint ID */ tlv_subtype = 0x01; tlv_sublen = htons(tlv_subvalsz); memcpy(tlv_ptr, &tlv_subtype, sizeof(tlv_subtype)); tlv_ptr += sizeof(tlv_subtype); memcpy(tlv_ptr, &tlv_sublen, sizeof(tlv_sublen)); tlv_ptr += sizeof(tlv_sublen); memcpy(tlv_ptr, tlv_subval, tlv_subvalsz); tlv_ptr += tlv_subvalsz; tlv_len = pcalloc(p, sizeof(uint16_t)); *tlv_len = htons(tlv_valsz); niov = *v2_niov; v2_iov[niov].iov_base = (void *) tlv_type; v2_iov[niov].iov_len = sizeof(uint8_t); niov++; v2_iov[niov].iov_base = (void *) tlv_len; v2_iov[niov].iov_len = sizeof(uint16_t); niov++; v2_iov[niov].iov_base = (void *) tlv_val; v2_iov[niov].iov_len = tlv_valsz; /* Make sure to increment niov one more, for the next TLV. */ *v2_niov = niov + 1; total_len = sizeof(uint8_t) + sizeof(uint16_t) + tlv_valsz; return total_len; } static int parse_ul(const char *text, uint32_t *num) { char *endp = NULL; unsigned long res; res = strtoul(text, &endp, 10); if (endp && *endp) { errno = EINVAL; return -1; } *num = (uint32_t) res; return 0; } static uint16_t add_v2_tlv_azure(pool *p, struct iovec *v2_iov, unsigned int *v2_niov) { uint8_t *tlv_type, tlv_subtype; uint16_t *tlv_len, tlv_sublen, total_len; char *tlv_ptr; void *tlv_val; const void *tlv_subval; size_t tlv_valsz = 0, tlv_subvalsz = 0, valsz = 0; unsigned int niov; uint32_t link_id; tlv_subval = pr_table_get(session.notes, "mod_proxy_protocol.azure.private-endpoint-linkid", &tlv_subvalsz); if (tlv_subval == NULL) { return 0; } /* Per Azure docs, the linkid is a uint32_t value, little-endian. But * mod_proxy_protocol wants to store session notes as strings, so it does an * snprintf("%u") on the value. We, of course, want to encode it on the * wire per the docs. So we need to change the text back to the uint32_t * value. */ if (parse_ul(tlv_subval, &link_id) < 0) { return 0; } pr_trace_msg(trace_channel, 22, "adding Azure V2 TLV: Private Endpoint Link ID %lu", (unsigned long) link_id); tlv_subval = &link_id; tlv_subvalsz = 4; tlv_type = pcalloc(p, sizeof(uint8_t)); /* Azure custom type for its TLVs. */ *tlv_type = 0xEE; /* This is more complicated, due to the nested nature of sub-TLVs. */ valsz = (sizeof(uint8_t) + sizeof(uint16_t) + tlv_subvalsz); tlv_ptr = tlv_val = pcalloc(p, valsz); tlv_valsz = valsz; /* Private Endpoint LinkID */ tlv_subtype = 0x01; tlv_sublen = htons(tlv_subvalsz); memcpy(tlv_ptr, &tlv_subtype, sizeof(tlv_subtype)); tlv_ptr += sizeof(tlv_subtype); memcpy(tlv_ptr, &tlv_sublen, sizeof(tlv_sublen)); tlv_ptr += sizeof(tlv_sublen); memcpy(tlv_ptr, tlv_subval, tlv_subvalsz); tlv_ptr += tlv_subvalsz; tlv_len = pcalloc(p, sizeof(uint16_t)); *tlv_len = htons(tlv_valsz); niov = *v2_niov; v2_iov[niov].iov_base = (void *) tlv_type; v2_iov[niov].iov_len = sizeof(uint8_t); niov++; v2_iov[niov].iov_base = (void *) tlv_len; v2_iov[niov].iov_len = sizeof(uint16_t); niov++; v2_iov[niov].iov_base = (void *) tlv_val; v2_iov[niov].iov_len = tlv_valsz; /* Make sure to increment niov one more, for the next TLV. */ *v2_niov = niov + 1; total_len = sizeof(uint8_t) + sizeof(uint16_t) + tlv_valsz; return total_len; } static uint16_t add_v2_tlv_other(pool *p, struct iovec *v2_iov, unsigned int *v2_niov) { uint16_t len, total_len = 0; len = add_v2_tlv_aws(p, v2_iov, v2_niov); total_len += len; len = add_v2_tlv_azure(p, v2_iov, v2_niov); total_len += len; return total_len; } int proxy_conn_send_proxy_v2(pool *p, conn_t *conn) { int res, xerrno; uint8_t ver_cmd, trans_fam, src_ipv6[16], dst_ipv6[16]; uint16_t v2_len, src_port, dst_port; uint32_t src_ipv4, dst_ipv4; struct iovec v2_iov[32]; unsigned int v2_niov = 8; pool *sub_pool = NULL, *tlv_pool = NULL; char *proto; const pr_netaddr_t *src_addr = NULL, *dst_addr = NULL; if (p == NULL || conn == NULL) { errno = EINVAL; return -1; } v2_iov[0].iov_base = (void *) proxy_protocol_v2_sig; v2_iov[0].iov_len = PROXY_PROTOCOL_V2_SIGLEN; /* PROXY protocol v2 + PROXY command */ ver_cmd = (0x20|0x01); v2_iov[1].iov_base = (void *) &ver_cmd; v2_iov[1].iov_len = sizeof(ver_cmd); src_addr = session.c->remote_addr; dst_addr = session.c->local_addr; if (pr_netaddr_get_family(src_addr) == AF_INET && pr_netaddr_get_family(dst_addr) == AF_INET) { struct sockaddr_in *saddr; proto = "TCP/IPv4"; trans_fam = (PROXY_PROTOCOL_V2_TRANSPORT_STREAM|PROXY_PROTOCOL_V2_FAMILY_INET); v2_len = PROXY_PROTOCOL_V2_ADDRLEN_INET; saddr = (struct sockaddr_in *) pr_netaddr_get_sockaddr(src_addr); src_ipv4 = saddr->sin_addr.s_addr; v2_iov[4].iov_base = (void *) &src_ipv4; v2_iov[4].iov_len = sizeof(src_ipv4); saddr = (struct sockaddr_in *) pr_netaddr_get_sockaddr(dst_addr); dst_ipv4 = saddr->sin_addr.s_addr; v2_iov[5].iov_base = (void *) &dst_ipv4; v2_iov[5].iov_len = sizeof(dst_ipv4); /* Quell compiler warnings about unused variables. */ (void) src_ipv6; (void) dst_ipv6; } else { struct sockaddr_in6 *saddr; proto = "TCP/IPv6"; trans_fam = (PROXY_PROTOCOL_V2_TRANSPORT_STREAM|PROXY_PROTOCOL_V2_FAMILY_INET6); v2_len = PROXY_PROTOCOL_V2_ADDRLEN_INET6; sub_pool = make_sub_pool(p); if (pr_netaddr_get_family(src_addr) == AF_INET) { src_addr = pr_netaddr_v4tov6(sub_pool, src_addr); } saddr = (struct sockaddr_in6 *) pr_netaddr_get_sockaddr(src_addr); memcpy(&src_ipv6, &(saddr->sin6_addr), sizeof(src_ipv6)); v2_iov[4].iov_base = (void *) &src_ipv6; v2_iov[4].iov_len = sizeof(src_ipv6); if (pr_netaddr_get_family(dst_addr) == AF_INET) { dst_addr = pr_netaddr_v4tov6(sub_pool, dst_addr); } saddr = (struct sockaddr_in6 *) pr_netaddr_get_sockaddr(dst_addr); memcpy(&dst_ipv6, &(saddr->sin6_addr), sizeof(dst_ipv6)); v2_iov[5].iov_base = (void *) &dst_ipv6; v2_iov[5].iov_len = sizeof(dst_ipv6); /* Quell compiler warnings about unused variables. */ (void) src_ipv4; (void) dst_ipv4; } v2_iov[2].iov_base = (void *) &trans_fam; v2_iov[2].iov_len = sizeof(trans_fam); if (proxy_opts & PROXY_OPT_USE_PROXY_PROTOCOL_V2_TLVS) { uint16_t tlv_len; tlv_pool = make_sub_pool(p); tlv_len = add_v2_tlv_alpn(tlv_pool, v2_iov, &v2_niov); if (tlv_len > 0) { v2_len += tlv_len; } tlv_len = add_v2_tlv_authority(tlv_pool, v2_iov, &v2_niov); if (tlv_len > 0) { v2_len += tlv_len; } tlv_len = add_v2_tlv_tls(tlv_pool, v2_iov, &v2_niov); if (tlv_len > 0) { v2_len += tlv_len; } tlv_len = add_v2_tlv_unique_id(tlv_pool, v2_iov, &v2_niov); if (tlv_len > 0) { v2_len += tlv_len; } /* Make sure we propagate any of the other custom TLVs, such as * for AWS or Azure. */ tlv_len = add_v2_tlv_other(tlv_pool, v2_iov, &v2_niov); if (tlv_len > 0) { v2_len += tlv_len; } } v2_len = htons(v2_len); v2_iov[3].iov_base = (void *) &v2_len; v2_iov[3].iov_len = sizeof(v2_len); src_port = htons(session.c->remote_port); v2_iov[6].iov_base = (void *) &src_port; v2_iov[6].iov_len = sizeof(src_port); dst_port = htons(session.c->local_port); v2_iov[7].iov_base = (void *) &dst_port; v2_iov[7].iov_len = sizeof(dst_port); pr_trace_msg(trace_channel, 9, "sending PROXY protocol V2 message for %s %s#%u %s#%u to backend", proto, pr_netaddr_get_ipstr(src_addr), (unsigned int) ntohs(src_port), pr_netaddr_get_ipstr(dst_addr), (unsigned int) ntohs(dst_port)); res = writev_conn(conn, v2_iov, v2_niov); xerrno = errno; if (sub_pool != NULL) { destroy_pool(sub_pool); } if (tlv_pool != NULL) { destroy_pool(tlv_pool); } errno = xerrno; return res; } proftpd-mod_proxy-0.9.5/lib/proxy/db.c000066400000000000000000000712261475737016700177320ustar00rootroot00000000000000/* * ProFTPD - mod_proxy database implementation * Copyright (c) 2015-2024 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "proxy/db.h" #include struct proxy_dbh { pool *pool; sqlite3 *db; const char *schema; pr_table_t *prepared_stmts; }; static const char *current_schema = NULL; static const char *trace_channel = "proxy.db"; #define PROXY_DB_SQLITE_MAX_RETRY_COUNT 20 #define PROXY_DB_SQLITE_MAX_RETRY_DELAY_MS 100 #define PROXY_DB_SQLITE_TRACE_LEVEL 17 static int db_busy(void *user_data, int busy_count) { int retry = FALSE; /* How many retries do we want to allow? */ if (busy_count <= PROXY_DB_SQLITE_MAX_RETRY_COUNT) { retry = TRUE; } if (current_schema != NULL) { pr_trace_msg(trace_channel, 1, "(sqlite3): schema '%s': busy count = %d, retry = %s", current_schema, busy_count, retry ? "true" : "false"); } else { pr_trace_msg(trace_channel, 1, "(sqlite3): busy count = %d, retry = %s", busy_count, retry ? "true" : "false"); } /* If we're busy, then sleep for a short while, on the assumption that the * other process will finish its business with our tables. */ (void) pr_timer_usleep(PROXY_DB_SQLITE_MAX_RETRY_DELAY_MS * 1000); return retry; } #if defined(SQLITE_CONFIG_LOG) static void db_err(void *user_data, int err_code, const char *err_msg) { if (current_schema != NULL) { pr_trace_msg(trace_channel, 1, "(sqlite3): schema '%s': [error %d] %s", current_schema, err_code, err_msg); } else { pr_trace_msg(trace_channel, 1, "(sqlite3): [error %d] %s", err_code, err_msg); } } #endif /* SQLITE_CONFIG_LOG */ #if defined(SQLITE_CONFIG_SQLLOG) static void db_sql(void *user_data, sqlite3 *db, const char *info, int event_type) { switch (event_type) { case 0: /* Opening database. */ pr_trace_msg(trace_channel, 1, "(sqlite3): opened database: %s", info); break; case 1: if (current_schema != NULL) { pr_trace_msg(trace_channel, 1, "(sqlite3): schema '%s': executed statement: %s", current_schema, info); } else { pr_trace_msg(trace_channel, 1, "(sqlite3): executed statement: %s", info); } break; case 2: /* Closing database. */ pr_trace_msg(trace_channel, 1, "(sqlite3): closed database: %s", sqlite3_db_filename(db, "main")); break; } } #endif /* SQLITE_CONFIG_SQLLOG */ #if defined(HAVE_SQLITE3_TRACE_V2) static int db_trace2(unsigned int trace_type, void *user_data, void *ptr, void *ptr_data) { const char *schema_name; schema_name = user_data; switch (trace_type) { case SQLITE_TRACE_STMT: { const char *stmt; stmt = ptr_data; if (schema_name == NULL) { pr_trace_msg(trace_channel, PROXY_DB_SQLITE_TRACE_LEVEL, "(sqlite3): executing stmt '%s'", stmt); } else { pr_trace_msg(trace_channel, PROXY_DB_SQLITE_TRACE_LEVEL, "(sqlite3): schema '%s': executing stmt '%s'", schema_name, stmt); } break; } case SQLITE_TRACE_PROFILE: { sqlite3_stmt *pstmt; int64_t ns = 0; char *expanded_sql = NULL, *orig_sql = NULL; pstmt = ptr; ns = *((int64_t *) ptr_data); orig_sql = expanded_sql = sqlite3_expanded_sql(pstmt); /* There are some SQL statements whose values we do NOT want to log. * Thus we have a hacky way to look for them. Sigh. */ if (expanded_sql != NULL && strstr(expanded_sql, "SSL SESSION PARAMETERS") != NULL) { expanded_sql = "(full SQL statement redacted)"; } if (schema_name == NULL) { pr_trace_msg(trace_channel, PROXY_DB_SQLITE_TRACE_LEVEL, "(sqlite3): stmt '%s' ran for %lu nanosecs", expanded_sql, (unsigned long) ns); } else { pr_trace_msg(trace_channel, PROXY_DB_SQLITE_TRACE_LEVEL, "(sqlite3): schema '%s': stmt '%s' ran for %lu nanosecs", schema_name, expanded_sql, (unsigned long) ns); } sqlite3_free(orig_sql); break; } case SQLITE_TRACE_ROW: { sqlite3_stmt *pstmt; char *expanded_sql = NULL, *orig_sql = NULL; pstmt = ptr; orig_sql = expanded_sql = sqlite3_expanded_sql(pstmt); /* There are some SQL statements whose values we do NOT want to log. * Thus we have a hacky way to look for them. Sigh. */ if (expanded_sql != NULL && strstr(expanded_sql, "SSL SESSION PARAMETERS") != NULL) { expanded_sql = "(full SQL statement redacted)"; } if (schema_name == NULL) { pr_trace_msg(trace_channel, PROXY_DB_SQLITE_TRACE_LEVEL, "(sqlite3): returning result row for stmt '%s'", expanded_sql); } else { pr_trace_msg(trace_channel, PROXY_DB_SQLITE_TRACE_LEVEL, "(sqlite3): schema '%s': returning result row for stmt '%s'", schema_name, expanded_sql); } sqlite3_free(orig_sql); break; } case SQLITE_TRACE_CLOSE: { sqlite3 *db; db = ptr; if (schema_name == NULL) { pr_trace_msg(trace_channel, PROXY_DB_SQLITE_TRACE_LEVEL, "(sqlite3): closing database connection to %s", sqlite3_db_filename(db, "main")); } else { pr_trace_msg(trace_channel, PROXY_DB_SQLITE_TRACE_LEVEL, "(sqlite3): schema '%s': closing database connection to %s", schema_name, sqlite3_db_filename(db, "main")); } break; } default: break; } return 0; } #elif defined(HAVE_SQLITE3_TRACE) static void db_trace(void *user_data, const char *trace_msg) { if (user_data != NULL) { const char *schema_name; schema_name = user_data; pr_trace_msg(trace_channel, PROXY_DB_SQLITE_TRACE_LEVEL, "(sqlite3): schema '%s': %s", schema_name, trace_msg); } else { pr_trace_msg(trace_channel, PROXY_DB_SQLITE_TRACE_LEVEL, "(sqlite3): %s", trace_msg); } } #endif /* HAVE_SQLITE3_TRACE */ static int stmt_cb(void *v, int ncols, char **cols, char **col_names) { register int i; const char *stmt; stmt = v; pr_trace_msg(trace_channel, 9, "results for '%s':", stmt); for (i = 0; i < ncols; i++) { pr_trace_msg(trace_channel, 9, "col #%d [%s]: %s", i+1, col_names[i], cols[i]); } return 0; } int proxy_db_exec_stmt(pool *p, struct proxy_dbh *dbh, const char *stmt, const char **errstr) { int res; char *ptr = NULL; unsigned int nretries = 0; if (dbh == NULL || stmt == NULL) { errno = EINVAL; return -1; } pr_trace_msg(trace_channel, 10, "schema '%s': executing statement '%s'", dbh->schema, stmt); current_schema = dbh->schema; res = sqlite3_exec(dbh->db, stmt, stmt_cb, (void *) stmt, &ptr); while (res != SQLITE_OK) { if (res == SQLITE_BUSY) { struct timeval tv; sqlite3_free(ptr); nretries++; pr_trace_msg(trace_channel, 3, "attempt #%u, database busy, trying '%s' again", nretries, stmt); /* Sleep for short bit, then try again. */ tv.tv_sec = 0; tv.tv_usec = 500000L; if (select(0, NULL, NULL, NULL, &tv) < 0) { if (errno == EINTR) { pr_signals_handle(); } } res = sqlite3_exec(dbh->db, stmt, NULL, NULL, &ptr); continue; } pr_trace_msg(trace_channel, 1, "error executing '%s': (%d) %s", stmt, res, ptr); if (errstr != NULL) { *errstr = pstrdup(p, ptr); } current_schema = NULL; sqlite3_free(ptr); errno = EINVAL; return -1; } current_schema = NULL; sqlite3_free(ptr); pr_trace_msg(trace_channel, 13, "successfully executed '%s'", stmt); return 0; } /* Prepared statements */ int proxy_db_prepare_stmt(pool *p, struct proxy_dbh *dbh, const char *stmt) { sqlite3_stmt *pstmt = NULL; int res; if (p == NULL || dbh == NULL || stmt == NULL) { errno = EINVAL; return -1; } pstmt = (sqlite3_stmt *) pr_table_get(dbh->prepared_stmts, stmt, NULL); if (pstmt != NULL) { res = sqlite3_clear_bindings(pstmt); if (res != SQLITE_OK) { pr_trace_msg(trace_channel, 3, "error clearing bindings from prepared statement '%s': %s", stmt, sqlite3_errmsg(dbh->db)); } res = sqlite3_reset(pstmt); if (res != SQLITE_OK) { pr_trace_msg(trace_channel, 3, "error resetting prepared statement '%s': %s", stmt, sqlite3_errmsg(dbh->db)); errno = EPERM; return -1; } return 0; } res = sqlite3_prepare_v2(dbh->db, stmt, -1, &pstmt, NULL); if (res != SQLITE_OK) { pr_trace_msg(trace_channel, 4, "schema '%s': error preparing statement '%s': %s", dbh->schema, stmt, sqlite3_errmsg(dbh->db)); errno = EINVAL; return -1; } /* The prepared statement handling here relies on this cache, thus if we fail * to stash the prepared statement here, it will cause problems later. */ res = pr_table_add(dbh->prepared_stmts, pstrdup(dbh->pool, stmt), pstmt, sizeof(sqlite3_stmt *)); if (res < 0) { int xerrno = errno; pr_trace_msg(trace_channel, 4, "error stashing prepared statement '%s': %s", stmt, strerror(xerrno)); errno = xerrno; return -1; } return 0; } int proxy_db_bind_stmt(pool *p, struct proxy_dbh *dbh, const char *stmt, int idx, int type, void *data, int datalen) { sqlite3_stmt *pstmt; int res; if (p == NULL || dbh == NULL || stmt == NULL) { errno = EINVAL; return -1; } /* SQLite3 bind parameters start at index 1. */ if (idx < 1) { errno = EINVAL; return -1; } if (dbh->prepared_stmts == NULL) { errno = ENOENT; return -1; } pstmt = (sqlite3_stmt *) pr_table_get(dbh->prepared_stmts, stmt, NULL); if (pstmt == NULL) { pr_trace_msg(trace_channel, 19, "unable to find prepared statement for '%s'", stmt); errno = ENOENT; return -1; } switch (type) { case PROXY_DB_BIND_TYPE_INT: { int i; if (data == NULL) { errno = EINVAL; return -1; } i = *((int *) data); res = sqlite3_bind_int(pstmt, idx, i); if (res != SQLITE_OK) { pr_trace_msg(trace_channel, 4, "error binding parameter %d of '%s' to INT %d: %s", idx, stmt, i, sqlite3_errmsg(dbh->db)); errno = EPERM; return -1; } break; } case PROXY_DB_BIND_TYPE_LONG: { long l; if (data == NULL) { errno = EINVAL; return -1; } l = *((long *) data); res = sqlite3_bind_int(pstmt, idx, l); if (res != SQLITE_OK) { pr_trace_msg(trace_channel, 4, "error binding parameter %d of '%s' to LONG %ld: %s", idx, stmt, l, sqlite3_errmsg(dbh->db)); errno = EPERM; return -1; } break; } case PROXY_DB_BIND_TYPE_TEXT: { const char *text; if (data == NULL) { errno = EINVAL; return -1; } text = (const char *) data; res = sqlite3_bind_text(pstmt, idx, text, datalen, NULL); if (res != SQLITE_OK) { pr_trace_msg(trace_channel, 4, "error binding parameter %d of '%s' to TEXT '%s': %s", idx, stmt, text, sqlite3_errmsg(dbh->db)); errno = EPERM; return -1; } break; } case PROXY_DB_BIND_TYPE_BLOB: { if (data == NULL) { errno = EINVAL; return -1; } res = sqlite3_bind_blob(pstmt, idx, data, datalen, NULL); if (res != SQLITE_OK) { pr_trace_msg(trace_channel, 4, "error binding parameter %d of '%s' to BLOB (%d bytes): %s", idx, stmt, datalen, sqlite3_errmsg(dbh->db)); errno = EPERM; return -1; } break; } case PROXY_DB_BIND_TYPE_NULL: res = sqlite3_bind_null(pstmt, idx); if (res != SQLITE_OK) { pr_trace_msg(trace_channel, 4, "error binding parameter %d of '%s' to NULL: %s", idx, stmt, sqlite3_errmsg(dbh->db)); errno = EPERM; return -1; } break; default: pr_trace_msg(trace_channel, 2, "unknown/unsupported bind data type %d", type); errno = EINVAL; return -1; } return 0; } int proxy_db_finish_stmt(pool *p, struct proxy_dbh *dbh, const char *stmt) { sqlite3_stmt *pstmt; int res; if (p == NULL || dbh == NULL || stmt == NULL) { errno = EINVAL; return -1; } if (dbh->prepared_stmts == NULL) { errno = ENOENT; return -1; } pstmt = (sqlite3_stmt *) pr_table_get(dbh->prepared_stmts, stmt, NULL); if (pstmt == NULL) { pr_trace_msg(trace_channel, 19, "unable to find prepared statement for '%s'", stmt); errno = ENOENT; return -1; } res = sqlite3_finalize(pstmt); if (res != SQLITE_OK) { pr_trace_msg(trace_channel, 3, "schema '%s': error finishing prepared statement '%s': %s", dbh->schema, stmt, sqlite3_errmsg(dbh->db)); errno = EPERM; return -1; } (void) pr_table_remove(dbh->prepared_stmts, stmt, NULL); return 0; } array_header *proxy_db_exec_prepared_stmt(pool *p, struct proxy_dbh *dbh, const char *stmt, const char **errstr) { sqlite3_stmt *pstmt; int readonly = FALSE, res; array_header *results = NULL; if (p == NULL || dbh == NULL || stmt == NULL) { errno = EINVAL; return NULL; } if (dbh->prepared_stmts == NULL) { errno = ENOENT; return NULL; } pstmt = (sqlite3_stmt *) pr_table_get(dbh->prepared_stmts, stmt, NULL); if (pstmt == NULL) { pr_trace_msg(trace_channel, 19, "unable to find prepared statement for '%s'", stmt); errno = ENOENT; return NULL; } current_schema = dbh->schema; /* The sqlit3_stmt_readonly() function first appeared in SQLite 3.7.x. */ #if defined(HAVE_SQLITE3_STMT_READONLY) readonly = sqlite3_stmt_readonly(pstmt); #else readonly = FALSE; #endif /* SQLite 3.7.x or earlier */ if (readonly == FALSE) { /* Assume this is an INSERT/UPDATE/DELETE. */ res = sqlite3_step(pstmt); if (res != SQLITE_DONE) { const char *errmsg; errmsg = sqlite3_errmsg(dbh->db); if (errstr != NULL) { *errstr = pstrdup(p, errmsg); } pr_trace_msg(trace_channel, 2, "error executing '%s': %s", stmt, errmsg); current_schema = NULL; errno = EPERM; return NULL; } current_schema = NULL; /* Indicate success for non-readonly statements by returning an empty * result set. */ pr_trace_msg(trace_channel, 13, "successfully executed '%s'", stmt); results = make_array(p, 0, sizeof(char *)); return results; } results = make_array(p, 0, sizeof(char *)); res = sqlite3_step(pstmt); while (res == SQLITE_ROW) { register int i; int ncols; ncols = sqlite3_column_count(pstmt); pr_trace_msg(trace_channel, 12, "schema '%s': executing prepared statement '%s' returned row " "(columns: %d)", dbh->schema, stmt, ncols); for (i = 0; i < ncols; i++) { char *val = NULL; int col_type; pr_signals_handle(); col_type = sqlite3_column_type(pstmt, i); if (col_type != SQLITE_BLOB) { /* By using sqlite3_column_text, SQLite will coerce the column value * into a string. */ val = pstrdup(p, (const char *) sqlite3_column_text(pstmt, i)); pr_trace_msg(trace_channel, 17, "column %s [%u]: %s", sqlite3_column_name(pstmt, i), i, val); *((char **) push_array(results)) = val; } else { int bloblen; char bloblen_text[64]; bloblen = sqlite3_column_bytes(pstmt, i); val = palloc(p, bloblen); memcpy(val, sqlite3_column_blob(pstmt, i), bloblen); *((char **) push_array(results)) = val; /* For BLOBs, we need to provide the length as well. */ memset(&bloblen_text, '\0', sizeof(bloblen_text)); pr_snprintf(bloblen_text, sizeof(bloblen_text)-1, "%d", bloblen); *((char **) push_array(results)) = pstrdup(p, bloblen_text); } } res = sqlite3_step(pstmt); } if (res != SQLITE_DONE) { const char *errmsg; errmsg = sqlite3_errmsg(dbh->db); if (errstr != NULL) { *errstr = pstrdup(p, errmsg); } current_schema = NULL; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "schema '%s': executing prepared statement '%s' did not complete " "successfully: %s", dbh->schema, stmt, errmsg); errno = EPERM; return NULL; } current_schema = NULL; pr_trace_msg(trace_channel, 13, "successfully executed '%s'", stmt); return results; } /* Database opening/closing. */ struct proxy_dbh *proxy_db_open(pool *p, const char *table_path, const char *schema_name) { int res, flags; pool *sub_pool; const char *stmt; sqlite3 *db = NULL; struct proxy_dbh *dbh; if (p == NULL || table_path == NULL || schema_name == NULL) { errno = EINVAL; return NULL; } pr_trace_msg(trace_channel, 19, "attempting to open %s tables at path '%s'", schema_name, table_path); flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE; #if defined(SQLITE_OPEN_PRIVATECACHE) /* By default, disable the shared cache mode. */ flags |= SQLITE_OPEN_PRIVATECACHE; #endif res = sqlite3_open_v2(table_path, &db, flags, NULL); if (res != SQLITE_OK) { pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": error opening SQLite database '%s': %s", table_path, sqlite3_errmsg(db)); if (db != NULL) { sqlite3_close(db); } errno = EPERM; return NULL; } /* Make sure we configure a busy handler. */ sqlite3_busy_handler(db, db_busy, (void *) schema_name); if (pr_trace_get_level(trace_channel) >= PROXY_DB_SQLITE_TRACE_LEVEL) { #if defined(HAVE_SQLITE3_TRACE_V2) sqlite3_trace_v2(db, SQLITE_TRACE_STMT|SQLITE_TRACE_PROFILE|SQLITE_TRACE_ROW|SQLITE_TRACE_CLOSE, db_trace2, (void *) schema_name); #elif defined(HAVE_SQLITE3_TRACE) sqlite3_trace(db, db_trace, (void *) schema_name); #endif /* HAVE_SQLITE3_TRACE or HAVE_SQLITE3_TRACE_V2 */ } sub_pool = make_sub_pool(p); pr_pool_tag(sub_pool, "Proxy Database Pool"); dbh = pcalloc(sub_pool, sizeof(struct proxy_dbh)); dbh->pool = sub_pool; dbh->db = db; dbh->schema = pstrdup(dbh->pool, schema_name); stmt = "PRAGMA temp_store = MEMORY;"; res = proxy_db_exec_stmt(p, dbh, stmt, NULL); if (res < 0) { pr_trace_msg(trace_channel, 2, "error setting MEMORY temp store on SQLite database '%s': %s", table_path, sqlite3_errmsg(dbh->db)); } /* Tell SQLite to only use in-memory journals. This is necessary for * working properly when a chroot is used. Note that the MEMORY journal mode * of SQLite is supported only for SQLite-3.6.5 and later. */ stmt = "PRAGMA journal_mode = MEMORY;"; res = proxy_db_exec_stmt(p, dbh, stmt, NULL); if (res < 0) { pr_trace_msg(trace_channel, 2, "error setting MEMORY journal mode on SQLite database '%s': %s", table_path, sqlite3_errmsg(dbh->db)); } dbh->prepared_stmts = pr_table_nalloc(dbh->pool, 0, 4); pr_trace_msg(trace_channel, 9, "opened SQLite table '%s'", table_path); return dbh; } static int get_schema_version(pool *p, struct proxy_dbh *dbh, const char *schema_name, unsigned int *schema_version) { int res, version; const char *stmt, *errstr = NULL; array_header *results; stmt = "SELECT version FROM schema_version WHERE schema = ?;"; res = proxy_db_prepare_stmt(p, dbh, stmt); if (res < 0) { /* This can happen when the schema_version table does not exist; treat * as "missing". */ pr_trace_msg(trace_channel, 5, "error preparing statement '%s', treating as missing schema version", stmt); *schema_version = 0; return 0; } res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_TEXT, (void *) schema_name, -1); if (res < 0) { return -1; } results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr); if (results == NULL) { *schema_version = 0; return 0; } if (results->nelts != 1) { pr_log_debug(DEBUG3, MOD_PROXY_VERSION ": expected 1 result from statement '%s', got %d", stmt, results->nelts); errno = EINVAL; return -1; } version = atoi(((char **) results->elts)[0]); if (version < 0) { /* Invalid schema version; treat as "missing". */ pr_trace_msg(trace_channel, 5, "statement '%s' yielded invalid schema version %d, treating as missing", stmt, version); *schema_version = 0; return 0; } *schema_version = version; return 0; } static int set_schema_version(pool *p, struct proxy_dbh *dbh, const char *schema_name, unsigned int schema_version) { int res, xerrno = 0; const char *stmt, *errstr = NULL; array_header *results; /* CREATE TABLE schema_version ( * schema TEXT NOT NULL PRIMARY KEY, * version INTEGER NOT NULL * ); */ stmt = "CREATE TABLE IF NOT EXISTS schema_version (schema TEXT NOT NULL PRIMARY KEY, version INTEGER NOT NULL);"; res = proxy_db_exec_stmt(p, dbh, stmt, &errstr); if (res < 0) { (void) pr_log_debug(DEBUG3, MOD_PROXY_VERSION ": error executing statement '%s': %s", stmt, errstr); errno = EPERM; return -1; } stmt = "INSERT INTO schema_version (schema, version) VALUES (?, ?);"; res = proxy_db_prepare_stmt(p, dbh, stmt); if (res < 0) { xerrno = errno; (void) pr_log_debug(DEBUG3, MOD_PROXY_VERSION ": schema '%s': error preparing statement '%s': %s", dbh->schema, stmt, strerror(xerrno)); errno = xerrno; return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_TEXT, (void *) schema_name, -1); if (res < 0) { return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_INT, (void *) &schema_version, 0); if (res < 0) { return -1; } results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr); if (results == NULL) { (void) pr_log_debug(DEBUG3, MOD_PROXY_VERSION ": error executing statement '%s': %s", stmt, errstr ? errstr : strerror(errno)); errno = EPERM; return -1; } return 0; } static void check_db_integrity(pool *p, struct proxy_dbh *dbh, int flags) { int res; const char *stmt, *errstr = NULL; if (flags & PROXY_DB_OPEN_FL_INTEGRITY_CHECK) { stmt = "PRAGMA integrity_check;"; res = proxy_db_exec_stmt(p, dbh, stmt, &errstr); if (res < 0) { (void) pr_log_debug(DEBUG3, MOD_PROXY_VERSION ": error executing statement '%s': %s", stmt, errstr); } } if (flags & PROXY_DB_OPEN_FL_VACUUM) { stmt = "VACUUM;"; res = proxy_db_exec_stmt(p, dbh, stmt, &errstr); if (res < 0) { (void) pr_log_debug(DEBUG3, MOD_PROXY_VERSION ": error executing statement '%s': %s", stmt, errstr); } } } struct proxy_dbh *proxy_db_open_with_version(pool *p, const char *table_path, const char *schema_name, unsigned int schema_version, int flags) { pool *tmp_pool = NULL; struct proxy_dbh *dbh = NULL; int res = 0, xerrno = 0; unsigned int current_version = 0; dbh = proxy_db_open(p, table_path, schema_name); if (dbh == NULL) { return NULL; } if (flags & PROXY_DB_OPEN_FL_SCHEMA_VERSION_CHECK) { pr_trace_msg(trace_channel, 19, "ensuring that schema at path '%s' has at least schema version %u", table_path, schema_version); tmp_pool = make_sub_pool(p); res = get_schema_version(tmp_pool, dbh, schema_name, ¤t_version); if (res < 0) { xerrno = errno; proxy_db_close(p, dbh); destroy_pool(tmp_pool); errno = xerrno; return NULL; } if (current_version >= schema_version) { pr_trace_msg(trace_channel, 11, "schema version %u >= desired version %u for path '%s'", current_version, schema_version, table_path); check_db_integrity(tmp_pool, dbh, flags); destroy_pool(tmp_pool); return dbh; } if (flags & PROXY_DB_OPEN_FL_ERROR_ON_SCHEMA_VERSION_SKEW) { pr_trace_msg(trace_channel, 5, "schema version %u < desired version %u for path '%s', failing", current_version, schema_version, table_path); proxy_db_close(p, dbh); destroy_pool(tmp_pool); errno = EPERM; return NULL; } /* The schema version is skewed; delete the old table, create a new one. */ pr_trace_msg(trace_channel, 4, "schema version %u < desired version %u for path '%s', deleting file", current_version, schema_version, table_path); if (proxy_db_close(p, dbh) < 0) { pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": error closing '%s' database: %s", table_path, strerror(errno)); } if (unlink(table_path) < 0) { pr_log_pri(PR_LOG_NOTICE, MOD_PROXY_VERSION ": error deleting '%s': %s", table_path, strerror(errno)); } dbh = proxy_db_open(p, table_path, schema_name); if (dbh == NULL) { xerrno = errno; destroy_pool(tmp_pool); errno = xerrno; return NULL; } res = set_schema_version(tmp_pool, dbh, schema_name, schema_version); xerrno = errno; } else { check_db_integrity(tmp_pool, dbh, flags); } destroy_pool(tmp_pool); if (res < 0) { errno = xerrno; return NULL; } return dbh; } int proxy_db_close(pool *p, struct proxy_dbh *dbh) { pool *tmp_pool; sqlite3_stmt *pstmt; int res; if (p == NULL || dbh == NULL) { errno = EINVAL; return -1; } pr_trace_msg(trace_channel, 19, "closing '%s' database handle", dbh->schema); tmp_pool = make_sub_pool(p); /* Make sure to close/finish any prepared statements associated with * the database. */ pstmt = sqlite3_next_stmt(dbh->db, NULL); while (pstmt != NULL) { sqlite3_stmt *next; const char *sql; pr_signals_handle(); next = sqlite3_next_stmt(dbh->db, pstmt); sql = pstrdup(tmp_pool, sqlite3_sql(pstmt)); res = sqlite3_finalize(pstmt); if (res != SQLITE_OK) { pr_trace_msg(trace_channel, 2, "schema '%s': error finishing prepared statement '%s': %s", dbh->schema, sql, sqlite3_errmsg(dbh->db)); } else { pr_trace_msg(trace_channel, 18, "finished prepared statement '%s'", sql); } pstmt = next; } destroy_pool(tmp_pool); res = sqlite3_close(dbh->db); if (res != SQLITE_OK) { pr_trace_msg(trace_channel, 2, "error closing SQLite database: %s", sqlite3_errmsg(dbh->db)); errno = EPERM; return -1; } pr_table_empty(dbh->prepared_stmts); pr_table_free(dbh->prepared_stmts); destroy_pool(dbh->pool); pr_trace_msg(trace_channel, 18, "%s", "closed SQLite database"); return 0; } int proxy_db_reindex(pool *p, struct proxy_dbh *dbh, const char *index_name, const char **errstr) { int res; const char *stmt; if (p == NULL || dbh == NULL || index_name == NULL) { errno = EINVAL; return -1; } stmt = pstrcat(p, "REINDEX ", index_name, ";", NULL); res = proxy_db_exec_stmt(p, dbh, stmt, errstr); return res; } int proxy_db_init(pool *p) { const char *version; if (p == NULL) { errno = EINVAL; return -1; } #if defined(SQLITE_CONFIG_SINGLETHREAD) /* Tell SQLite that we are not a multi-threaded application. */ sqlite3_config(SQLITE_CONFIG_SINGLETHREAD); #endif /* SQLITE_CONFIG_SINGLETHREAD */ #if defined(SQLITE_CONFIG_LOG) /* Register an error logging callback with SQLite3. */ sqlite3_config(SQLITE_CONFIG_LOG, db_err, NULL); #endif /* SQLITE_CONFIG_LOG */ #if defined(SQLITE_CONFIG_SQLLOG) sqlite3_config(SQLITE_CONFIG_SQLLOG, db_sql, NULL); #endif /* SQLITE_CONFIG_SQLLOG */ /* Check that the SQLite headers used match the version of the SQLite * library used. * * For now, we only log if there is a difference. */ version = sqlite3_libversion(); if (strcmp(version, SQLITE_VERSION) != 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "compiled using SQLite version '%s' headers, but linked to " "SQLite version '%s' library", SQLITE_VERSION, version); } pr_trace_msg(trace_channel, 9, "using SQLite %s", version); return 0; } int proxy_db_free(void) { return 0; } proftpd-mod_proxy-0.9.5/lib/proxy/dns.c000066400000000000000000000427221475737016700201300ustar00rootroot00000000000000/* * ProFTPD - mod_proxy DNS resolution * Copyright (c) 2020-2023 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "proxy/dns.h" /* The C_ANY macro is defined in ProFTPD's ftp.h file for "any" FTP command, * and may conflict with the DNS macros. This API does not use ProFTPD's C_ANY * macro, so remove it and avoid the collision. */ #undef C_ANY #include #include static const char *trace_channel = "proxy.dns"; struct srv_record { uint16_t priority; uint16_t weight; uint16_t port; const char *target; }; /* Sorting algorithm: priority first, then weight. */ static int srv_cmp(const void *left, const void *right) { const struct srv_record *a, *b; a = left; b = right; /* Lower priority wins */ if (a->priority < b->priority) { return -1; } if (b->priority < a->priority) { return 1; } /* For equal priorities, higher weight wins. * * Yes, I know that RFC 2782 prescribes a more nuanced algorithm, with * weighted random selection of records with equal priorities. */ if (a->weight > b->weight) { return -1; } if (b->weight > a->weight) { return 1; } return 0; } static int dns_query_error(const char *query_type, const char *query) { pr_trace_msg(trace_channel, 3, "failed to resolve %s records for '%s': %s", query_type, query, hstrerror(h_errno)); /* Try to set an appropriate errno. */ switch (h_errno) { #if defined(HOST_NOT_FOUND) case HOST_NOT_FOUND: errno = ENOENT; break; #endif /* HOST_NOT_FOUND */ #if defined(NO_DATA) case NO_DATA: errno = ENOENT; break; #endif /* NO_DATA */ default: errno = EPERM; } return -1; } static int dns_resolve_srv_a(pool *p, struct srv_record *srv, ns_rr rr, array_header *resp) { int xerrno; char text[INET_ADDRSTRLEN]; const pr_netaddr_t *addr; pr_inet_ntop(AF_INET, ns_rr_rdata(rr), text, sizeof(text)); addr = pr_netaddr_get_addr(p, text, NULL); xerrno = errno; if (addr == NULL) { pr_trace_msg(trace_channel, 3, "error resolving SRV A record '%s': %s", text, strerror(xerrno)); errno = xerrno; return -1; } pr_netaddr_set_port2((pr_netaddr_t *) addr, srv->port); pr_trace_msg(trace_channel, 19, "adding SRV A record for %s#%u", pr_netaddr_get_ipstr(addr), ntohs(pr_netaddr_get_port(addr))); *((const pr_netaddr_t **) push_array(resp)) = addr; return 0; } static int dns_resolve_srv_aaaa(pool *p, struct srv_record *srv, ns_rr rr, array_header *resp) { #if defined(PR_USE_IPV6) int xerrno; char text[INET6_ADDRSTRLEN]; const pr_netaddr_t *addr; pr_inet_ntop(AF_INET6, ns_rr_rdata(rr), text, sizeof(text)); addr = pr_netaddr_get_addr(p, text, NULL); xerrno = errno; if (addr == NULL) { pr_trace_msg(trace_channel, 3, "error resolving SRV A record '%s': %s", text, strerror(xerrno)); errno = xerrno; return -1; } pr_netaddr_set_port2((pr_netaddr_t *) addr, srv->port); pr_trace_msg(trace_channel, 19, "adding SRV AAAA record for %s#%u", pr_netaddr_get_ipstr(addr), ntohs(pr_netaddr_get_port(addr))); *((const pr_netaddr_t **) push_array(resp)) = addr; return 0; #endif /* PR_USE_IPV6 */ errno = ENOSYS; return -1; } static int dns_resolve_srv_name(pool *p, struct srv_record *srv, array_header *resp) { int xerrno; pool *tmp_pool; pr_netaddr_t *addr; const pr_netaddr_t *res; array_header *addrs = NULL; tmp_pool = make_sub_pool(p); pr_pool_tag(tmp_pool, "SRV name resolution"); res = pr_netaddr_get_addr(tmp_pool, srv->target, &addrs); xerrno = errno; if (res == NULL) { destroy_pool(tmp_pool); pr_trace_msg(trace_channel, 3, "error resolving SRV target '%s': %s", srv->target, strerror(xerrno)); errno = xerrno; return -1; } addr = pr_netaddr_dup(p, res); pr_netaddr_set_port2(addr, srv->port); pr_trace_msg(trace_channel, 19, "adding '%s' resolved record for %s#%u", srv->target, pr_netaddr_get_ipstr(addr), ntohs(pr_netaddr_get_port(addr))); *((pr_netaddr_t **) push_array(resp)) = addr; if (addrs != NULL) { register unsigned int i; pr_netaddr_t **elts; /* Other addresses were found associated with this name. */ elts = addrs->elts; for (i = 0; i < addrs->nelts; i++) { pr_netaddr_t *elt; elt = elts[i]; addr = pr_netaddr_dup(p, elt); pr_netaddr_set_port2(addr, srv->port); pr_trace_msg(trace_channel, 19, "adding '%s' resolved record for %s#%u", srv->target, pr_netaddr_get_ipstr(addr), ntohs(pr_netaddr_get_port(addr))); *((pr_netaddr_t **) push_array(resp)) = addr; } } return 0; } static int dns_resolve_srv_target(pool *p, const char *query, struct srv_record *srv, ns_msg msgh, array_header **resp, uint32_t *ttl) { register unsigned int i; unsigned int count, found = 0; /* Look for A, AAAA records in the "Additional Data" (`ns_s_ar`) section. * These SHOULD be the records for the targets mentioned by the SRV records. * If no matching A, AAAA records are found, we resort to our normal * resolution routine, i.e. pr_netaddr_get_addr(). * * If we see a CNAME record in the "Additional Data" section, ignore it; it * will be treated as if there are no A, AAAA records found. */ count = ns_msg_count(msgh, ns_s_ar); pr_trace_msg(trace_channel, 17, "found %u %s in the '%s' SRV additional data section", count, count != 1 ? "records" : "record", query); for (i = 0; i < count; i++) { ns_rr record; uint32_t record_ttl; const char *record_name; pr_signals_handle(); if (ns_parserr(&msgh, ns_s_ar, i, &record) < 0) { pr_trace_msg(trace_channel, 4, "error parsing DNS resource record #%u, skipping: %s", i + 1, strerror(errno)); continue; } record_name = ns_rr_name(record); /* Remember that DNS names are case-insensitive. */ if (strcasecmp(srv->target, record_name) != 0) { pr_trace_msg(trace_channel, 9, "additional resource record (#%u, %s) " "does not match target '%s', skipping", i + 1, record_name, srv->target); continue; } record_ttl = ns_rr_ttl(record); switch (ns_rr_type(record)) { case ns_t_a: if (ns_rr_rdlen(record) == 4) { pr_trace_msg(trace_channel, 4, "found additional A resource record (#%u, %s) for '%s' (TTL %lu)", i + 1, record_name, query, (unsigned long) record_ttl); if (dns_resolve_srv_a(p, srv, record, *resp) == 0) { if (ttl != NULL) { if (record_ttl < *ttl) { *ttl = record_ttl; } } found++; } } else { pr_trace_msg(trace_channel, 9, "found additional A resource record (#%u, %s) for '%s' with bad " "length (%d), skipping", i + 1, record_name, query, ns_rr_rdlen(record)); } break; case ns_t_aaaa: if (ns_rr_rdlen(record) == 16) { pr_trace_msg(trace_channel, 4, "found additional AAAA resource record (#%u, %s) for '%s' " "(TTL %lu)", i + 1, record_name, query, (unsigned long) record_ttl); if (dns_resolve_srv_aaaa(p, srv, record, *resp) == 0) { if (ttl != NULL) { if (record_ttl < *ttl) { *ttl = record_ttl; } } found++; } } else { pr_trace_msg(trace_channel, 9, "found additional AAAA resource record (#%u, %s) for '%s' with bad " "length (%d), skipping", i + 1, record_name, query, ns_rr_rdlen(record)); } break; case ns_t_cname: pr_trace_msg(trace_channel, 9, "found additional CNAME resource record (#%u, %s) for '%s' " "(TTL %lu), skipping", i + 1, record_name, query, (unsigned long) record_ttl); break; default: pr_trace_msg(trace_channel, 9, "found additional unexpected resource record (#%u, %d, %s) for '%s', " "skipping", i + 1, ns_rr_type(record), record_name, query); break; } } if (found == 0) { /* No matching addresses found in "Additional data"; resolve manually. */ if (dns_resolve_srv_name(p, srv, *resp) < 0) { return -1; } } return 0; } static int dns_resolve_srv_targets(pool *p, const char *query, array_header *srvs, ns_msg msgh, array_header **resp, uint32_t *ttl) { register unsigned int i; struct srv_record **elts; *resp = make_array(p, srvs->nelts, sizeof(pr_netaddr_t *)); elts = srvs->elts; for (i = 0; i < srvs->nelts; i++) { struct srv_record *srv; srv = elts[i]; if (dns_resolve_srv_target(p, query, srv, msgh, resp, ttl) < 0) { pr_trace_msg(trace_channel, 3, "error resolving SRV target '%s' to address: %s", srv->target, strerror(errno)); } } return 0; } static int dns_resolve_srv(pool *p, const char *name, array_header **resp, uint32_t *ttl) { register unsigned int i; int answerlen, res; unsigned char answer[NS_PACKETSZ * 4]; unsigned int count; ns_msg msgh; pool *srv_pool; array_header *srvs; pr_trace_msg(trace_channel, 17, "querying DNS for SRV records for '%s'", name); answerlen = res_query(name, ns_c_in, ns_t_srv, answer, sizeof(answer)-1); pr_trace_msg(trace_channel, 22, "received answer (%d bytes) of SRV records " "for '%s'", answerlen, name); if (answerlen < 0) { return dns_query_error("SRV", name); } if (ns_initparse(answer, answerlen, &msgh) < 0) { pr_trace_msg(trace_channel, 2, "failed parsing SRV response for '%s'", name); errno = EINVAL; return -1; } count = ns_msg_count(msgh, ns_s_an); pr_trace_msg(trace_channel, 17, "found %u %s in the '%s' SRV answer section", count, count != 1 ? "records" : "record", name); srv_pool = make_sub_pool(p); pr_pool_tag(srv_pool, "SRV records"); srvs = make_array(srv_pool, count, sizeof(struct srv_record *)); /* Note: What does it mean, if there are more than one SRV records for a * given service for a domain? * * Answer: Consider the different priorities, different weights. So yes, * it's quite probable. Hopefully each of the different SRV records has * a different target. Right? */ for (i = 0; i < count; i++) { ns_rr record; uint16_t priority, weight, port, offset; uint32_t record_ttl; size_t target_len; char *target_text; int expanded_namelen; char expanded_name[NS_MAXDNAME]; struct srv_record *srv; pr_signals_handle(); if (ns_parserr(&msgh, ns_s_an, i, &record) < 0) { pr_trace_msg(trace_channel, 4, "error parsing DNS resource record #%u, skipping: %s", i + 1, strerror(errno)); continue; } if (ns_rr_type(record) != ns_t_srv) { pr_trace_msg(trace_channel, 4, "found non-SRV DNS resource record #%u, skipping", i + 1); continue; } record_ttl = ns_rr_ttl(record); offset = 0; priority = ns_get16(ns_rr_rdata(record) + offset); offset += NS_INT16SZ; weight = ns_get16(ns_rr_rdata(record) + offset); /* TODO: Watch out for port 0 values! */ offset += NS_INT16SZ; port = ns_get16(ns_rr_rdata(record) + offset); offset += NS_INT16SZ; /* Ideally, we would assume proper RFC 2782 implementations, and would NOT * attempt to decompress the target names. For related issues, see: * * systemd should not compress target names in SRV records: * https://github.com/systemd/systemd/issues/9793 * * net: target domain names in SRV records should not be decompressed * https://github.com/golang/go/issues/10622 * * However, we opportunistically attempt to uncompress the target name, * for now. Behavior subject to change without notice. */ expanded_namelen = ns_name_uncompress(ns_msg_base(msgh), ns_msg_end(msgh), ns_rr_rdata(record) + offset, expanded_name, sizeof(expanded_name)); if (expanded_namelen < 0) { /* Assume the target name was properly NOT compressed. */ target_len = ns_rr_rdlen(record) - offset; target_text = pcalloc(srv_pool, target_len + 1); memcpy(target_text, (unsigned char *) ns_rr_rdata(record) + offset, target_len); } else { target_len = expanded_namelen; target_text = pcalloc(srv_pool, target_len + 1); memcpy(target_text, expanded_name, expanded_namelen); } pr_trace_msg(trace_channel, 17, "resolved '%s' to SRV record #%u " "(TTL %lu): priority = %u, weight = %u, port = %u, target = '%s'", name, i + 1, (unsigned long) record_ttl, priority, weight, port, target_text); /* If target is ".", abort (per RFC 2782); this means that this service * is decidedly not offered for this host/domain. */ if (strcmp(target_text, ".") == 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "SRV records for '%s' indicate that the service is explicitly " "not available", name); *resp = NULL; errno = ENOENT; return -1; } srv = palloc(srv_pool, sizeof(struct srv_record)); srv->priority = priority; srv->weight = weight; srv->port = port; srv->target = target_text; *((struct srv_record **) push_array(srvs)) = srv; } /* Sort our SRV records to get the ordered list of target names/ports. */ qsort(srvs->elts, srvs->nelts, sizeof(struct srv_record *), srv_cmp); res = dns_resolve_srv_targets(p, name, srvs, msgh, resp, ttl); if (res < 0) { pr_trace_msg(trace_channel, 3, "error resolving SRV targets to addresses: %s", strerror(errno)); } destroy_pool(srv_pool); return (*resp)->nelts; } static int dns_resolve_txt(pool *p, const char *name, array_header **resp, uint32_t *ttl) { register unsigned int i; int answerlen; unsigned char answer[NS_PACKETSZ * 4]; unsigned int count; ns_msg msgh; pr_trace_msg(trace_channel, 17, "querying DNS for TXT records for '%s'", name); answerlen = res_query(name, ns_c_in, ns_t_txt, answer, sizeof(answer)-1); pr_trace_msg(trace_channel, 22, "received answer (%d bytes) of TXT records " "for '%s'", answerlen, name); if (answerlen < 0) { return dns_query_error("TXT", name); } if (ns_initparse(answer, answerlen, &msgh) < 0) { pr_trace_msg(trace_channel, 2, "failed parsing TXT response for '%s'", name); errno = EINVAL; return -1; } count = ns_msg_count(msgh, ns_s_an); pr_trace_msg(trace_channel, 17, "found %u %s in the '%s' TXT answer section", count, count != 1 ? "records" : "record", name); *resp = make_array(p, count, sizeof(char *)); for (i = 0; i < count; i++) { ns_rr record; uint32_t record_ttl; size_t record_len; char *record_text; pr_signals_handle(); if (ns_parserr(&msgh, ns_s_an, i, &record) < 0) { pr_trace_msg(trace_channel, 4, "error parsing DNS resource record #%u, skipping: %s", i + 1, strerror(errno)); continue; } if (ns_rr_type(record) != ns_t_txt) { pr_trace_msg(trace_channel, 4, "found non-TXT DNS resource record #%u, skipping", i + 1); continue; } record_ttl = ns_rr_ttl(record); record_len = ns_rr_rdlen(record) - 1; record_text = pcalloc(p, record_len + 1); memcpy(record_text, (unsigned char *) ns_rr_rdata(record) + 1, record_len); pr_trace_msg(trace_channel, 17, "resolved '%s' to TXT record #%u: '%s' (TTL %lu)", name, i + 1, record_text, (unsigned long) record_ttl); /* It is up to the caller to filter through these TXT records, looking for * what they want (e.g. URLs). */ *((char **) push_array(*resp)) = record_text; if (ttl != NULL) { if (record_ttl < *ttl) { *ttl = record_ttl; } } } return (*resp)->nelts; } /* Note that this is mostly used for resolving SRV, TXT records. */ int proxy_dns_resolve(pool *p, const char *name, proxy_dns_type_e dns_type, array_header **resp, uint32_t *ttl) { int res; if (p == NULL || name == NULL || resp == NULL) { errno = EINVAL; return -1; } switch (dns_type) { case PROXY_DNS_A: /* Currently not implemented. */ errno = ENOSYS; res = -1; break; #if defined(PR_USE_IPV6) case PROXY_DNS_AAAA: /* Currently not implemented. */ errno = ENOSYS; res = -1; break; #endif /* PR_USE_IPV6 */ case PROXY_DNS_SRV: res = dns_resolve_srv(p, name, resp, ttl); break; case PROXY_DNS_TXT: res = dns_resolve_txt(p, name, resp, ttl); break; case PROXY_DNS_UNKNOWN: default: errno = EPERM; res = -1; } return res; } proftpd-mod_proxy-0.9.5/lib/proxy/forward.c000066400000000000000000000667141475737016700210170ustar00rootroot00000000000000/* * ProFTPD - mod_proxy forward proxy implementation * Copyright (c) 2012-2022 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "proxy/conn.h" #include "proxy/netio.h" #include "proxy/inet.h" #include "proxy/forward.h" #include "proxy/tls.h" #include "proxy/ftp/ctrl.h" #include "proxy/ftp/sess.h" static int proxy_method = PROXY_FORWARD_METHOD_USER_WITH_PROXY_AUTH; static int forward_retry_count = PROXY_DEFAULT_RETRY_COUNT; /* handle_user_passthru flags */ #define PROXY_FORWARD_USER_PASSTHRU_FL_PARSE_DSTADDR 0x001 #define PROXY_FORWARD_USER_PASSTHRU_FL_CONNECT_DSTADDR 0x002 #define PROXY_FORWARD_USER_PASSTHRU_FL_SNI_DSTADDR 0x004 static const char *trace_channel = "proxy.forward"; int proxy_forward_use_proxy_auth(void) { switch (proxy_method) { case PROXY_FORWARD_METHOD_USER_NO_PROXY_AUTH: case PROXY_FORWARD_METHOD_USER_SNI_NO_PROXY_AUTH: return FALSE; default: break; } return TRUE; } int proxy_forward_init(pool *p, const char *tables_dir) { return 0; } int proxy_forward_free(pool *p) { /* TODO: Implement any necessary cleanup */ return 0; } int proxy_forward_sess_free(pool *p, struct proxy_session *proxy_sess) { /* Reset any state. */ proxy_method = PROXY_FORWARD_METHOD_USER_WITH_PROXY_AUTH; forward_retry_count = PROXY_DEFAULT_RETRY_COUNT; return 0; } int proxy_forward_sess_init(pool *p, const char *tables_dir, struct proxy_session *proxy_sess) { config_rec *c; int allowed = FALSE; const void *enabled = NULL; /* By default, only allow connections from RFC1918 addresses to use * forward proxying. Otherwise, it must be from an explicitly allowed * connection class, via the class notes. */ if (session.conn_class != NULL) { enabled = pr_table_get(session.conn_class->cls_notes, PROXY_FORWARD_ENABLED_NOTE, NULL); } if (enabled != NULL) { allowed = *((int *) enabled); if (allowed == FALSE) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "forward proxying not allowed from client address %s in " "(see ProxyForwardEnabled)", pr_netaddr_get_ipstr(session.c->remote_addr), session.conn_class->cls_name); } } else { if (pr_netaddr_is_rfc1918(session.c->remote_addr) == TRUE) { allowed = TRUE; } else { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "forward proxying not allowed from non-RFC1918 client address %s", pr_netaddr_get_ipstr(session.c->remote_addr)); } } if (allowed == FALSE) { errno = EPERM; return -1; } c = find_config(main_server->conf, CONF_PARAM, "ProxyForwardMethod", FALSE); if (c != NULL) { proxy_method = *((int *) c->argv[0]); } c = find_config(main_server->conf, CONF_PARAM, "ProxyRetryCount", FALSE); if (c != NULL) { forward_retry_count = *((int *) c->argv[0]); } return 0; } int proxy_forward_have_authenticated(cmd_rec *cmd) { int authd = FALSE; /* Authenticated here means authenticated *to the proxy*, i.e. should we * allow more commands, or reject them because the client hasn't authenticated * yet. */ switch (proxy_method) { case PROXY_FORWARD_METHOD_USER_NO_PROXY_AUTH: authd = TRUE; break; case PROXY_FORWARD_METHOD_PROXY_USER_WITH_PROXY_AUTH: case PROXY_FORWARD_METHOD_USER_WITH_PROXY_AUTH: if (proxy_sess_state & PROXY_SESS_STATE_PROXY_AUTHENTICATED) { authd = TRUE; } break; default: authd = FALSE; } if (authd == FALSE) { pr_response_send(R_530, _("Please login with USER and PASS")); } return authd; } static int forward_tls_postopen(pool *p, struct proxy_session *proxy_sess, conn_t *server_conn, pr_response_t **resp) { int xerrno; if (proxy_netio_postopen(server_conn->instrm) < 0) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "postopen error for backend control connection input stream: %s", strerror(xerrno)); proxy_inet_close(session.pool, server_conn); proxy_sess->backend_ctrl_conn = NULL; *resp = NULL; errno = xerrno; return -1; } if (proxy_netio_postopen(server_conn->outstrm) < 0) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "postopen error for backend control connection output stream: %s", strerror(xerrno)); proxy_inet_close(session.pool, server_conn); proxy_sess->backend_ctrl_conn = NULL; *resp = NULL; errno = xerrno; return -1; } return 0; } static int forward_connect(pool *p, struct proxy_session *proxy_sess, pr_response_t **resp, unsigned int *resp_nlines) { conn_t *server_conn = NULL; int banner_ok = TRUE, use_tls, xerrno = 0; const pr_netaddr_t *dst_addr; array_header *other_addrs = NULL; char port_text[32]; dst_addr = proxy_sess->dst_addr; other_addrs = proxy_sess->other_addrs; if (proxy_tls_using_tls() == PROXY_TLS_ENGINE_MATCH_CLIENT) { proxy_tls_match_client_tls(); } /* If the destination port is 990, assume implicit FTPS. */ if (ntohs(pr_netaddr_get_port(dst_addr)) == PROXY_TLS_IMPLICIT_FTPS_PORT) { pr_trace_msg(trace_channel, 9, "%s#%u requesting, using implicit FTPS", pr_netaddr_get_ipstr(dst_addr), (unsigned int) ntohs(pr_netaddr_get_port(dst_addr))); proxy_tls_set_tls(PROXY_TLS_ENGINE_IMPLICIT); } server_conn = proxy_conn_get_server_conn(p, proxy_sess, dst_addr); if (server_conn == NULL) { xerrno = errno; if (other_addrs != NULL) { register unsigned int i; /* Try the other IP addresses for the requested name (if any) as well. */ for (i = 0; i < other_addrs->nelts; i++) { dst_addr = ((pr_netaddr_t **) other_addrs->elts)[i]; pr_trace_msg(trace_channel, 8, "attempting to connect to other address #%u (%s) for requested " "URI '%.100s'", i+1, pr_netaddr_get_ipstr(dst_addr), proxy_conn_get_uri(proxy_sess->dst_pconn)); server_conn = proxy_conn_get_server_conn(p, proxy_sess, dst_addr); if (server_conn != NULL) { proxy_sess->dst_addr = dst_addr; break; } } } if (server_conn == NULL) { xerrno = errno; /* EINVALs lead to strange-looking error responses; change them to * EPERM. */ if (xerrno == EINVAL) { xerrno = EPERM; } } errno = xerrno; return -1; } proxy_sess->frontend_ctrl_conn = session.c; proxy_sess->backend_ctrl_conn = server_conn; use_tls = proxy_tls_using_tls(); /* Handle implicit FTPS connects. */ if (use_tls == PROXY_TLS_ENGINE_IMPLICIT) { if (forward_tls_postopen(p, proxy_sess, server_conn, resp) < 0) { return -1; } } /* XXX Support/send a CLNT command of our own? Configurable via e.g. * "UserAgent" string? */ /* Read the response from the backend server. */ *resp = proxy_ftp_ctrl_recv_resp(p, proxy_sess->backend_ctrl_conn, resp_nlines, 0); if (*resp == NULL) { xerrno = errno; pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to read banner from server %s:%u: %s", pr_netaddr_get_ipstr(proxy_sess->backend_ctrl_conn->remote_addr), ntohs(pr_netaddr_get_port(proxy_sess->backend_ctrl_conn->remote_addr)), strerror(xerrno)); errno = EPERM; return -1; } if ((*resp)->num[0] != '2') { banner_ok = FALSE; } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "received banner from backend %s:%u%s: %s %s", pr_netaddr_get_ipstr(proxy_sess->backend_ctrl_conn->remote_addr), ntohs(pr_netaddr_get_port(proxy_sess->backend_ctrl_conn->remote_addr)), banner_ok ? "" : ", DISCONNECTING", (*resp)->num, (*resp)->msg); if (banner_ok == FALSE) { pr_inet_close(p, proxy_sess->backend_ctrl_conn); proxy_sess->backend_ctrl_conn = NULL; errno = EPERM; return -1; } /* Get the features supported by the backend server */ if (proxy_ftp_sess_get_feat(p, proxy_sess) < 0) { if (errno != EPERM) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to determine features of backend server: %s", strerror(errno)); } } use_tls = proxy_tls_using_tls(); if (use_tls != PROXY_TLS_ENGINE_OFF && use_tls != PROXY_TLS_ENGINE_IMPLICIT) { if (proxy_ftp_sess_send_auth_tls(p, proxy_sess) < 0 && errno != ENOSYS) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error enabling TLS on control connection to backend server: %s", strerror(xerrno)); pr_inet_close(p, proxy_sess->backend_ctrl_conn); proxy_sess->backend_ctrl_conn = NULL; *resp = NULL; errno = xerrno; return -1; } use_tls = proxy_tls_using_tls(); } if (use_tls != PROXY_TLS_ENGINE_OFF && use_tls != PROXY_TLS_ENGINE_IMPLICIT) { if (forward_tls_postopen(p, proxy_sess, server_conn, resp) < 0) { return -1; } } if (use_tls != PROXY_TLS_ENGINE_OFF) { if (proxy_sess_state & PROXY_SESS_STATE_BACKEND_HAS_CTRL_TLS) { /* NOTE: should this be a fatal error? */ (void) proxy_ftp_sess_send_pbsz_prot(p, proxy_sess); } } (void) proxy_ftp_sess_send_host(p, proxy_sess); /* Populate the session notes about this connection. */ memset(port_text, '\0', sizeof(port_text)); pr_snprintf(port_text, sizeof(port_text)-1, "%d", proxy_conn_get_port(proxy_sess->dst_pconn)); (void) pr_table_add_dup(session.notes, "mod_proxy.backend-ip", pr_netaddr_get_ipstr(dst_addr), 0); (void) pr_table_remove(session.notes, "mod_proxy.backend-port", NULL); (void) pr_table_add_dup(session.notes, "mod_proxy.backend-port", port_text, 0); (void) pr_table_add_dup(session.notes, "mod_proxy.backend-url", proxy_conn_get_uri(proxy_sess->dst_pconn), 0); proxy_sess_state |= PROXY_SESS_STATE_CONNECTED; return 0; } static int forward_dst_filter(pool *p, const char *hostport) { #ifdef PR_USE_REGEX config_rec *c; pr_regex_t *pre; int negated = FALSE, res; c = find_config(main_server->conf, CONF_PARAM, "ProxyForwardTo", FALSE); if (c == NULL) { return 0; } pre = c->argv[0]; negated = *((int *) c->argv[1]); res = pr_regexp_exec(pre, hostport, 0, NULL, 0, 0, 0); if (res == 0) { /* Pattern matched */ if (negated == TRUE) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "host/port '%.100s' matched ProxyForwardTo !%s, rejecting", hostport, pr_regexp_get_pattern(pre)); errno = EPERM; return -1; } } else { /* Pattern NOT matched */ if (negated == FALSE) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "host/port '%.100s' did not match ProxyForwardTo %s, rejecting", hostport, pr_regexp_get_pattern(pre)); errno = EPERM; return -1; } } #endif /* PR_USE_REGEX */ return 0; } static int forward_cmd_parse_dst(pool *p, const char *arg, char **name, const struct proxy_conn **pconn) { const char *default_proto = NULL, *default_port = NULL, *proto = NULL, *port, *uri = NULL; char *host = NULL, *hostport = NULL, *host_ptr = NULL, *port_ptr = NULL; /* TODO: Revisit these defaults once we start supporting other protocols. */ default_proto = "ftp"; default_port = "21"; /* First, look for the optional port. */ port_ptr = strrchr(arg, ':'); if (port_ptr == NULL) { port = default_port; } else { char *tmp2 = NULL; long num; num = strtol(port_ptr+1, &tmp2, 10); if (tmp2 && *tmp2) { /* Trailing garbage found in port number. */ (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "malformed port number '%s' found in USER '%s', rejecting", port_ptr+1, arg); errno = EINVAL; return -1; } if (num < 0 || num > 65535) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "invalid port number %ld found in USER '%s', rejecting", num, arg); errno = EINVAL; return -1; } port = pstrdup(p, port_ptr + 1); } /* Find the required '@' delimiter. */ host_ptr = strrchr(arg, '@'); if (host_ptr == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "missing required '@' delimiter in USER '%s', rejecting", arg); errno = EINVAL; return -1; } if (port_ptr == NULL) { host = pstrdup(p, host_ptr + 1); } else { host = pstrndup(p, host_ptr + 1, (port_ptr - host_ptr - 1)); } *name = pstrndup(p, arg, (host_ptr - arg)); proto = default_proto; hostport = pstrcat(p, host, ":", port, NULL); if (forward_dst_filter(p, hostport) < 0) { return -1; } uri = pstrcat(p, proto, "://", hostport, NULL); /* Note: We deliberately use proxy_pool, rather than the given pool, here * so that the created structure (especially the pr_netaddr_t) are * longer-lived. */ *pconn = proxy_conn_create(proxy_pool, uri, 0); if (*pconn == NULL) { int xerrno = errno; pr_trace_msg(trace_channel, 1, "error handling URI '%.100s': %s", uri, strerror(xerrno)); errno = xerrno; return -1; } return 0; } static int forward_cmd_parse_sni(pool *p, const struct proxy_conn **pconn) { const char *sni = NULL; char *hostport = NULL, *port_ptr = NULL, *uri = NULL; sni = pr_table_get(session.notes, "mod_tls.sni", NULL); if (sni == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to use ProxyForwardMethod 'user@sni' due to missing TLS SNI"); errno = EPERM; return -1; } port_ptr = strrchr(sni, ':'); if (port_ptr == NULL) { hostport = pstrcat(p, sni, ":21", NULL); } else { /* In this case, the SNI already includes a port; no need to add one. */ hostport = pstrdup(p, sni); } if (forward_dst_filter(p, hostport) < 0) { return -1; } uri = pstrcat(p, "ftp://", hostport, NULL); /* Note: We deliberately use proxy_pool, rather than the given pool, here * so that the created structure (especially the pr_netaddr_t) are * longer-lived. */ *pconn = proxy_conn_create(proxy_pool, uri, 0); if (*pconn == NULL) { int xerrno = errno; pr_trace_msg(trace_channel, 1, "error handling URI '%.100s': %s", uri, strerror(xerrno)); errno = xerrno; return -1; } return 0; } static int forward_handle_user_passthru(cmd_rec *cmd, struct proxy_session *proxy_sess, int *successful, int flags) { int res, xerrno; char *user = NULL; cmd_rec *user_cmd = NULL; pr_response_t *resp = NULL; unsigned int resp_nlines = 0; if ((flags & PROXY_FORWARD_USER_PASSTHRU_FL_PARSE_DSTADDR) || (flags & PROXY_FORWARD_USER_PASSTHRU_FL_SNI_DSTADDR)) { const struct proxy_conn *pconn = NULL; const pr_netaddr_t *remote_addr = NULL; array_header *other_addrs = NULL; if (flags & PROXY_FORWARD_USER_PASSTHRU_FL_PARSE_DSTADDR) { res = forward_cmd_parse_dst(cmd->tmp_pool, cmd->arg, &user, &pconn); } else { res = forward_cmd_parse_sni(cmd->tmp_pool, &pconn); } if (res < 0) { errno = EINVAL; return -1; } remote_addr = proxy_conn_get_addr(pconn, &other_addrs); /* Ensure that the requested remote address is NOT (blatantly) ourselves, * i.e. the proxy itself. This prevents easy-to-detect proxy loops. */ if (pr_netaddr_cmp(remote_addr, session.c->local_addr) == 0 && pr_netaddr_get_port(remote_addr) == pr_netaddr_get_port(session.c->local_addr)) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "requested destination %s#%u is local address %s#%u, rejecting", pr_netaddr_get_ipstr(remote_addr), ntohs(pr_netaddr_get_port(remote_addr)), pr_netaddr_get_ipstr(session.c->local_addr), ntohs(pr_netaddr_get_port(session.c->local_addr))); pr_response_send(R_530, _("Unable to connect to %s: %s"), proxy_conn_get_hostport(pconn), strerror(EPERM)); return 1; } proxy_sess->dst_addr = remote_addr; proxy_sess->other_addrs = other_addrs; proxy_sess->dst_pconn = pconn; if (flags & PROXY_FORWARD_USER_PASSTHRU_FL_PARSE_DSTADDR) { /* Change the command so that it no longer includes the proxy info. */ user_cmd = pr_cmd_alloc(cmd->pool, 2, C_USER, user); user_cmd->arg = user; } else { user_cmd = cmd; } } else { user_cmd = cmd; } if (flags & PROXY_FORWARD_USER_PASSTHRU_FL_CONNECT_DSTADDR) { pr_response_t *banner = NULL; unsigned int banner_nlines = 0; res = forward_connect(proxy_pool, proxy_sess, &banner, &banner_nlines); if (res < 0) { xerrno = errno; *successful = FALSE; /* Send a failed USER response to our waiting frontend client, but do * not necessarily close the frontend connection. */ resp = pcalloc(cmd->tmp_pool, sizeof(pr_response_t)); resp->num = R_530; if (banner != NULL) { resp->msg = banner->msg; resp_nlines = banner_nlines; } else { resp->msg = pstrcat(cmd->tmp_pool, "Unable to connect to ", proxy_conn_get_hostport(proxy_sess->dst_pconn), ": ", strerror(xerrno), NULL); resp_nlines = 1; } res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool, proxy_sess->frontend_ctrl_conn, resp, resp_nlines); if (res < 0) { xerrno = errno; pr_response_block(TRUE); errno = xerrno; return -1; } errno = EINVAL; return 1; } } res = proxy_ftp_ctrl_send_cmd(cmd->tmp_pool, proxy_sess->backend_ctrl_conn, user_cmd); if (res < 0) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error sending %s to backend: %s", (char *) user_cmd->argv[0], strerror(xerrno)); errno = xerrno; return -1; } resp = proxy_ftp_ctrl_recv_resp(cmd->tmp_pool, proxy_sess->backend_ctrl_conn, &resp_nlines, 0); if (resp == NULL) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error receiving %s response from backend: %s", (char *) cmd->argv[0], strerror(xerrno)); errno = xerrno; return -1; } if (resp->num[0] == '2' || resp->num[0] == '3') { *successful = TRUE; if (strcmp(resp->num, R_232) == 0) { proxy_sess_state |= PROXY_SESS_STATE_BACKEND_AUTHENTICATED; pr_timer_remove(PR_TIMER_LOGIN, ANY_MODULE); } } /* XXX TODO: Concatenate the banner from the connect with the USER response * message here, and send the entire kit to the frontend client, e.g.: * * Name (gatekeeper:you): anonymous@ftp.uu.net * 331-(----GATEWAY CONNECTED TO ftp.uu.net----) * 331-(220 ftp.uu.net FTP server (SunOS 4.1) ready. * 331 Guest login ok, send ident as password. * Password: ###### * 230 Guest login ok, access restrictions apply. * ftp> dir */ res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool, proxy_sess->frontend_ctrl_conn, resp, resp_nlines); if (res < 0) { xerrno = errno; pr_response_block(TRUE); errno = xerrno; return -1; } return 1; } static int forward_handle_user_proxyuserwithproxyauth(cmd_rec *cmd, struct proxy_session *proxy_sess, int *successful, int *block_responses) { int flags = 0, res; if (!(proxy_sess_state & PROXY_SESS_STATE_PROXY_AUTHENTICATED)) { char *user = NULL; const struct proxy_conn *pconn = NULL; const pr_netaddr_t *remote_addr = NULL; array_header *other_addrs = NULL; res = forward_cmd_parse_dst(cmd->pool, cmd->arg, &user, &pconn); if (res < 0) { errno = EINVAL; return -1; } remote_addr = proxy_conn_get_addr(pconn, &other_addrs); proxy_sess->dst_addr = remote_addr; proxy_sess->other_addrs = other_addrs; proxy_sess->dst_pconn = pconn; /* Rewrite the USER command here with the trimmed/truncated name. */ pr_cmd_clear_cache(cmd); cmd->arg = cmd->argv[1] = pstrdup(cmd->pool, user); /* By returning zero here, we let the rest of the proftpd internals * deal with the USER command locally, leading to proxy auth. */ *block_responses = FALSE; return 0; } flags = PROXY_FORWARD_USER_PASSTHRU_FL_CONNECT_DSTADDR; res = forward_handle_user_passthru(cmd, proxy_sess, successful, flags); return res; } static int forward_handle_user_userwithproxyauth(cmd_rec *cmd, struct proxy_session *proxy_sess, int *successful, int *block_responses) { int flags = 0, res; if (!(proxy_sess_state & PROXY_SESS_STATE_PROXY_AUTHENTICATED)) { /* By returning zero here, we let the rest of the proftpd internals * deal with the USER command locally, leading to proxy auth. */ *block_responses = FALSE; return 0; } flags = PROXY_FORWARD_USER_PASSTHRU_FL_PARSE_DSTADDR|PROXY_FORWARD_USER_PASSTHRU_FL_CONNECT_DSTADDR; res = forward_handle_user_passthru(cmd, proxy_sess, successful, flags); return res; } int proxy_forward_handle_user(cmd_rec *cmd, struct proxy_session *proxy_sess, int *successful, int *block_responses) { int res = -1; /* Look at our proxy method to see what we should do here. */ switch (proxy_method) { case PROXY_FORWARD_METHOD_USER_NO_PROXY_AUTH: { int flags = PROXY_FORWARD_USER_PASSTHRU_FL_PARSE_DSTADDR|PROXY_FORWARD_USER_PASSTHRU_FL_CONNECT_DSTADDR; res = forward_handle_user_passthru(cmd, proxy_sess, successful, flags); break; } case PROXY_FORWARD_METHOD_USER_SNI_NO_PROXY_AUTH: { int flags = PROXY_FORWARD_USER_PASSTHRU_FL_SNI_DSTADDR|PROXY_FORWARD_USER_PASSTHRU_FL_CONNECT_DSTADDR; /* This method requires use of TLS with SNI; make sure that that is * actually the case for this session. */ if (session.rfc2228_mech != NULL && strcmp(session.rfc2228_mech, "TLS") == 0) { res = forward_handle_user_passthru(cmd, proxy_sess, successful, flags); } else { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to use ProxyForwardMethod 'user@sni' due to lack of TLS"); errno = EINVAL; res = -1; } break; } case PROXY_FORWARD_METHOD_USER_WITH_PROXY_AUTH: res = forward_handle_user_userwithproxyauth(cmd, proxy_sess, successful, block_responses); break; case PROXY_FORWARD_METHOD_PROXY_USER_WITH_PROXY_AUTH: res = forward_handle_user_proxyuserwithproxyauth(cmd, proxy_sess, successful, block_responses); break; default: errno = ENOSYS; res = -1; } return res; } static int forward_handle_pass_passthru(cmd_rec *cmd, struct proxy_session *proxy_sess, int *successful) { int res, xerrno; pr_response_t *resp; unsigned int resp_nlines = 0; res = proxy_ftp_ctrl_send_cmd(cmd->tmp_pool, proxy_sess->backend_ctrl_conn, cmd); if (res < 0) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error sending %s to backend: %s", (char *) cmd->argv[0], strerror(xerrno)); errno = xerrno; return -1; } resp = proxy_ftp_ctrl_recv_resp(cmd->tmp_pool, proxy_sess->backend_ctrl_conn, &resp_nlines, 0); if (resp == NULL) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error receiving %s response from backend: %s", (char *) cmd->argv[0], strerror(xerrno)); /* If we receive an EPERM here, it is probably because the backend * closed its control connection, yielding an EOF. To better indicate * this situation, propagate the error using EPIPE. */ if (xerrno == EPERM) { xerrno = EPIPE; } errno = xerrno; return -1; } /* XXX What about other response codes for PASS? */ if (resp->num[0] == '2') { *successful = TRUE; proxy_sess_state |= PROXY_SESS_STATE_BACKEND_AUTHENTICATED; } res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool, proxy_sess->frontend_ctrl_conn, resp, resp_nlines); if (res < 0) { xerrno = errno; pr_response_block(TRUE); errno = xerrno; return -1; } return 1; } static int forward_handle_pass_userwithproxyauth(cmd_rec *cmd, struct proxy_session *proxy_sess, int *successful, int *block_responses) { if (!(proxy_sess_state & PROXY_SESS_STATE_PROXY_AUTHENTICATED)) { int res; const char *user; user = pr_table_get(session.notes, "mod_auth.orig-user", NULL); res = proxy_session_check_password(cmd->pool, user, cmd->arg); if (res < 0) { errno = EINVAL; return -1; } res = proxy_session_setup_env(proxy_pool, user, PROXY_SESSION_FL_CHECK_LOGIN_ACL); if (res < 0) { errno = EINVAL; return -1; } if (session.auth_mech) { pr_log_debug(DEBUG2, "user '%s' authenticated by %s", user, session.auth_mech); } pr_response_send(R_230, _("User %s logged in"), user); return 1; } return forward_handle_pass_passthru(cmd, proxy_sess, successful); } static int forward_handle_pass_proxyuserwithproxyauth(cmd_rec *cmd, struct proxy_session *proxy_sess, int *successful, int *block_responses) { /* The functionality is identical to that of handle_pass_userwithproxyauth. */ return forward_handle_pass_userwithproxyauth(cmd, proxy_sess, successful, block_responses); } int proxy_forward_handle_pass(cmd_rec *cmd, struct proxy_session *proxy_sess, int *successful, int *block_responses) { int res = -1, xerrno = 0; /* Look at our proxy method to see what we should do here. */ switch (proxy_method) { case PROXY_FORWARD_METHOD_USER_NO_PROXY_AUTH: case PROXY_FORWARD_METHOD_USER_SNI_NO_PROXY_AUTH: res = forward_handle_pass_passthru(cmd, proxy_sess, successful); xerrno = errno; if (res == 1) { pr_timer_remove(PR_TIMER_LOGIN, ANY_MODULE); } break; case PROXY_FORWARD_METHOD_USER_WITH_PROXY_AUTH: res = forward_handle_pass_userwithproxyauth(cmd, proxy_sess, successful, block_responses); xerrno = errno; if (res == 1) { pr_timer_remove(PR_TIMER_LOGIN, ANY_MODULE); } break; case PROXY_FORWARD_METHOD_PROXY_USER_WITH_PROXY_AUTH: res = forward_handle_pass_proxyuserwithproxyauth(cmd, proxy_sess, successful, block_responses); xerrno = errno; if (res == 1) { pr_timer_remove(PR_TIMER_LOGIN, ANY_MODULE); } break; default: xerrno = ENOSYS; res = -1; } errno = xerrno; return res; } int proxy_forward_get_method(const char *method) { if (method == NULL) { errno = EINVAL; return -1; } if (strcasecmp(method, "proxyuser,user@host") == 0) { return PROXY_FORWARD_METHOD_USER_WITH_PROXY_AUTH; } else if (strcasecmp(method, "user@host") == 0) { return PROXY_FORWARD_METHOD_USER_NO_PROXY_AUTH; } else if (strcasecmp(method, "proxyuser@host,user") == 0) { return PROXY_FORWARD_METHOD_PROXY_USER_WITH_PROXY_AUTH; } else if (strcasecmp(method, "user@sni") == 0) { return PROXY_FORWARD_METHOD_USER_SNI_NO_PROXY_AUTH; } errno = ENOENT; return -1; } proftpd-mod_proxy-0.9.5/lib/proxy/ftp/000077500000000000000000000000001475737016700177625ustar00rootroot00000000000000proftpd-mod_proxy-0.9.5/lib/proxy/ftp/conn.c000066400000000000000000000245141475737016700210710ustar00rootroot00000000000000/* * ProFTPD - mod_proxy FTP connection routines * Copyright (c) 2013-2025 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "include/proxy/inet.h" #include "include/proxy/netio.h" #include "include/proxy/ftp/conn.h" static const char *trace_channel = "proxy.ftp.conn"; static int set_conn_socket_opts(pool *p, conn_t *conn, int rcvbufsz, int sndbufsz, struct tcp_keepalive *keepalive, int reuse_port) { int res; #if PROFTPD_VERSION_NUMBER >= 0x0001030801 res = pr_inet_set_socket_opts2(p, conn, rcvbufsz, sndbufsz, keepalive, reuse_port); #else res = pr_inet_set_socket_opts(p, conn, rcvbufsz, sndbufsz, keepalive); /* Earlier versions of ProFTPD did not support setting the SO_REUSEPORT * socket option via pr_inet_set_socket_opts(), so we do it ourselves. * * For active data transfers, enabling SO_REUSEPORT can be very useful, * since the number/range of available source ports may be small. */ # if defined(SO_REUSEPORT) if (setsockopt(conn->listen_fd, SOL_SOCKET, SO_REUSEPORT, (void *) &reuse_port, sizeof(reuse_port)) < 0) { pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error setting SO_REUSEPORT on fd %d: %s", conn->listen_fd, strerror(errno)); } else { pr_trace_msg(trace_channel, 8, "set socket fd %d reuseport = %d", conn->listen_fd, reuse_port); } # endif /* SO_REUSEPORT */ #endif /* ProFTPD 1.3.8rc1 or later */ return res; } conn_t *proxy_ftp_conn_accept(pool *p, conn_t *data_conn, conn_t *ctrl_conn, int frontend_data) { conn_t *conn; int reverse_dns; if (p == NULL || data_conn == NULL || ctrl_conn == NULL) { errno = EINVAL; return NULL; } reverse_dns = pr_netaddr_set_reverse_dns(ServerUseReverseDNS); if (session.xfer.direction == PR_NETIO_IO_RD) { set_conn_socket_opts(data_conn->pool, data_conn, (main_server->tcp_rcvbuf_override ? main_server->tcp_rcvbuf_len : 0), 0, main_server->tcp_keepalive, 0); } else { set_conn_socket_opts(data_conn->pool, data_conn, 0, (main_server->tcp_sndbuf_override ? main_server->tcp_sndbuf_len : 0), main_server->tcp_keepalive, 0); } if (frontend_data) { conn = pr_inet_accept(session.pool, data_conn, ctrl_conn, -1, -1, TRUE); } else { conn = proxy_inet_accept(session.pool, data_conn, ctrl_conn, -1, -1, TRUE); } pr_netaddr_set_reverse_dns(reverse_dns); if (conn == NULL) { int xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error accepting backend data connection: %s", strerror(xerrno)); errno = xerrno; return NULL; } /* Check for error conditions. */ if (conn->mode == CM_ERROR) { int xerrno = conn->xerrno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error accepting backend data connection: %s", strerror(xerrno)); destroy_pool(conn->pool); errno = xerrno; return NULL; } if (frontend_data) { pr_pool_tag(conn->pool, "proxy frontend data accept conn pool"); } else { pr_pool_tag(conn->pool, "proxy backend data accept conn pool"); } pr_trace_msg(trace_channel, 9, "accepted connection from server '%s'", conn->remote_name); return conn; } conn_t *proxy_ftp_conn_connect(pool *p, const pr_netaddr_t *bind_addr, const pr_netaddr_t *remote_addr, int frontend_data) { conn_t *conn, *opened = NULL; int default_inet_family = 0, remote_family, res, reverse_dns, xerrno; if (p == NULL || remote_addr == NULL) { errno = EINVAL; return NULL; } remote_family = pr_netaddr_get_family(remote_addr); pr_trace_msg(trace_channel, 9, "using %s family for backend socket address %s", remote_family == AF_INET ? "IPv4" : "IPv6", pr_netaddr_get_ipstr(remote_addr)); default_inet_family = pr_inet_set_default_family(p, remote_family); conn = pr_inet_create_conn(session.pool, -1, bind_addr, INPORT_ANY, TRUE); xerrno = errno; if (conn == NULL) { pr_inet_set_default_family(p, default_inet_family); errno = xerrno; return NULL; } reverse_dns = pr_netaddr_set_reverse_dns(ServerUseReverseDNS); if (session.xfer.direction == PR_NETIO_IO_RD) { set_conn_socket_opts(conn->pool, conn, (main_server->tcp_rcvbuf_override ? main_server->tcp_rcvbuf_len : 0), 0, main_server->tcp_keepalive, 1); } else { set_conn_socket_opts(conn->pool, conn, 0, (main_server->tcp_sndbuf_override ? main_server->tcp_sndbuf_len : 0), main_server->tcp_keepalive, 1); } pr_inet_set_proto_opts(session.pool, conn, main_server->tcp_mss_len, 1, IPTOS_THROUGHPUT, 1); pr_inet_generate_socket_event("proxy.data-connect", main_server, conn->local_addr, conn->listen_fd); pr_trace_msg(trace_channel, 9, "connecting to %s#%u from %s#%u", pr_netaddr_get_ipstr(remote_addr), ntohs(pr_netaddr_get_port(remote_addr)), pr_netaddr_get_ipstr(bind_addr), ntohs(pr_netaddr_get_port(bind_addr))); if (frontend_data == TRUE) { res = pr_inet_connect(p, conn, remote_addr, ntohs(pr_netaddr_get_port(remote_addr))); } else { res = proxy_inet_connect(p, conn, remote_addr, ntohs(pr_netaddr_get_port(remote_addr))); } if (res < 0) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to connect to %s#%u: %s\n", pr_netaddr_get_ipstr(remote_addr), ntohs(pr_netaddr_get_port(remote_addr)), strerror(xerrno)); if (frontend_data == FALSE) { proxy_inet_close(session.pool, conn); } pr_inet_close(session.pool, conn); errno = xerrno; return NULL; } /* XXX Will it always be STRM_DATA? */ if (frontend_data == TRUE) { opened = pr_inet_openrw(session.pool, conn, NULL, PR_NETIO_STRM_DATA, conn->listen_fd, -1, -1, TRUE); } else { opened = proxy_inet_openrw(session.pool, conn, NULL, PR_NETIO_STRM_DATA, conn->listen_fd, -1, -1, TRUE); } pr_netaddr_set_reverse_dns(reverse_dns); if (opened == NULL) { xerrno = errno; if (frontend_data == FALSE) { proxy_inet_close(session.pool, conn); } pr_inet_close(session.pool, conn); errno = xerrno; return NULL; } /* The conn returned by pr_inet_openrw() is a copy of the input conn; * we no longer need the input conn at this point. */ if (frontend_data == TRUE) { pr_inet_close(session.pool, conn); pr_pool_tag(opened->pool, "proxy frontend data connect conn pool"); } else { proxy_inet_close(session.pool, conn); pr_inet_close(session.pool, conn); pr_pool_tag(opened->pool, "proxy backend data connect conn pool"); } pr_inet_set_nonblock(session.pool, opened); pr_trace_msg(trace_channel, 9, "connected to server '%s'", opened->remote_name); return opened; } conn_t *proxy_ftp_conn_listen(pool *p, const pr_netaddr_t *bind_addr, int frontend_data) { int res; conn_t *conn = NULL; config_rec *c; if (p == NULL || bind_addr == NULL) { errno = EINVAL; return NULL; } c = find_config(main_server->conf, CONF_PARAM, "PassivePorts", FALSE); if (c != NULL) { int pasv_min_port = *((int *) c->argv[0]); int pasv_max_port = *((int *) c->argv[1]); conn = pr_inet_create_conn_portrange(session.pool, bind_addr, pasv_min_port, pasv_max_port); if (conn == NULL) { /* If not able to open a passive port in the given range, default to * normal behavior (using INPORT_ANY), and log the failure. This * indicates a too-small range configuration. */ pr_log_pri(PR_LOG_WARNING, "unable to find open port in PassivePorts range %d-%d: " "defaulting to INPORT_ANY (consider defining a larger PassivePorts " "range)", pasv_min_port, pasv_max_port); } } if (conn == NULL) { conn = pr_inet_create_conn(session.pool, -1, bind_addr, INPORT_ANY, FALSE); } if (conn == NULL) { int xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error creating socket: %s", strerror(xerrno)); errno = EINVAL; return NULL; } /* Make sure that necessary socket options are set on the socket prior * to the call to listen(2). */ pr_inet_set_proto_opts(session.pool, conn, main_server->tcp_mss_len, 1, IPTOS_THROUGHPUT, 1); pr_inet_generate_socket_event("proxy.data-listen", main_server, conn->local_addr, conn->listen_fd); pr_inet_set_block(session.pool, conn); if (frontend_data) { res = pr_inet_listen(session.pool, conn, 1, 0); } else { res = proxy_inet_listen(session.pool, conn, 1, 0); } if (res < 0) { int xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to listen on %s#%u: %s", pr_netaddr_get_ipstr(bind_addr), ntohs(pr_netaddr_get_port(bind_addr)), strerror(xerrno)); if (!frontend_data) { proxy_inet_close(session.pool, conn); } pr_inet_close(session.pool, conn); errno = xerrno; return NULL; } if (frontend_data) { pr_pool_tag(conn->pool, "proxy frontend data listen conn pool"); conn->instrm = pr_netio_open(session.pool, PR_NETIO_STRM_DATA, conn->listen_fd, PR_NETIO_IO_RD); conn->outstrm = pr_netio_open(session.pool, PR_NETIO_STRM_DATA, conn->listen_fd, PR_NETIO_IO_WR); } else { pr_pool_tag(conn->pool, "proxy backend data listen conn pool"); conn->instrm = proxy_netio_open(session.pool, PR_NETIO_STRM_DATA, conn->listen_fd, PR_NETIO_IO_RD); conn->outstrm = proxy_netio_open(session.pool, PR_NETIO_STRM_DATA, conn->listen_fd, PR_NETIO_IO_WR); } return conn; } proftpd-mod_proxy-0.9.5/lib/proxy/ftp/ctrl.c000066400000000000000000000400031475737016700210670ustar00rootroot00000000000000/* * ProFTPD - mod_proxy FTP control conn routines * Copyright (c) 2012-2023 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "proxy/netio.h" #include "proxy/ftp/ctrl.h" #include "proxy/tls.h" static const char *trace_channel = "proxy.ftp.ctrl"; static char *ftp_telnet_gets(char *buf, size_t buflen, pr_netio_stream_t *nstrm, conn_t *conn) { char *buf_ptr = buf; unsigned char cp; int nread, saw_newline = FALSE; pr_buffer_t *pbuf = NULL; if (buflen == 0 || nstrm == NULL || conn == NULL) { errno = EINVAL; return NULL; } buflen--; if (nstrm->strm_buf != NULL) { pbuf = nstrm->strm_buf; } else { pbuf = pr_netio_buffer_alloc(nstrm); } while (buflen > 0) { /* Is the buffer empty? */ if (pbuf->current == NULL || pbuf->remaining == pbuf->buflen) { nread = proxy_netio_read(nstrm, pbuf->buf, (buflen < pbuf->buflen ? buflen : pbuf->buflen), 4); if (nread <= 0) { if (buf_ptr != buf) { *buf_ptr = '\0'; return buf; } if (nread == 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "read EOF from %s", conn->remote_name); errno = EPERM; } return NULL; } pbuf->remaining = pbuf->buflen - nread; pbuf->current = pbuf->buf; pr_event_generate("mod_proxy.ctrl-read", pbuf); } nread = pbuf->buflen - pbuf->remaining; /* Expensive copying of bytes while we look for the trailing LF. */ while (buflen > 0 && nread > 0 && *pbuf->current != '\n' && nread--) { pr_signals_handle(); cp = *pbuf->current++; pbuf->remaining++; *buf_ptr++ = cp; buflen--; } if (buflen > 0 && nread > 0 && *pbuf->current == '\n') { buflen--; nread--; *buf_ptr++ = *pbuf->current++; pbuf->remaining++; saw_newline = TRUE; break; } if (nread == 0) { pbuf->current = NULL; } } if (saw_newline == FALSE) { /* If we haven't seen a newline, then assume the server is deliberately * sending a too-long response, trying to exploit buffer sizes and make * the proxy make some possibly bad assumptions. */ errno = E2BIG; return NULL; } *buf_ptr = '\0'; return buf; } pr_response_t *proxy_ftp_ctrl_recv_resp(pool *p, conn_t *ctrl_conn, unsigned int *nlines, int flags) { char buf[PR_TUNABLE_BUFFER_SIZE]; pr_response_t *resp = NULL; int multi_line = FALSE; unsigned int count = 0; if (p == NULL || ctrl_conn == NULL || nlines == NULL) { errno = EINVAL; return NULL; } while (TRUE) { char c, *ptr; int resp_code; size_t buflen; pr_signals_handle(); memset(buf, '\0', sizeof(buf)); if (ftp_telnet_gets(buf, sizeof(buf)-1, ctrl_conn->instrm, ctrl_conn) == NULL) { int xerrno = errno; pr_trace_msg(trace_channel, 9, "error reading telnet data: %s", strerror(xerrno)); errno = xerrno; return NULL; } buflen = strlen(buf); /* TODO: What if the given buffer does not end in a CR/LF? What if the * backend server is spewing response lines longer than our buffer? */ /* Remove any trailing CRs, LFs. */ while (buflen > 0 && (buf[buflen-1] == '\r' || buf[buflen-1] == '\n')) { pr_signals_handle(); buf[buflen-1] = '\0'; buflen--; } if (buflen == 0 && (flags & PROXY_FTP_CTRL_FL_IGNORE_BLANK_RESP)) { pr_trace_msg(trace_channel, 19, "%s", "skipping blank response line from backend server"); continue; } /* If we are the first line of the response, the first three characters * MUST be numeric, followed by a hypen. Anything else is nonconformant * with RFC 959. * * If we are NOT the first line of the response, then we are probably * handling a multi-line response. If the first character is a space, then * this is a continuation line. Otherwise, the first three characters * MUST be numeric, AND MUST match the numeric code from the first line. * This indicates the last line in the multi-line response -- and the * character after the numerics MUST be a space. * * Unfortunately, some FTP servers (IIS, for instance) will use multi-line * responses whose continuation lines do NOT start with the mandated * space (as for a multi-line STAT response on a file, for example). Sigh. */ if (resp == NULL) { /* First line of a possibly multi-line response (or just the only * line). */ if (buflen < 4) { pr_trace_msg(trace_channel, 12, "read %lu characters of response, needed at least %d", (unsigned long) buflen, 4); errno = EINVAL; return NULL; } if (!PR_ISDIGIT((int) buf[0]) || !PR_ISDIGIT((int) buf[1]) || !PR_ISDIGIT((int) buf[2])) { pr_trace_msg(trace_channel, 1, "non-numeric characters in start of response data: '%c%c%c'", buf[0], buf[1], buf[2]); errno = EINVAL; return NULL; } /* If this is a space, then we have a single line response. If it * is a hyphen, then this is the first line of a multi-line response. */ if (buf[3] != ' ' && buf[3] != '-') { pr_trace_msg(trace_channel, 1, "unexpected character '%c' following numeric response code", buf[3]); errno = EINVAL; return NULL; } if (buf[3] == '-') { multi_line = TRUE; } count++; resp = (pr_response_t *) pcalloc(p, sizeof(pr_response_t)); } else { if (buflen >= 1) { /* TODO: We should have a limit for how large of a buffered response * we will tolerate. Consider a malicious/buggy backend server whose * multi-line response is in the GB? * * One way to avoid the buffering would be to relay each individual * response line, as we read them, to the frontend client. But if * we do so, then we will not be properly acting as an FTP protocol * sanitizer, either. Hrm. */ if (buf[0] == ' ') { /* Continuation line; append it the existing response. */ if (buflen > 1) { resp->msg = pstrcat(p, resp->msg, "\r\n", buf, NULL); } count++; continue; } else { /* Possible ending line of multi-line response. */ if (buflen < 4) { errno = EINVAL; return NULL; } if (!PR_ISDIGIT((int) buf[0]) || !PR_ISDIGIT((int) buf[1]) || !PR_ISDIGIT((int) buf[2])) { pr_trace_msg(trace_channel, 1, "non-numeric characters in end of response data: '%c%c%c'", buf[0], buf[1], buf[2]); /* NOTE: We could/should be strict here, and require conformant * responses only. For now, though, we'll proxy through the * backend's response to the frontend client, to let it decide * how it wants to handle this response data. */ resp->msg = pstrcat(p, resp->msg, "\r\n", buf, NULL); count++; continue; } if (buf[3] != ' ') { /* NOTE: We could/should be strict here, and require conformant * responses only. For now, though, we'll proxy through the * backend's response to the frontend client, to let it decide * how it wants to handle this response data. */ resp->msg = pstrcat(p, resp->msg, "\r\n", buf, NULL); count++; continue; } count++; } } } ptr = &(buf[3]); c = *ptr; *ptr = '\0'; resp_code = atoi(buf); if (resp_code < 100 || resp_code >= 700) { /* Outside of the expected/defined FTP response code range. */ pr_trace_msg(trace_channel, 1, "invalid FTP response code %d received", resp_code); errno = EINVAL; return NULL; } if (resp->num == NULL) { resp->num = pstrdup(p, buf); } else { /* Make sure the last line of the multi-line response uses the same * response code. */ if (strncmp(resp->num, buf, 3) != 0) { pr_trace_msg(trace_channel, 1, "invalid multi-line FTP response: mismatched starting response " "code (%s) and ending response code (%s)", resp->num, buf); errno = EINVAL; return NULL; } } if (resp->msg == NULL) { if (buflen > 4) { if (multi_line == TRUE) { *ptr = c; resp->msg = pstrdup(p, ptr); *ptr = '\0'; } else { resp->msg = pstrdup(p, ptr + 1); } } else { resp->msg = ""; } /* If the character after the response code was a space, then this is * a single line response; we can be done now. */ if (c == ' ') { break; } } else { if (buflen > 4) { if (multi_line == TRUE) { *ptr = c; /* This the last line of a multi-line response, which means we * need the ENTIRE line, including the response code. */ resp->msg = pstrcat(p, resp->msg, "\r\n", buf, NULL); } else { resp->msg = pstrcat(p, resp->msg, "\r\n", ptr + 1, NULL); } } break; } } *nlines = count; pr_trace_msg(trace_channel, 9, "received '%s%s%s' response from backend to frontend", resp->num, multi_line ? "-" : " ", resp->msg); return resp; } #ifndef TELNET_DM # define TELNET_DM 242 #endif /* TELNET_DM */ #ifndef TELNET_IAC # define TELNET_IAC 255 #endif /* TELNET_IAC */ #ifndef TELNET_IP # define TELNET_IP 244 #endif /* TELNET_IP */ int proxy_ftp_ctrl_send_abort(pool *p, conn_t *ctrl_conn, cmd_rec *cmd) { int fd, res, use_tls, xerrno; unsigned char buf[7]; if (p == NULL || ctrl_conn == NULL || cmd == NULL) { errno = EINVAL; return -1; } /* If we are proxying the ABOR command, preface it with the Telnet "Sync" * mechanism, using OOB data. If the receiving server supports this, it can * generate a signal to interrupt any IO occurring on the backend server * (such as when sendfile(2) is used). * * Note that such Telnet codes can only be used if we are NOT using TLS * on the backend control connection. */ use_tls = proxy_tls_using_tls(); if (use_tls != PROXY_TLS_ENGINE_OFF) { return proxy_ftp_ctrl_send_cmd(p, ctrl_conn, cmd); } fd = PR_NETIO_FD(ctrl_conn->outstrm); buf[0] = TELNET_IAC; buf[1] = TELNET_IP; buf[2] = TELNET_IAC; pr_trace_msg(trace_channel, 9, "sending Telnet abort code out-of-band to backend"); res = send(fd, &buf, 3, MSG_OOB); xerrno = errno; if (res < 0) { pr_trace_msg(trace_channel, 1, "error sending Telnet abort code out-of-band to backend: %s", strerror(xerrno)); errno = xerrno; return -1; } buf[0] = TELNET_DM; buf[1] = 'A'; buf[2] = 'B'; buf[3] = 'O'; buf[4] = 'R'; buf[5] = '\r'; buf[6] = '\n'; pr_trace_msg(trace_channel, 9, "proxied %s command from frontend to backend", (char *) cmd->argv[0]); res = send(fd, &buf, 7, 0); xerrno = errno; if (res < 0) { pr_trace_msg(trace_channel, 1, "error sending Telnet DM code to backend: %s", strerror(xerrno)); errno = xerrno; return -1; } return 0; } int proxy_ftp_ctrl_send_cmd(pool *p, conn_t *ctrl_conn, cmd_rec *cmd) { int res; if (p == NULL || ctrl_conn == NULL || cmd == NULL) { errno = EINVAL; return -1; } if (cmd->argc > 1) { const char *display_str; size_t display_len = 0; display_str = pr_cmd_get_displayable_str(cmd, &display_len); pr_trace_msg(trace_channel, 9, "proxied command '%s' from frontend to backend", display_str); res = proxy_netio_printf(ctrl_conn->outstrm, "%s %s\r\n", (char *) cmd->argv[0], cmd->arg); } else { pr_trace_msg(trace_channel, 9, "proxied %s command from frontend to backend", (char *) cmd->argv[0]); res = proxy_netio_printf(ctrl_conn->outstrm, "%s\r\n", (char *) cmd->argv[0]); } return res; } int proxy_ftp_ctrl_send_resp(pool *p, conn_t *ctrl_conn, pr_response_t *resp, unsigned int resp_nlines) { pool *curr_pool; (void) ctrl_conn; if (p == NULL || resp == NULL) { errno = EINVAL; return -1; } pr_trace_msg(trace_channel, 9, "backend->frontend response: %s%s%s", resp->num, resp_nlines <= 1 ? " " : "", resp->msg); curr_pool = pr_response_get_pool(); if (curr_pool == NULL) { pr_response_set_pool(p); } if (resp_nlines > 1) { pr_response_send_raw("%s-%s", resp->num, resp->msg); } else { pr_response_send(resp->num, "%s", resp->msg); } pr_response_set_pool(curr_pool); return 0; } int proxy_ftp_ctrl_handle_async(pool *p, conn_t *backend_conn, conn_t *frontend_conn, int flags) { if (p == NULL || backend_conn == NULL || backend_conn->instrm == NULL || frontend_conn == NULL) { errno = EINVAL; return -1; } if (!(proxy_sess_state & PROXY_SESS_STATE_CONNECTED)) { /* Nothing to do if we're not yet connected to the backend server. */ return 0; } while (TRUE) { fd_set rfds; struct timeval tv; int ctrlfd, res, xerrno = 0; /* By using a timeout of zero, we effect a poll on the fd. */ tv.tv_sec = 0; tv.tv_usec = 0; pr_signals_handle(); FD_ZERO(&rfds); ctrlfd = PR_NETIO_FD(backend_conn->instrm); FD_SET(ctrlfd, &rfds); res = select(ctrlfd + 1, &rfds, NULL, NULL, &tv); if (res < 0) { xerrno = errno; if (xerrno == EINTR) { pr_signals_handle(); continue; } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error calling select(2) on backend control connection (fd %d): %s", ctrlfd, strerror(xerrno)); return 0; } if (res == 0) { /* Nothing there. */ break; } pr_trace_msg(trace_channel, 19, "select(2) reported %d for backend %s (fd %d)", res, backend_conn->remote_name, ctrlfd); if (FD_ISSET(ctrlfd, &rfds)) { unsigned int resp_nlines = 0; pr_response_t *resp; pr_timer_reset(PR_TIMER_IDLE, ANY_MODULE); pr_trace_msg(trace_channel, 9, "reading async response from backend %s", backend_conn->remote_name); resp = proxy_ftp_ctrl_recv_resp(p, backend_conn, &resp_nlines, flags); if (resp == NULL) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error receiving response from backend control connection: %s", strerror(xerrno)); errno = xerrno; return -1; } res = proxy_ftp_ctrl_send_resp(p, frontend_conn, resp, resp_nlines); if (res < 0) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error sending response to frontend control connection: %s", strerror(xerrno)); errno = xerrno; return -1; } } break; } return 0; } proftpd-mod_proxy-0.9.5/lib/proxy/ftp/data.c000066400000000000000000000111701475737016700210370ustar00rootroot00000000000000/* * ProFTPD - mod_proxy FTP data conn routines * Copyright (c) 2012-2020 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "proxy/netio.h" #include "proxy/ftp/data.h" static const char *trace_channel = "proxy.ftp.data"; pr_buffer_t *proxy_ftp_data_recv(pool *p, conn_t *data_conn, int frontend_data) { int nread; pr_buffer_t *pbuf = NULL; if (p == NULL || data_conn == NULL || data_conn->instrm == NULL) { errno = EINVAL; return NULL; } if (data_conn->instrm->strm_buf != NULL) { pbuf = data_conn->instrm->strm_buf; } else { pbuf = pr_netio_buffer_alloc(data_conn->instrm); } pbuf->current = pbuf->buf; pbuf->remaining = pbuf->buflen; while (TRUE) { size_t avail_len; if (frontend_data) { nread = pr_netio_read(data_conn->instrm, pbuf->current, pbuf->remaining, 1); } else { nread = proxy_netio_read(data_conn->instrm, pbuf->current, pbuf->remaining, 1); } if (nread < 0) { return NULL; } if (nread == 0) { /* We might have had data left over in the buffer from a previous * iteration of the loop, thus we return it as is. */ return pbuf; } pr_timer_reset(PR_TIMER_NOXFER, ANY_MODULE); pr_timer_reset(PR_TIMER_STALLED, ANY_MODULE); pr_timer_reset(PR_TIMER_IDLE, ANY_MODULE); pr_trace_msg(trace_channel, 15, "received %d bytes of data", nread); pbuf->current += nread; pbuf->remaining -= nread; pr_event_generate("mod_proxy.data-read", pbuf); /* How much data is available in the buffer? It is possible that * event listeners consumed that data entirely. If there is no available * data left, we need to read more. */ avail_len = pbuf->current - pbuf->buf; if (avail_len > 0) { break; } } return pbuf; } int proxy_ftp_data_send(pool *p, conn_t *data_conn, pr_buffer_t *pbuf, int frontend_data) { int nwrote; char *buf; size_t buflen; if (p == NULL || data_conn == NULL || data_conn->outstrm == NULL || pbuf == NULL) { errno = EINVAL; return -1; } pr_event_generate("mod_proxy.data-write", pbuf); /* Currently, we make the conn_t nonblocking (via pr_inet_set_nonblocking), * BUT that does NOT set the nonblocking flag on the contained stream. * Thus this write is actually a BLOCKING write -- which means that we will * not need to worry about short writes here. * * In the future, we may want to make the streams nonblocking, but that * makes mod_proxy a little more sensitive to the slow producer/consumer * problem. */ buf = pbuf->buf; buflen = pbuf->current - pbuf->buf; pr_trace_msg(trace_channel, 25, "writing %lu bytes of data to %s", (unsigned long) buflen, frontend_data ? "frontend client" : "backend server"); if (frontend_data) { nwrote = pr_netio_write(data_conn->outstrm, buf, buflen); } else { nwrote = proxy_netio_write(data_conn->outstrm, buf, buflen); } while (nwrote < 0) { int xerrno = errno; if (xerrno == EAGAIN) { /* Since our socket is in non-blocking mode, write(2) can return * EAGAIN if there is not enough from for our data yet. Handle * this by delaying temporarily, then trying again. */ errno = EINTR; pr_signals_handle(); if (frontend_data) { nwrote = pr_netio_write(data_conn->outstrm, buf, buflen); } else { nwrote = proxy_netio_write(data_conn->outstrm, buf, buflen); } continue; } errno = xerrno; return -1; } pr_timer_reset(PR_TIMER_NOXFER, ANY_MODULE); pr_timer_reset(PR_TIMER_STALLED, ANY_MODULE); pr_timer_reset(PR_TIMER_IDLE, ANY_MODULE); return nwrote; } proftpd-mod_proxy-0.9.5/lib/proxy/ftp/dirlist.c000066400000000000000000001042121475737016700216000ustar00rootroot00000000000000/* * ProFTPD - mod_proxy FTP dirlist routines * Copyright (c) 2020-2025 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "proxy/str.h" #include "proxy/ftp/dirlist.h" #include "proxy/ftp/facts.h" static unsigned long facts_opts = 0UL; /* Tracks all of the state/context for parsing a single directory listing. */ struct dirlist_ctx { pool *pool; unsigned long opts; /* Style/format of directory listing: Unix, Windows/DOS, etc. */ int list_style; /* Skip the "total NNN" leadling line in some listings? */ unsigned char skip_total; /* Accumulated unprocessed input data. In theory, this should never be * more than a single line of text, minus the terminating LF. */ char *input_ptr, *input_text; size_t input_textsz, input_textlen; /* Accumulated output data. */ char *output_ptr, *output_text; size_t output_textsz, output_textlen; }; #define DIRLIST_LIST_STYLE_UNKNOWN 0 #define DIRLIST_LIST_STYLE_UNIX 1 #define DIRLIST_LIST_STYLE_WINDOWS 2 static const char *trace_channel = "proxy.ftp.dirlist"; int proxy_ftp_dirlist_init(pool *p, struct proxy_session *proxy_sess) { struct dirlist_ctx *ctx; pool *ctx_pool; if (p == NULL || proxy_sess == NULL) { errno = EINVAL; return -1; } facts_opts = proxy_ftp_facts_get_opts(); ctx_pool = make_sub_pool(p); pr_pool_tag(ctx_pool, "Proxy Dirlist Context Pool"); ctx = pcalloc(ctx_pool, sizeof(struct dirlist_ctx)); ctx->pool = ctx_pool; ctx->opts = proxy_sess->dirlist_opts; ctx->list_style = DIRLIST_LIST_STYLE_UNKNOWN; ctx->skip_total = TRUE; /* This is the maximum size of one line, per mod_ls. Be aware, however, that * we may be talking to non-ProFTPD servers, whose behaviors will be different. */ ctx->input_textsz = (PR_TUNABLE_PATH_MAX * 2) + 256; ctx->input_ptr = ctx->input_text = palloc(ctx_pool, ctx->input_textsz); ctx->output_textsz = (pr_config_get_server_xfer_bufsz(PR_NETIO_IO_WR) * 64); ctx->output_ptr = ctx->output_text = palloc(ctx_pool, ctx->output_textsz); proxy_sess->dirlist_ctx = (void *) ctx; return 0; } int proxy_ftp_dirlist_finish(struct proxy_session *proxy_sess) { if (proxy_sess == NULL) { errno = EINVAL; return -1; } facts_opts = 0UL; if (proxy_sess->dirlist_ctx != NULL) { struct dirlist_ctx *ctx; ctx = proxy_sess->dirlist_ctx; destroy_pool(ctx->pool); proxy_sess->dirlist_ctx = NULL; } return 0; } struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_dos(pool *p, const char *text, size_t textlen, unsigned long opts) { struct proxy_dirlist_fileinfo *pdf; char *buf, *ptr; size_t buflen, windows_ts_fmtlen = 17; const char *windows_ts_fmt = "%m-%d-%y %I:%M%p"; if (p == NULL || text == NULL || textlen == 0) { errno = EINVAL; return NULL; } pr_trace_msg(trace_channel, 19, "parsing Windows text: '%.*s'", (int) textlen, text); /* 24 is the minimum length of a well-formatted Windows directory listing * line. */ if (textlen < 24) { pr_trace_msg(trace_channel, 3, "error parsing Windows text (too short, need at least 24 bytes): '%.*s'", (int) textlen, text); errno = EINVAL; return NULL; } pdf = pcalloc(p, sizeof(struct proxy_dirlist_fileinfo)); ptr = (char *) text; buflen = 8; buf = pstrndup(p, ptr, buflen); if (strpbrk(buf, "0123456789-") == NULL) { pr_trace_msg(trace_channel, 3, "unexpected Windows date format: '%.*s'", (int) buflen, buf); errno = EINVAL; return NULL; } ptr += buflen; if (strncmp(ptr, " ", 2) != 0) { pr_trace_msg(trace_channel, 3, "malformed Windows text (expected 2 spaces after date): '%.*s'", (int) textlen, text); errno = EINVAL; return NULL; } ptr += 2; buflen = 7; buf = pstrndup(p, ptr, buflen); if (strpbrk(buf, "AMP0123456789:") == NULL) { pr_trace_msg(trace_channel, 3, "unexpected Windows time format: '%.*s'", (int) buflen, buf); errno = EINVAL; return NULL; } /* Some servers might mistakenly omit the AM/PM markers; try to handle these * cases gracefully. */ if (strpbrk(buf, "AMP") == NULL) { pr_trace_msg(trace_channel, 3, "Windows time format lacks AM/PM marker, adjusting expectations"); windows_ts_fmt = "%m-%d-%y %I:%M"; windows_ts_fmtlen = 15; } pdf->tm = pcalloc(p, sizeof(struct tm)); buflen = windows_ts_fmtlen; buf = pstrndup(p, text, buflen); pr_trace_msg(trace_channel, 19, "parsing Windows-style timestamp: '%.*s'", (int) buflen, buf); if (strptime(buf, windows_ts_fmt, pdf->tm) == NULL) { pr_trace_msg(trace_channel, 3, "unexpected Windows timestamp format: '%.*s'", (int) buflen, buf); errno = EINVAL; return NULL; } ptr = (char *) text + buflen; /* We now expect at least 7 spaces. */ if (strncmp(ptr, " ", 7) != 0) { pr_trace_msg(trace_channel, 3, "malformed Windows text (expected 7 spaces after timestamp): '%.*s'", (int) textlen, text); errno = EINVAL; return NULL; } ptr += 7; pdf->st = pcalloc(p, sizeof(struct stat)); if (strncmp(ptr, "", 5) == 0) { pdf->st->st_mode |= S_IFDIR; pdf->type = pstrdup(p, "dir"); /* For a directory, we expect the next 10 characters to be spaces. */ ptr += 5; if (strncmp(ptr, " ", 10) != 0) { pr_trace_msg(trace_channel, 3, "malformed Windows text (expected 10 spaces after dir): '%.*s'", (int) textlen, text); errno = EINVAL; return NULL; } ptr += 10; } else if (strncmp(ptr, " ", 5) == 0) { char *size_ptr; off_t filesz; pdf->st->st_mode |= S_IFREG; pdf->type = pstrdup(p, "file"); /* For a file, we expect to see the file size within 9 characters or * less. */ ptr += 5; buflen = 9; buf = pstrndup(p, ptr, buflen); if (strpbrk(buf, "0123456789 ") == NULL) { pr_trace_msg(trace_channel, 3, "malformed Windows text (expected filesize with '%.*s'): '%.*s'", (int) buflen, buf, (int) textlen, text); errno = EINVAL; return NULL; } size_ptr = strpbrk(buf, "0123456789"); if (size_ptr == NULL) { pr_trace_msg(trace_channel, 3, "malformed Windows text (expected filesize not found): '%.*s'", (int) textlen, text); errno = EINVAL; return NULL; } pr_trace_msg(trace_channel, 19, "parsing Windows-style filesize from '%s'", size_ptr); if (pr_str_get_nbytes(size_ptr, NULL, &filesz) < 0) { pr_trace_msg(trace_channel, 3, "malformed Windows text (unable to parse filesize: %s): '%.*s'", strerror(errno), (int) textlen, text); errno = EINVAL; return NULL; } pdf->st->st_size = filesz; ptr += 9; if (strncmp(ptr, " ", 1) != 0) { pr_trace_msg(trace_channel, 3, "malformed Windows text (missing space after filesize): '%.*s'", (int) textlen, text); errno = EINVAL; return NULL; } ptr += 1; } else { pr_trace_msg(trace_channel, 3, "malformed Windows text (unexpected spaces after timestamp): '%.*s'", (int) textlen, text); errno = EINVAL; return NULL; } pdf->path = pstrdup(p, ptr); return pdf; } static mode_t get_unix_mode(const char *text) { mode_t perms = 0; /* User permissions */ switch (text[0]) { /* S_IRUSR only */ case 'r': perms |= S_IRUSR; break; case '-': break; } switch (text[1]) { /* S_IWUSR only */ case 'w': perms |= S_IWUSR; break; case '-': break; } switch (text[2]) { case 'S': #if defined(S_ISUID) perms |= S_ISUID; #endif /* S_ISUID */ break; /* S_ISUID + S_IXUSR */ case 's': #if defined(S_ISUID) perms |= S_ISUID; perms |= S_IXUSR; #endif /* S_ISUID */ break; /* S_IXUSR only */ case 'x': perms |= S_IXUSR; break; case '-': break; } /* Group permissions */ switch (text[3]) { case 'r': perms |= S_IRGRP; break; case '-': break; } switch (text[4]) { case 'w': perms |= S_IWGRP; break; case '-': break; } switch (text[5]) { case 'S': #if defined(S_ISGID) perms |= S_ISGID; #endif /* S_ISGID */ break; /* S_ISGID + S_IXGRP */ case 's': #if defined(S_ISGID) perms |= S_ISGID; perms |= S_IXGRP; #endif /* S_ISGID */ break; /* S_IXGRP only */ case 'x': perms |= S_IXGRP; break; case '-': break; } /* World/other permissions */ switch (text[6]) { case 'r': perms |= S_IROTH; break; case '-': break; } switch (text[7]) { case 'w': perms |= S_IWOTH; break; case '-': break; } switch (text[8]) { /* S_ISVTX only */ case 'T': #if defined(S_ISVTX) perms |= S_ISVTX; #endif /* S_ISVTX */ break; /* S_ISVTX + S_IXOTH */ case 't': #if defined(S_ISVTX) perms |= S_ISVTX; perms |= S_IXOTH; #endif /* S_ISVTX */ break; /* S_IXOTH only */ case 'x': perms |= S_IXOTH; break; case '-': break; } return perms; } /* See RFC 3659, Section 7.5.5: "The perm Fact" */ static char *get_perm_fact(pool *p, mode_t mode) { char *perm = ""; if (!S_ISDIR(mode)) { if (mode & (S_IWUSR|S_IWGRP|S_IWOTH)) { perm = pstrcat(p, perm, "a", NULL); } perm = pstrcat(p, perm, "d", NULL); if (mode & (S_IWUSR|S_IWGRP|S_IWOTH)) { perm = pstrcat(p, perm, "f", NULL); } if (mode & (S_IRUSR|S_IRGRP|S_IROTH)) { perm = pstrcat(p, perm, "r", NULL); } if (mode & (S_IWUSR|S_IWGRP|S_IWOTH)) { perm = pstrcat(p, perm, "w", NULL); } } else if (S_ISDIR(mode)) { if (mode & (S_IWUSR|S_IWGRP|S_IWOTH)) { perm = pstrcat(p, perm, "c", NULL); } perm = pstrcat(p, perm, "d", NULL); if (mode & (S_IXUSR|S_IXGRP|S_IXOTH)) { perm = pstrcat(p, perm, "e", NULL); } if (mode & (S_IWUSR|S_IWGRP|S_IWOTH)) { perm = pstrcat(p, perm, "f", NULL); } if (mode & (S_IXUSR|S_IXGRP|S_IXOTH)) { perm = pstrcat(p, perm, "l", NULL); } if (mode & (S_IWUSR|S_IWGRP|S_IWOTH)) { perm = pstrcat(p, perm, "mp", NULL); } } return perm; } static int get_unix_nlink(pool *p, char *buf, size_t buflen, struct stat *st) { char *nlinks_ptr; if (strpbrk(buf, "0123456789 ") == NULL) { errno = EINVAL; return -1; } nlinks_ptr = strpbrk(buf, "0123456789"); if (nlinks_ptr == NULL) { errno = EINVAL; return -1; } st->st_nlink = atoi(nlinks_ptr); return 0; } static int get_unix_user(pool *p, char *buf, size_t buflen, struct proxy_dirlist_fileinfo *pdf) { int res; char user[32]; uid_t uid; memset(user, '\0', sizeof(user)); while (PR_ISSPACE(*buf) && *buf) { buf += 1; buflen -= 1; } res = sscanf(buf, "%s", user); if (res != 1) { pr_trace_msg(trace_channel, 3, "malformed Unix text (unable to parse user): '%.*s'", (int) buflen, buf); errno = EINVAL; return -1; } if (pr_str2uid(user, &uid) == 0) { pdf->st->st_uid = uid; pdf->have_uid = TRUE; } else { pdf->user = pstrdup(p, user); } return 0; } static int get_unix_group(pool *p, char *buf, size_t buflen, struct proxy_dirlist_fileinfo *pdf) { int res; char group[32]; gid_t gid; memset(group, '\0', sizeof(group)); while (PR_ISSPACE(*buf) && *buf) { buf += 1; buflen -= 1; } res = sscanf(buf, "%s", group); if (res != 1) { pr_trace_msg(trace_channel, 3, "malformed Unix text (unable to parse group): '%.*s'", (int) buflen, buf); errno = EINVAL; return -1; } if (pr_str2gid(group, &gid) == 0) { pdf->st->st_gid = gid; pdf->have_gid = TRUE; } else { pdf->group = pstrdup(p, group); } return 0; } static int get_unix_filesize(pool *p, char *buf, size_t buflen, struct stat *st) { off_t filesz; if (pr_str_get_nbytes(buf, NULL, &filesz) < 0) { pr_trace_msg(trace_channel, 3, "malformed Unix text (unable to parse filesize: %s): '%.*s'", strerror(errno), (int) buflen, buf); errno = EINVAL; return -1; } st->st_size = filesz; return 0; } static const char *months[13] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL }; /* Either: * * "Jul 21 04:53" * "Apr 9 2015" */ static int get_unix_timestamp(pool *p, char *buf, size_t buflen, struct tm *tm, int current_year) { register unsigned int i; int found_month = FALSE, mday, year, hour, min, res; for (i = 0; months[i]; i++) { if (strncmp(buf, months[i], 3) == 0) { tm->tm_mon = (int) i; found_month = TRUE; break; } } if (found_month == FALSE) { pr_trace_msg(trace_channel, 3, "malformed Unix text (unable to month in '%.*s')", (int) buflen, buf); errno = EINVAL; return -1; } buf += 3; buflen -= 3; if (strncmp(buf, " ", 1) != 0) { pr_trace_msg(trace_channel, 3, "malformed Unix text (expected space after month): '%.*s'", (int) buflen, buf); errno = EINVAL; return -1; } buf += 1; buflen -= 1; res = sscanf(buf, "%2d", &mday); if (res != 1) { pr_trace_msg(trace_channel, 3, "malformed Unix text (expected mday after month): '%.*s'", (int) buflen, buf); errno = EINVAL; return -1; } tm->tm_mday = mday; buf += 2; buflen -= 2; res = sscanf(buf, "%02d:%02d", &hour, &min); if (res == 2) { tm->tm_year = current_year; tm->tm_hour = hour; tm->tm_min = min; } else { /* We have text of 5 characters, but years are only 4 characters. * Advance past the space character. */ buf += 1; buflen -= 1; res = sscanf(buf, "%4d", &year); if (res == 1) { tm->tm_year = year; if (tm->tm_year > 1900) { tm->tm_year -= 1900; } } else { pr_trace_msg(trace_channel, 3, "malformed Unix text (expected year/hour/min after mday): '%.*s'", (int) buflen, buf); errno = EINVAL; return -1; } } return 0; } struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_unix(pool *p, const char *text, size_t textlen, struct tm *tm, unsigned long opts) { struct proxy_dirlist_fileinfo *pdf; char *buf, *perm, *ptr, *ptr2; size_t buflen; mode_t mode; if (p == NULL || text == NULL || textlen == 0 || tm == NULL) { errno = EINVAL; return NULL; } pr_trace_msg(trace_channel, 19, "parsing Unix text: '%.*s'", (int) textlen, text); /* 43 is the minimum length of a well-formatted Unix directory listing * line. */ if (textlen < 43) { pr_trace_msg(trace_channel, 3, "error parsing Unix text (too short, need at least 43 bytes): '%.*s'", (int) textlen, text); errno = EINVAL; return NULL; } pdf = pcalloc(p, sizeof(struct proxy_dirlist_fileinfo)); pdf->st = pcalloc(p, sizeof(struct stat)); switch (text[0]) { case '-': pdf->st->st_mode |= S_IFREG; pdf->type = pstrdup(p, "file"); break; case 'd': pdf->st->st_mode |= S_IFDIR; pdf->type = pstrdup(p, "dir"); break; case 'l': #if defined(S_IFLNK) pdf->st->st_mode |= S_IFLNK; #endif /* S_IFLNK */ /* If the USE_SLINK option is set, then pdf->type will be filled in * later, once we know the symlink target. */ if (!(opts & PROXY_FTP_DIRLIST_OPT_USE_SLINK)) { pdf->type = pstrdup(p, "OS.unix=symlink"); } break; case 'p': #if defined(S_IFIFO) pdf->st->st_mode |= S_IFIFO; #else pdf->st->st_mode |= S_IFREG; #endif /* S_IFIFO */ pdf->type = pstrdup(p, "OS.unix=pipe"); break; case 's': #if defined(S_IFSOCK) pdf->st->st_mode |= S_IFSOCK; #else pdf->st->st_mode |= S_IFREG; #endif /* S_IFSOCK */ pdf->type = pstrdup(p, "OS.unix=socket"); break; case 'c': #if defined(S_IFCHR) pdf->st->st_mode |= S_IFCHR; #else pdf->st->st_mode |= S_IFREG; #endif /* S_IFCHR */ pdf->type = pstrdup(p, "OS.unix=chardev"); break; case 'b': #if defined(S_IFBLK) pdf->st->st_mode |= S_IFBLK; #else pdf->st->st_mode |= S_IFREG; #endif /* S_IFBLK */ pdf->type = pstrdup(p, "OS.unix=blockdev"); break; case 'D': #if defined(S_IFIFO) pdf->st->st_mode |= S_IFIFO; #else pdf->st->st_mode |= S_IFREG; #endif /* S_IFIFO */ pdf->type = pstrdup(p, "OS.solaris=door"); break; default: pr_trace_msg(trace_channel, 3, "unknown Unix file type: '%.*s'", 1, text); errno = EINVAL; return NULL; } ptr = (char *) text + 1; buflen = 9; buf = pstrndup(p, ptr, buflen); if (strpbrk(buf, "rwx-tTsS") == NULL) { pr_trace_msg(trace_channel, 3, "malformed Unix text (expected permissions): '%.*s'", (int) buflen, buf); errno = EINVAL; return NULL; } mode = get_unix_mode(buf); pdf->st->st_mode |= mode; perm = get_perm_fact(p, pdf->st->st_mode); pdf->perm = perm; ptr += buflen; if (strncmp(ptr, " ", 1) != 0) { pr_trace_msg(trace_channel, 3, "malformed Unix text (expected space after permissions): '%.*s'", (int) textlen, text); errno = EINVAL; return NULL; } ptr += 1; if (*ptr == ' ') { buflen = 3; } else { ptr2 = strchr(ptr, ' '); if (ptr2 == NULL) { pr_trace_msg(trace_channel, 3, "malformed Unix text (expected space after nlink): '%.*s'", (int) textlen, text); errno = EINVAL; return NULL; } buflen = ptr2 - ptr; } buf = pstrndup(p, ptr, buflen); if (get_unix_nlink(p, buf, buflen, pdf->st) < 0) { int xerrno = errno; pr_trace_msg(trace_channel, 3, "malformed Unix text (expected nlink with '%.*s'): '%.*s'", (int) buflen, buf, (int) textlen, text); errno = xerrno; return NULL; } ptr += buflen; if (strncmp(ptr, " ", 1) != 0) { pr_trace_msg(trace_channel, 3, "malformed Unix text (expected space after nlink): '%.*s'", (int) textlen, text); errno = EINVAL; return NULL; } ptr += 1; buflen = 8; buf = pstrndup(p, ptr, buflen); if (get_unix_user(p, buf, buflen, pdf) < 0) { int xerrno = errno; pr_trace_msg(trace_channel, 3, "malformed Unix text (expected user with '%.*s'): '%.*s'", (int) buflen, buf, (int) textlen, text); errno = xerrno; return NULL; } ptr += buflen; if (strncmp(ptr, " ", 1) != 0) { pr_trace_msg(trace_channel, 3, "malformed Unix text (expected space after user): '%.*s'", (int) textlen, text); errno = EINVAL; return NULL; } ptr += 1; buflen = 8; buf = pstrndup(p, ptr, buflen); if (get_unix_group(p, buf, buflen, pdf) < 0) { int xerrno = errno; pr_trace_msg(trace_channel, 3, "malformed Unix text (expected group with '%.*s'): '%.*s'", (int) buflen, buf, (int) textlen, text); errno = xerrno; return NULL; } ptr += buflen; if (strncmp(ptr, " ", 1) != 0) { pr_trace_msg(trace_channel, 3, "malformed Unix text (expected space after group): '%.*s'", (int) textlen, text); errno = EINVAL; return NULL; } ptr += 1; while (PR_ISSPACE(*ptr) && *ptr) { pr_signals_handle(); ptr++; } ptr2 = strchr(ptr, ' '); if (ptr2 == NULL) { pr_trace_msg(trace_channel, 3, "malformed Unix text (expected space after filesize): '%.*s'", (int) textlen, text); errno = EINVAL; return NULL; } buflen = ptr2 - ptr; buf = pstrndup(p, ptr, buflen); if (get_unix_filesize(p, buf, buflen, pdf->st) < 0) { int xerrno = errno; pr_trace_msg(trace_channel, 3, "malformed Unix text (expected filesize with '%.*s'): '%.*s'", (int) buflen, buf, (int) textlen, text); errno = xerrno; return NULL; } ptr += buflen; if (strncmp(ptr, " ", 1) != 0) { pr_trace_msg(trace_channel, 3, "malformed Unix text (expected space after filesize): '%.*s'", (int) textlen, text); errno = EINVAL; return NULL; } ptr += 1; pdf->tm = pcalloc(p, sizeof(struct tm)); buflen = 12; buf = pstrndup(p, ptr, buflen); if (get_unix_timestamp(p, buf, buflen, pdf->tm, tm->tm_year) < 0) { int xerrno = errno; pr_trace_msg(trace_channel, 3, "malformed Unix text (expected timestamp with '%.*s'): '%.*s'", (int) buflen, buf, (int) textlen, text); errno = xerrno; return NULL; } ptr += buflen; if (strncmp(ptr, " ", 1) != 0) { pr_trace_msg(trace_channel, 3, "malformed Unix text (expected space after timestamp): '%.*s'", (int) textlen, text); errno = EINVAL; return NULL; } ptr += 1; if (S_ISLNK(pdf->st->st_mode)) { ptr2 = strchr(ptr, ' '); if (ptr2 == NULL) { pr_trace_msg(trace_channel, 3, "malformed Unix text (expected space after symlink source): '%.*s'", (int) textlen, text); errno = EINVAL; return NULL; } buflen = ptr2 - ptr; pdf->path = pstrndup(p, ptr, buflen); ptr = ptr2 + 1; if (strncmp(ptr, "-> ", 3) != 0) { pr_trace_msg(trace_channel, 3, "malformed Unix text (expected arrow after symlink source): '%.*s'", (int) textlen, text); errno = EINVAL; return NULL; } ptr += 3; if (opts & PROXY_FTP_DIRLIST_OPT_USE_SLINK) { char *target_path; target_path = pstrdup(p, ptr); pdf->type = pstrcat(p, "OS.unix=slink:", target_path, NULL); } } else { pdf->path = pstrdup(p, ptr); } if (strcmp(pdf->path, ".") == 0) { pdf->type = pstrdup(p, "cdir"); } else if (strcmp(pdf->path, "..") == 0) { pdf->type = pstrdup(p, "pdir"); } return pdf; } struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_text(pool *p, const char *text, size_t textlen, struct tm *tm, void *user_data, unsigned long opts) { struct proxy_session *proxy_sess; struct dirlist_ctx *ctx; struct proxy_dirlist_fileinfo *pdf = NULL; if (p == NULL || text == NULL || textlen == 0 || user_data == NULL) { errno = EINVAL; return NULL; } proxy_sess = user_data; if (proxy_sess->dirlist_ctx == NULL) { errno = EINVAL; return NULL; } ctx = proxy_sess->dirlist_ctx; if (ctx->list_style == DIRLIST_LIST_STYLE_UNKNOWN) { /* We don't know yet what style of listing we have, so we use some * heuristics to guess. * * A Windows-style listing always starts with a timestamp, e.g.: * * 01-29-97 11:32PM prog * * Thus if the first character is '0' or '1', we treat it as Windows, * otherwise Unix. */ if (text[0] == '0' || text[1] == '1') { ctx->list_style = DIRLIST_LIST_STYLE_WINDOWS; pr_trace_msg(trace_channel, 19, "assuming Windows-style directory listing data"); } else { ctx->list_style = DIRLIST_LIST_STYLE_UNIX; pr_trace_msg(trace_channel, 19, "assuming Unix-style directory listing data"); } } switch (ctx->list_style) { case DIRLIST_LIST_STYLE_UNIX: pdf = proxy_ftp_dirlist_fileinfo_from_unix(p, text, textlen, tm, opts); break; case DIRLIST_LIST_STYLE_WINDOWS: pdf = proxy_ftp_dirlist_fileinfo_from_dos(p, text, textlen, opts); break; default: pr_trace_msg(trace_channel, 3, "unable to determine directory listing style"); errno = EPERM; pdf = NULL; break; } return pdf; } static size_t facts_fmt(const struct proxy_dirlist_fileinfo *pdf, char *buf, size_t bufsz) { int len; char *ptr; size_t buflen = 0; memset(buf, '\0', bufsz); ptr = buf; if (pdf->tm != NULL && (facts_opts & PROXY_FTP_FACTS_OPT_SHOW_MODIFY)) { len = pr_snprintf(ptr, bufsz, "modify=%04d%02d%02d%02d%02d%02d;", pdf->tm->tm_year+1900, pdf->tm->tm_mon+1, pdf->tm->tm_mday, pdf->tm->tm_hour, pdf->tm->tm_min, pdf->tm->tm_sec); buflen += len; ptr = buf + buflen; } if (pdf->perm != NULL && (facts_opts & PROXY_FTP_FACTS_OPT_SHOW_PERM)) { len = pr_snprintf(ptr, bufsz - buflen, "perm=%s;", pdf->perm); buflen += len; ptr = buf + buflen; } if (pdf->st != NULL && !S_ISDIR(pdf->st->st_mode) && (facts_opts & PROXY_FTP_FACTS_OPT_SHOW_SIZE)) { len = pr_snprintf(ptr, bufsz - buflen, "size=%" PR_LU ";", (pr_off_t) pdf->st->st_size); buflen += len; ptr = buf + buflen; } if (pdf->type != NULL && (facts_opts & PROXY_FTP_FACTS_OPT_SHOW_TYPE)) { len = pr_snprintf(ptr, bufsz - buflen, "type=%s;", pdf->type); buflen += len; ptr = buf + buflen; } if (pdf->st != NULL) { if (facts_opts & PROXY_FTP_FACTS_OPT_SHOW_UNIQUE) { len = pr_snprintf(ptr, bufsz - buflen, "unique=%lXU%lX;", (unsigned long) pdf->st->st_dev, (unsigned long) pdf->st->st_ino); buflen += len; ptr = buf + buflen; } if (pdf->have_gid == TRUE && (facts_opts & PROXY_FTP_FACTS_OPT_SHOW_UNIX_GROUP)) { len = pr_snprintf(ptr, bufsz - buflen, "UNIX.group=%s;", pr_gid2str(NULL, pdf->st->st_gid)); buflen += len; ptr = buf + buflen; } } if (pdf->group != NULL && (facts_opts & PROXY_FTP_FACTS_OPT_SHOW_UNIX_GROUP_NAME)) { len = pr_snprintf(ptr, bufsz - buflen, "UNIX.groupname=%s;", pdf->group); buflen += len; ptr = buf + buflen; } if (pdf->st != NULL) { if (facts_opts & PROXY_FTP_FACTS_OPT_SHOW_UNIX_MODE) { len = pr_snprintf(ptr, bufsz - buflen, "UNIX.mode=0%o;", (unsigned int) pdf->st->st_mode & 07777); buflen += len; ptr = buf + buflen; } if (pdf->have_uid == TRUE && (facts_opts & PROXY_FTP_FACTS_OPT_SHOW_UNIX_OWNER)) { len = pr_snprintf(ptr, bufsz - buflen, "UNIX.owner=%s;", pr_uid2str(NULL, pdf->st->st_uid)); buflen += len; ptr = buf + buflen; } } if (pdf->user != NULL && (facts_opts & PROXY_FTP_FACTS_OPT_SHOW_UNIX_OWNER_NAME)) { len = pr_snprintf(ptr, bufsz - buflen, "UNIX.ownername=%s;", pdf->user); buflen += len; ptr = buf + buflen; } /* Make sure we terminate each line with CRLF; this text will be sent to * the requesting client as is. */ len = pr_snprintf(ptr, bufsz - buflen, " %s\r\n", pdf->path); buf[bufsz-1] = '\0'; buflen += len; return buflen; } const char *proxy_ftp_dirlist_fileinfo_to_facts(pool *p, const struct proxy_dirlist_fileinfo *pdf, size_t *textlen) { char buf[PR_TUNABLE_BUFFER_SIZE]; size_t buflen; if (p == NULL || pdf == NULL || textlen == NULL) { errno = EINVAL; return NULL; } buflen = facts_fmt(pdf, buf, sizeof(buf)); *textlen = buflen; return pstrndup(p, buf, buflen); } static array_header *text_to_lines(pool *p, const char *text, size_t textlen) { char *ptr; array_header *text_lines; text_lines = make_array(p, 1, sizeof(char *)); ptr = proxy_strnstr(text, "\r\n", textlen); while (ptr != NULL) { size_t linelen; pr_signals_handle(); linelen = ptr - text; if (linelen > 0) { char *line; line = palloc(p, linelen + 1); memcpy(line, text, linelen); line[linelen] = '\0'; *((char **) push_array(text_lines)) = line; } text = ptr + 2; textlen = textlen - linelen - 2; if (textlen == 0) { break; } ptr = proxy_strnstr(text, "\r\n", textlen); } if (textlen > 0) { *((char **) push_array(text_lines)) = pstrdup(p, text); } return text_lines; } int proxy_ftp_dirlist_to_text(pool *p, char *buf, size_t buflen, size_t max_textsz, char **output_text, size_t *output_textlen, void *user_data) { register unsigned int i; pool *tmp_pool; struct proxy_session *proxy_sess; struct dirlist_ctx *ctx; char *text, **lines; size_t textlen; array_header *text_lines; unsigned long current_facts_opts; time_t now; struct tm *tm; if (p == NULL || buf == NULL || buflen == 0 || max_textsz == 0 || output_text == NULL || output_textlen == NULL || user_data == NULL) { errno = EINVAL; return -1; } proxy_sess = user_data; if (proxy_sess->dirlist_ctx == NULL) { errno = EINVAL; return -1; } ctx = proxy_sess->dirlist_ctx; tmp_pool = make_sub_pool(p); pr_pool_tag(tmp_pool, "Proxy Dirlist Text Pool"); /* Our text to process is comprised of any previous buffered input text, * and our given input buffer. */ if (ctx->input_textlen == 0) { text = buf; textlen = buflen; } else { /* We cannot use pstrcat() here because the buffers are not * NUL-terminated. */ textlen = ctx->input_textlen + buflen; text = palloc(tmp_pool, textlen + 1); memcpy(text, ctx->input_ptr, ctx->input_textlen); memcpy(text + ctx->input_textlen, buf, buflen); text[textlen] = '\0'; ctx->input_text = ctx->input_ptr; ctx->input_textlen = 0; } if (textlen < 3) { /* Not enough; keep accumulating. */ memcpy(ctx->input_text, text, textlen); ctx->input_text += textlen; ctx->input_textlen += textlen; return 0; } /* Check for a terminating CRLF. If present, we can process the entire * text. Otherwise, trim off the unterminated line, and save it for the * next pass. */ if (text[textlen-2] != '\r' || text[textlen-1] != '\n') { char *ptr = NULL; size_t len = 0; /* Too bad there is no `memrchr(3)` library function. */ for (i = textlen-1; i != 0; i--) { if (text[i] == '\n') { ptr = &(text[i]); break; } } if (ptr == NULL) { memcpy(ctx->input_text, text, textlen); ctx->input_text += textlen; ctx->input_textlen += textlen; return 0; } ptr++; len = textlen - (ptr - text); if (len > ctx->input_textsz) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unterminated directory list data length (%lu bytes) exceeds " "capacity (%lu bytes), rejecting", (unsigned long) len, (unsigned long) ctx->input_textsz); errno = EPERM; return -1; } memcpy(ctx->input_text, ptr, len); ctx->input_text += len; ctx->input_textlen += len; pr_trace_msg(trace_channel, 25, "given text (%lu bytes) is not CRLF-terminated, " "trimming %lu bytes for later", (unsigned long) textlen, (unsigned long) len); textlen -= len; } text_lines = text_to_lines(tmp_pool, text, textlen); current_facts_opts = facts_opts; /* We get the current time, for filling in defaults. */ now = time(NULL); tm = pr_gmtime(tmp_pool, &now); lines = text_lines->elts; for (i = 0; i < text_lines->nelts; i++) { const char *input_line, *output_line; size_t input_linelen, output_linelen = 0; struct proxy_dirlist_fileinfo *pdf; pr_signals_handle(); input_line = lines[i]; input_linelen = strlen(input_line); /* Skip any possible "total NNN" lines, as from /bin/ls. */ if (ctx->skip_total == TRUE) { ctx->skip_total = FALSE; if (strncmp(input_line, "total ", 6) == 0) { continue; } } pdf = proxy_ftp_dirlist_fileinfo_from_text(tmp_pool, input_line, input_linelen, tm, user_data, proxy_sess->dirlist_opts); if (pdf == NULL) { pr_trace_msg(trace_channel, 3, "error parsing text '%.*s': %s", (int) input_linelen, input_line, strerror(errno)); continue; } if (ctx->list_style == DIRLIST_LIST_STYLE_WINDOWS) { /* Once we know that we are parsing a Windows-style directory listing, * we can toggle off the RFC 3649 facts that we KNOW will not be provided * by the listing data. */ facts_opts &= ~PROXY_FTP_FACTS_OPT_SHOW_PERM; facts_opts &= ~PROXY_FTP_FACTS_OPT_SHOW_UNIQUE; facts_opts &= ~PROXY_FTP_FACTS_OPT_SHOW_UNIX_MODE; facts_opts &= ~PROXY_FTP_FACTS_OPT_SHOW_UNIX_GROUP; facts_opts &= ~PROXY_FTP_FACTS_OPT_SHOW_UNIX_GROUP_NAME; facts_opts &= ~PROXY_FTP_FACTS_OPT_SHOW_UNIX_OWNER; facts_opts &= ~PROXY_FTP_FACTS_OPT_SHOW_UNIX_OWNER_NAME; } else { /* Once we know that we are parsing a Unix-style directory listing, * we can toggle off the RFC 3649 facts that we KNOW will not be provided * by the listing data. */ facts_opts &= ~PROXY_FTP_FACTS_OPT_SHOW_UNIQUE; } output_line = proxy_ftp_dirlist_fileinfo_to_facts(tmp_pool, pdf, &output_linelen); pr_trace_msg(trace_channel, 19, "emitting line: '%.*s'", (int) output_linelen, output_line); /* XXX What to do if this will exceed capacity of output buffer? */ /* TODO: Watch for output_linelen > (ctx->output_textsz - ctx->output_textlen), * and rejigger this function to handle the case of "no more input to * accumulate, but have unprocess input". */ sstrcat(ctx->output_text, output_line, ctx->output_textsz - ctx->output_textlen); ctx->output_text += output_linelen; ctx->output_textlen += output_linelen; } facts_opts = current_facts_opts; *output_textlen = ctx->output_textlen; if (*output_textlen > max_textsz) { *output_textlen = max_textsz; } pr_trace_msg(trace_channel, 29, "emitting %lu bytes of output text (max %lu), for %lu bytes of input text", (unsigned long) *output_textlen, (unsigned long) max_textsz, textlen); *output_text = palloc(p, *output_textlen); memcpy(*output_text, ctx->output_ptr, *output_textlen); memmove(ctx->output_ptr, ctx->output_ptr + *output_textlen, ctx->output_textsz - *output_textlen); ctx->output_text = ctx->output_ptr; ctx->output_textlen -= *output_textlen; destroy_pool(tmp_pool); return 0; } proftpd-mod_proxy-0.9.5/lib/proxy/ftp/facts.c000066400000000000000000000066021475737016700212320ustar00rootroot00000000000000/* * ProFTPD - mod_proxy FTP Facts routines * Copyright (c) 2020 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "proxy/ftp/facts.h" /* Similar to those of mod_facts. */ /* NOTE: We only want to parse/handle any OPTS MLST commands from the frontend * client IFF the DirectoryListPolicy is "LIST". Otherwise, let the backend * handle them. But...what if the backend doesn't support OPTS MLST? Do * we watch for that error, and handle it ourselves (e.g. show our defaults)? */ static unsigned long facts_opts = PROXY_FTP_FACTS_OPT_SHOW_MODIFY| PROXY_FTP_FACTS_OPT_SHOW_PERM| PROXY_FTP_FACTS_OPT_SHOW_SIZE| PROXY_FTP_FACTS_OPT_SHOW_TYPE| PROXY_FTP_FACTS_OPT_SHOW_UNIQUE| PROXY_FTP_FACTS_OPT_SHOW_UNIX_GROUP| PROXY_FTP_FACTS_OPT_SHOW_UNIX_GROUP_NAME| PROXY_FTP_FACTS_OPT_SHOW_UNIX_MODE| PROXY_FTP_FACTS_OPT_SHOW_UNIX_OWNER| PROXY_FTP_FACTS_OPT_SHOW_UNIX_OWNER_NAME; static const char *trace_channel = "proxy.ftp.facts"; unsigned long proxy_ftp_facts_get_opts(void) { return facts_opts; } void proxy_ftp_facts_parse_opts(char *facts) { unsigned long opts = 0UL; char *ptr; if (facts == NULL) { return; } ptr = strchr(facts, ';'); while (ptr != NULL) { pr_signals_handle(); *ptr = '\0'; if (strcasecmp(facts, "modify") == 0) { opts |= PROXY_FTP_FACTS_OPT_SHOW_MODIFY; } else if (strcasecmp(facts, "perm") == 0) { opts |= PROXY_FTP_FACTS_OPT_SHOW_PERM; } else if (strcasecmp(facts, "size") == 0) { opts |= PROXY_FTP_FACTS_OPT_SHOW_SIZE; } else if (strcasecmp(facts, "type") == 0) { opts |= PROXY_FTP_FACTS_OPT_SHOW_TYPE; } else if (strcasecmp(facts, "unique") == 0) { opts |= PROXY_FTP_FACTS_OPT_SHOW_UNIQUE; } else if (strcasecmp(facts, "UNIX.group") == 0) { opts |= PROXY_FTP_FACTS_OPT_SHOW_UNIX_GROUP; } else if (strcasecmp(facts, "UNIX.groupname") == 0) { opts |= PROXY_FTP_FACTS_OPT_SHOW_UNIX_GROUP_NAME; } else if (strcasecmp(facts, "UNIX.mode") == 0) { opts |= PROXY_FTP_FACTS_OPT_SHOW_UNIX_MODE; } else if (strcasecmp(facts, "UNIX.owner") == 0) { opts |= PROXY_FTP_FACTS_OPT_SHOW_UNIX_OWNER; } else if (strcasecmp(facts, "UNIX.ownername") == 0) { opts |= PROXY_FTP_FACTS_OPT_SHOW_UNIX_OWNER_NAME; } else { pr_trace_msg(trace_channel, 7, "client requested unsupported fact '%s'", facts); } *ptr = ';'; facts = ptr + 1; ptr = strchr(facts, ';'); } facts_opts = opts; } proftpd-mod_proxy-0.9.5/lib/proxy/ftp/msg.c000066400000000000000000000272741475737016700207300ustar00rootroot00000000000000/* * ProFTPD - mod_proxy FTP message routines * Copyright (c) 2013-2021 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "include/proxy/ftp/msg.h" static const char *trace_channel = "proxy.ftp.msg"; const char *proxy_ftp_msg_fmt_addr(pool *p, const pr_netaddr_t *addr, unsigned short port, int use_masqaddr) { char *addr_str, *msg, *ptr; size_t msglen; if (p == NULL || addr == NULL) { errno = EINVAL; return NULL; } if (use_masqaddr) { config_rec *c; /* TODO What about TLSMasqueradeAddress? */ /* Handle MasqueradeAddress. */ c = find_config(main_server->conf, CONF_PARAM, "MasqueradeAddress", FALSE); if (c != NULL) { addr = c->argv[0]; } } addr_str = pstrdup(p, pr_netaddr_get_ipstr(addr)); /* Fixup the address string for use in PORT commands/PASV responses. */ ptr = strrchr(addr_str, ':'); if (ptr != NULL) { addr_str = ptr + 1; } for (ptr = addr_str; *ptr; ptr++) { if (*ptr == '.') { *ptr = ','; } } /* Allocate enough room for 6 numbers (3 digits max each), 5 separators, * and a trailing NUL. */ msglen = (6 * 3) + (5 * 1) + 1; msg = pcalloc(p, msglen); snprintf(msg, msglen, "%s,%u,%u", addr_str, (port >> 8) & 255, port & 255); return msg; } const char *proxy_ftp_msg_fmt_ext_addr(pool *p, const pr_netaddr_t *addr, unsigned short port, int cmd_id, int use_masqaddr) { const char *addr_str; char delim = '|', *msg; int family = 0; size_t addr_strlen, msglen; if (p == NULL || addr == NULL) { errno = EINVAL; return NULL; } if (use_masqaddr) { config_rec *c; /* Handle MasqueradeAddress. */ c = find_config(main_server->conf, CONF_PARAM, "MasqueradeAddress", FALSE); if (c != NULL) { addr = c->argv[0]; } } /* Format is protoip addressport (ASCII in network order), * where is an arbitrary delimiter character. */ switch (pr_netaddr_get_family(addr)) { case AF_INET: family = 1; break; #ifdef PR_USE_IPV6 case AF_INET6: family = 2; break; #endif /* PR_USE_IPV6 */ default: /* Unlikely to happen. */ errno = EINVAL; return NULL; } addr_str = pr_netaddr_get_ipstr(addr); addr_strlen = strlen(addr_str); /* 4 delimiters, the network protocol, the IP address, the port, and a NUL. */ msglen = (4 * 1) + addr_strlen + 6 + 1; msg = pcalloc(p, msglen); switch (cmd_id) { case PR_CMD_EPRT_ID: snprintf(msg, msglen, "%c%d%c%s%c%hu%c", delim, family, delim, addr_str, delim, port, delim); break; case PR_CMD_EPSV_ID: snprintf(msg, msglen-1, "%c%c%c%u%c", delim, delim, delim, port, delim); break; default: pr_trace_msg(trace_channel, 3, "invalid/unsupported command ID: %d", cmd_id); errno = EINVAL; return NULL; } return msg; } const pr_netaddr_t *proxy_ftp_msg_parse_addr(pool *p, const char *msg, int addr_family) { int valid_fmt = FALSE; const char *ptr; char *addr_buf; unsigned int h1, h2, h3, h4, p1, p2; unsigned short port; size_t addrlen; pr_netaddr_t *addr; if (p == NULL || msg == NULL) { errno = EINVAL; return NULL; } /* Have to scan the message for the encoded address/port. Note that we may * see some strange formats for PASV responses from FTP servers here. * * We can't predict where the expected address/port numbers start in the * string, so start from the beginning. */ h1 = h2 = h3 = h4 = p1 = p2 = 0; for (ptr = msg; *ptr; ptr++) { pr_signals_handle(); h1 = h2 = h3 = h4 = p1 = p2 = 0; if (sscanf(ptr, "%u,%u,%u,%u,%u,%u", &h1, &h2, &h3, &h4, &p1, &p2) == 6) { valid_fmt = TRUE; break; } } if (valid_fmt == FALSE) { pr_trace_msg(trace_channel, 12, "unable to find PORT/PASV address/port format in '%s'", msg); errno = EPERM; return NULL; } if (h1 > 255 || h2 > 255 || h3 > 255 || h4 > 255 || p1 > 255 || p2 > 255 || (h1|h2|h3|h4) == 0 || (p1|p2) == 0) { pr_trace_msg(trace_channel, 9, "message '%s' has invalid address/port value(s)", msg); errno = EINVAL; return NULL; } /* A dotted quad address has a maximum size of 16 bytes: 4 numbers of 3 digits * (max), 3 periods, and 1 terminating NUL. */ addrlen = 16; #ifdef PR_USE_IPV6 /* Allow extra room for any necessary "::ffff:" prefix, for IPv6 sessions. */ addrlen += 7; #endif /* PR_USE_IPV6 */ addr_buf = pcalloc(p, addrlen); #ifdef PR_USE_IPV6 if (pr_netaddr_use_ipv6()) { if (addr_family == AF_INET6) { snprintf(addr_buf, addrlen, "::ffff:%u.%u.%u.%u", h1, h2, h3, h4); } else { snprintf(addr_buf, addrlen, "%u.%u.%u.%u", h1, h2, h3, h4); } } else { snprintf(addr_buf, addrlen, "%u.%u.%u.%u", h1, h2, h3, h4); } #else snprintf(addr_buf, addrlen, "%u.%u.%u.%u", h1, h2, h3, h4); #endif /* PR_USE_IPV6 */ addr = (pr_netaddr_t *) pr_netaddr_get_addr(p, addr_buf, NULL); if (addr == NULL) { int xerrno = errno; pr_trace_msg(trace_channel, 7, "unable to resolve '%s' from message '%s': %s", addr_buf, msg, strerror(xerrno)); errno = xerrno; return NULL; } port = (p1 << 8) + p2; pr_netaddr_set_port2(addr, port); pr_trace_msg(trace_channel, 9, "parsed '%s' into %s %s#%u", msg, pr_netaddr_get_family(addr) == AF_INET ? "IPv4" : "IPv6", pr_netaddr_get_ipstr(addr), ntohs(pr_netaddr_get_port(addr))); return addr; } const pr_netaddr_t *proxy_ftp_msg_parse_ext_addr(pool *p, const char *msg, const pr_netaddr_t *addr, int cmd_id, const char *net_proto) { pr_netaddr_t *res = NULL, na; int family = 0; unsigned short port = 0; char delim, *msg_str, *ptr; size_t msglen; if (p == NULL || msg == NULL || addr == NULL) { errno = EINVAL; return NULL; } if (cmd_id == PR_CMD_EPSV_ID) { int decr = 0; /* First, find the opening '(' character. */ ptr = strchr(msg, '('); if (ptr == NULL) { pr_trace_msg(trace_channel, 12, "missing starting '(' character for extended address in '%s'", msg); errno = EINVAL; return NULL; } /* Make sure that one of the last characters is a closing ')'. Note that * some servers may have a trailing '.' as well. */ msglen = strlen(ptr); if (ptr[msglen-1] == ')') { decr = 1; } else if (ptr[msglen-2] == ')') { decr = 2; } else { pr_trace_msg(trace_channel, 12, "missing ending ')' character for extended address in '%s'", msg); errno = EINVAL; return NULL; } msg_str = pstrndup(p, ptr+1, msglen-decr-1); } else { msg_str = pstrdup(p, msg); } /* Format is protoip addressport (ASCII in network order), * where is an arbitrary delimiter character. */ delim = *msg_str++; /* If the network protocol string (e.g. sent by client in EPSV command) is * null, then determine the protocol family from the address family we were * given. */ /* XXX Hack to skip "all", e.g. "EPSV ALL" commands. */ if (net_proto != NULL) { if (strncasecmp(net_proto, "all", 4) == 0) { net_proto = NULL; } } if (net_proto == NULL) { if (*msg_str == delim) { switch (pr_netaddr_get_family(addr)) { case AF_INET: family = 1; break; #ifdef PR_USE_IPV6 case AF_INET6: if (pr_netaddr_use_ipv6()) { family = 2; break; } #endif /* PR_USE_IPV6 */ default: break; } } else { family = atoi(msg_str); } } else { family = atoi(net_proto); } switch (family) { case 1: pr_trace_msg(trace_channel, 19, "parsed IPv4 address from '%s'", msg); break; #ifdef PR_USE_IPV6 case 2: pr_trace_msg(trace_channel, 19, "parsed IPv6 address from '%s'", msg); if (pr_netaddr_use_ipv6()) { break; } #endif /* PR_USE_IPV6 */ default: pr_trace_msg(trace_channel, 12, "unsupported network protocol %d", family); errno = EPROTOTYPE; return NULL; } /* Now, skip past those numeric characters that atoi() used. */ while (PR_ISDIGIT(*msg_str)) { msg_str++; } /* If the next character is not the delimiter, it's a badly formatted * parameter. */ if (*msg_str == delim) { msg_str++; } else { pr_trace_msg(trace_channel, 17, "rejecting badly formatted message '%s'", msg_str); errno = EPERM; return NULL; } pr_netaddr_clear(&na); /* If the next character IS the delimiter, then the address portion is * omitted (which is permissible). */ if (*msg_str == delim) { pr_netaddr_set_family(&na, pr_netaddr_get_family(addr)); pr_netaddr_set_sockaddr(&na, pr_netaddr_get_sockaddr(addr)); msg_str++; } else { ptr = strchr(msg_str, delim); if (ptr == NULL) { /* Badly formatted message. */ errno = EINVAL; return NULL; } /* Twiddle the string so that just the address portion will be processed * by pr_inet_pton(). */ *ptr = '\0'; /* Use pr_inet_pton() to translate the address string into the address * value. */ switch (family) { case 1: { struct sockaddr *sa = NULL; pr_netaddr_set_family(&na, AF_INET); sa = pr_netaddr_get_sockaddr(&na); if (sa) { sa->sa_family = AF_INET; } if (pr_inet_pton(AF_INET, msg_str, pr_netaddr_get_inaddr(&na)) <= 0) { pr_trace_msg(trace_channel, 2, "error converting IPv4 address '%s': %s", msg_str, strerror(errno)); errno = EPERM; return NULL; } break; } case 2: { struct sockaddr *sa = NULL; pr_netaddr_set_family(&na, AF_INET6); sa = pr_netaddr_get_sockaddr(&na); if (sa) { sa->sa_family = AF_INET6; } if (pr_inet_pton(AF_INET6, msg_str, pr_netaddr_get_inaddr(&na)) <= 0) { pr_trace_msg(trace_channel, 2, "error converting IPv6 address '%s': %s", msg_str, strerror(errno)); errno = EPERM; return NULL; } break; } } /* Advance past the address portion of the argument. */ msg_str = ++ptr; } port = atoi(msg_str); while (PR_ISDIGIT(*msg_str)) { msg_str++; } /* If the next character is not the delimiter, it's a badly formatted * parameter. */ if (*msg_str != delim) { pr_trace_msg(trace_channel, 17, "rejecting badly formatted message '%s'", msg_str); errno = EPERM; return NULL; } res = pr_netaddr_dup(p, &na); pr_netaddr_set_port(res, htons(port)); pr_trace_msg(trace_channel, 9, "parsed '%s' into %s %s#%u", msg, pr_netaddr_get_family(res) == AF_INET ? "IPv4" : "IPv6", pr_netaddr_get_ipstr(res), ntohs(pr_netaddr_get_port(res))); return res; } proftpd-mod_proxy-0.9.5/lib/proxy/ftp/sess.c000066400000000000000000000414561475737016700211150ustar00rootroot00000000000000/* * ProFTPD - mod_proxy FTP session routines * Copyright (c) 2013-2023 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "proxy/conn.h" #include "proxy/netio.h" #include "proxy/tls.h" #include "proxy/ftp/sess.h" #include "proxy/ftp/ctrl.h" static const char *feat_crlf = "\r\n"; static int tls_xfer_prot_policy = 1; static const char *trace_channel = "proxy.ftp.sess"; /* Many FTP servers (e.g. IIS) use the semicolon delimiter syntax, as used * for listing the MLSD/MLST facts, for other FEAT values (e.g. AUTH, PROT, * etc). * * NOTE: Should this return a table rather than an array, for easier lookup * of parsed values by callers? */ static int parse_feat(pool *p, const char *feat, array_header **res) { char *ptr, *ptr2 = NULL; array_header *vals; size_t len; if (feat == NULL) { return 0; } vals = make_array(p, 1, sizeof(char *)); /* No semicolons in this value? No work to do...*/ ptr = strchr(feat, ';'); if (ptr == NULL) { *((char **) push_array(vals)) = pstrdup(p, feat); *res = vals; return vals->nelts; } len = ptr - feat; if (len > 0) { *((char **) push_array(vals)) = pstrndup(p, feat, len); } /* Watch for any sequences of just semicolons. */ while (*ptr == ';') { pr_signals_handle(); ptr++; } ptr2 = strchr(ptr, ';'); while (ptr2 != NULL) { pr_signals_handle(); len = ptr2 - ptr; if (len > 0) { *((char **) push_array(vals)) = pstrndup(p, ptr, len); } ptr = ptr2; while (*ptr == ';') { pr_signals_handle(); ptr++; } ptr2 = strchr(ptr, ';'); } /* Since the semicolon delimiter syntax uses a trailing semicolon, * we shouldn't need to worry about something like "...;FOO". Right? */ *res = vals; return vals->nelts; } int proxy_ftp_sess_get_feat(pool *p, const struct proxy_session *proxy_sess) { pool *tmp_pool; int flags, res, xerrno = 0; cmd_rec *cmd; pr_response_t *resp; unsigned int resp_nlines = 0; char *feats, *feats_start, *token; size_t feats_len = 0, token_len = 0; if (p == NULL || proxy_sess == NULL) { errno = EINVAL; return -1; } tmp_pool = make_sub_pool(p); cmd = pr_cmd_alloc(tmp_pool, 1, C_FEAT); res = proxy_ftp_ctrl_send_cmd(tmp_pool, proxy_sess->backend_ctrl_conn, cmd); if (res < 0) { xerrno = errno; pr_trace_msg(trace_channel, 4, "error sending %s to backend: %s", (char *) cmd->argv[0], strerror(xerrno)); destroy_pool(tmp_pool); errno = xerrno; return -1; } /* Some broken FTP servers actually send blank lines in responses, such * as in a FEAT response. Ugh. (See Issue #251.) * * TODO: Should this use some sort of "enable compatibility with broken/ * non-conformat servrs" ProxyOption/flag? */ flags = PROXY_FTP_CTRL_FL_IGNORE_BLANK_RESP; resp = proxy_ftp_ctrl_recv_resp(tmp_pool, proxy_sess->backend_ctrl_conn, &resp_nlines, flags); if (resp == NULL) { xerrno = errno; pr_trace_msg(trace_channel, 4, "error receiving %s response from backend: %s", (char *) cmd->argv[0], strerror(xerrno)); destroy_pool(tmp_pool); errno = xerrno; return -1; } if (resp->num[0] != '2') { pr_trace_msg(trace_channel, 4, "received unexpected %s response code %s from backend", (char *) cmd->argv[0], resp->num); /* Note: If the UseProxyProtocol ProxyOption is enabled, AND if the * response message mentions a "PROXY" command, we might read an * error response here that is NOT actually for the FEAT command we just * sent. * * A backend FTP server which does not understand the PROXY protocol * will treat it as a normal FTP command, and respond. And that will * put us, the client, out of lockstep with the server, for how do we know * that we need to read that error response FIRST, then send another * command? */ destroy_pool(tmp_pool); errno = EPERM; return -1; } ((struct proxy_session *) proxy_sess)->backend_features = pr_table_nalloc(p, 0, 4); feats_start = feats = (char *) resp->msg; feats_len = strlen(feats); token = pr_str_get_token2(&feats, (char *) feat_crlf, &token_len); while (token != NULL) { pr_signals_handle(); if (token_len > 0) { /* The FEAT response lines in which we are interested all start with * a single space, per RFC spec. Ignore any other lines. */ if (token[0] == ' ') { char *key, *val, *ptr; /* Find the next space in the string, to delimit our key/value pairs. */ ptr = strchr(token + 1, ' '); if (ptr != NULL) { key = pstrndup(p, token + 1, ptr - token - 1); val = pstrdup(p, ptr + 1); } else { key = pstrdup(p, token + 1); val = pstrdup(p, ""); } pr_table_add(proxy_sess->backend_features, key, val, 0); } } feats = token + token_len + 1; /* Don't advance past the end of our FEAT response. */ if (feats > feats_start + feats_len) { break; } token = pr_str_get_token2(&feats, (char *) feat_crlf, &token_len); } destroy_pool(tmp_pool); return 0; } static pr_response_t *send_recv(pool *p, conn_t *conn, cmd_rec *cmd, unsigned int *resp_nlines) { int res, xerrno; pr_response_t *resp; res = proxy_ftp_ctrl_send_cmd(p, conn, cmd); if (res < 0) { xerrno = errno; pr_trace_msg(trace_channel, 4, "error sending '%s %s' to backend: %s", (char *) cmd->argv[0], cmd->arg, strerror(xerrno)); errno = xerrno; return NULL; } resp = proxy_ftp_ctrl_recv_resp(p, conn, resp_nlines, 0); if (resp == NULL) { xerrno = errno; pr_trace_msg(trace_channel, 4, "error receiving %s response from backend: %s", (char *) cmd->argv[0], strerror(xerrno)); errno = xerrno; return NULL; } return resp; } int proxy_ftp_sess_send_host(pool *p, const struct proxy_session *proxy_sess) { pool *tmp_pool; int xerrno = 0; cmd_rec *cmd; pr_response_t *resp; unsigned int resp_nlines = 0; const char *host; if (p == NULL || proxy_sess == NULL) { errno = EINVAL; return -1; } if (pr_table_get(proxy_sess->backend_features, C_HOST, NULL) == NULL) { pr_trace_msg(trace_channel, 9, "HOST not supported by backend server, ignoring"); return 0; } tmp_pool = make_sub_pool(p); host = proxy_conn_get_host(proxy_sess->dst_pconn); /* Make sure we format the HOST parameter properly for an IPv6 address. */ if (pr_netaddr_is_v6(host) == TRUE) { host = pstrcat(tmp_pool, "[", host, "]", NULL); } cmd = pr_cmd_alloc(tmp_pool, 2, C_HOST, host); cmd->arg = pstrdup(tmp_pool, host); resp = send_recv(tmp_pool, proxy_sess->backend_ctrl_conn, cmd, &resp_nlines); if (resp == NULL) { xerrno = errno; destroy_pool(tmp_pool); errno = xerrno; return -1; } if (resp->num[0] != '2') { pr_trace_msg(trace_channel, 4, "received unexpected %s response code %s from backend", (char *) cmd->argv[0], resp->num); destroy_pool(tmp_pool); errno = EPERM; return -1; } destroy_pool(tmp_pool); return 0; } int proxy_ftp_sess_send_auth_tls(pool *p, const struct proxy_session *proxy_sess) { int uri_tls, use_tls, xerrno; const char *auth_feat; array_header *auth_feats = NULL; pool *tmp_pool; cmd_rec *cmd; pr_response_t *resp; unsigned int resp_nlines = 0; config_rec *c; if (p == NULL || proxy_sess == NULL) { errno = EINVAL; return -1; } use_tls = proxy_tls_using_tls(); /* Note: In theory, use_tls should never be MATCH_CLIENT here; that should * have been handled earlier, in the connect routines of the forward/reverse * APIs. */ if (use_tls == PROXY_TLS_ENGINE_MATCH_CLIENT) { proxy_tls_match_client_tls(); use_tls = proxy_tls_using_tls(); } if (use_tls == PROXY_TLS_ENGINE_OFF) { pr_trace_msg(trace_channel, 19, "TLS support not enabled/desired, skipping 'AUTH TLS' command"); return 0; } if (use_tls == PROXY_TLS_ENGINE_IMPLICIT) { pr_trace_msg(trace_channel, 19, "implicit FTPS support requested, skipping 'AUTH TLS' command"); return 0; } /* Check for any per-URI scheme-based TLS requirements. */ uri_tls = proxy_conn_get_tls(proxy_sess->dst_pconn); auth_feat = pr_table_get(proxy_sess->backend_features, C_AUTH, NULL); if (auth_feat == NULL) { /* Backend server does not indicate that it supports AUTH via FEAT. * * Even though this is the case, we will still try to send the AUTH * command. A malicious attacker could be modifying the plaintext * FEAT listing, to make us think that TLS is not supported, and thus * prevent us from encrypting the session (a la "SSL stripping"). */ /* If TLS is required, then complain loudly. */ if (uri_tls == PROXY_TLS_ENGINE_ON || use_tls == PROXY_TLS_ENGINE_ON) { const char *ip_str; ip_str = pr_netaddr_get_ipstr(proxy_sess->backend_ctrl_conn->remote_addr); if (uri_tls == PROXY_TLS_ENGINE_ON) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "backend server %s does not support AUTH TLS (see FEAT response) but " "URI '%.100s' requires TLS, attempting anyway", ip_str, proxy_conn_get_uri(proxy_sess->dst_pconn)); } else if (use_tls == PROXY_TLS_ENGINE_ON) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "backend server %s does not support AUTH TLS (see FEAT response) but " "ProxyTLSEngine requires TLS, attempting anyway", ip_str); } } pr_trace_msg(trace_channel, 9, "backend server does not support AUTH TLS (via FEAT)"); } tmp_pool = make_sub_pool(p); /* Note: the FEAT response against IIS servers shows e.g.: * * 211-Extended features supported: * LANG EN* * UTF8 * AUTH TLS;TLS-C;SSL;TLS-P; * PBSZ * PROT C;P; * CCC * HOST * SIZE * MDTM * REST STREAM * 211 END * * Note how the AUTH and PROT values are not exactly as specified * in RFC 4217. This means we'll need to deal with it as is. There will * be other servers with other FEAT response formats, too. */ if (parse_feat(tmp_pool, auth_feat, &auth_feats) > 0) { register unsigned int i; pr_trace_msg(trace_channel, 9, "parsed FEAT value '%s' into %d %s", auth_feat, auth_feats->nelts, auth_feats->nelts != 1 ? "values" : "value"); for (i = 0; i < auth_feats->nelts; i++) { char *val; val = ((char **) auth_feats->elts)[i]; pr_trace_msg(trace_channel, 9, " %s", val); } } /* XXX How should we interoperate with servers that support/want the * older formats, e.g.: * * AUTH SSL (which automatically assumes PBSZ 0, PROT P) * AUTH TLS-P (synonym for AUTH SSL) * AUTH TLS-C (synonym for AUTH TLS) */ cmd = pr_cmd_alloc(tmp_pool, 2, C_AUTH, "TLS"); cmd->arg = pstrdup(tmp_pool, "TLS"); resp = send_recv(tmp_pool, proxy_sess->backend_ctrl_conn, cmd, &resp_nlines); if (resp == NULL) { xerrno = errno; proxy_netio_use(PR_NETIO_STRM_CTRL, NULL); destroy_pool(tmp_pool); errno = xerrno; return -1; } if (resp->num[0] != '2') { if (uri_tls != PROXY_TLS_ENGINE_ON && use_tls != PROXY_TLS_ENGINE_ON) { proxy_tls_set_tls(PROXY_TLS_ENGINE_OFF); errno = ENOSYS; return -1; } /* XXX Some older servers might respond with a 334 response code, per * RFC 2228. See, for example: * http://serverfault.com/questions/640978/ftp-alter-server-response-in-transit */ pr_trace_msg(trace_channel, 4, "received unexpected %s response code %s from backend", (char *) cmd->argv[0], resp->num); proxy_netio_use(PR_NETIO_STRM_CTRL, NULL); destroy_pool(tmp_pool); errno = EPERM; return -1; } /* Now that we have our AUTH TLS, check for TLS-related configs. */ c = find_config(main_server->conf, CONF_PARAM, "ProxyTLSTransferProtectionPolicy", FALSE); if (c != NULL) { tls_xfer_prot_policy = *((int *) c->argv[0]); switch (tls_xfer_prot_policy) { case PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_CLIENT: case PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_REQUIRED: proxy_tls_set_data_prot(TRUE); break; case PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_CLEAR: proxy_tls_set_data_prot(FALSE); break; default: /* ignore */ break; } } destroy_pool(tmp_pool); return 0; } int proxy_ftp_sess_send_pbsz_prot(pool *p, const struct proxy_session *proxy_sess) { int have_feat_pbsz = FALSE, have_feat_prot = FALSE, res, send_prot = FALSE, use_tls, xerrno; pool *tmp_pool; cmd_rec *cmd; pr_response_t *resp; unsigned int resp_nlines = 0; if (p == NULL || proxy_sess == NULL) { errno = EINVAL; return -1; } use_tls = proxy_tls_using_tls(); if (use_tls == PROXY_TLS_ENGINE_OFF) { pr_trace_msg(trace_channel, 19, "TLS support not enabled/desired, skipping"); return 0; } /* Some FTPS servers don't properly list PBSZ, PROT in their FEAT * responses. If we are to use TLS, though, we will need to send PBSZ, * PROT commands in order to comply with RFC 4217. * * Thus we will opportunistically send PBSZ, PROT commands anyway. * If these are listed in the server's FEAT response, and the commands * fail, then we will return an error; otherwise, we log the response * and move on. */ /* PBSZ */ if (pr_table_get(proxy_sess->backend_features, C_PBSZ, NULL) != NULL) { have_feat_pbsz = TRUE; } tmp_pool = make_sub_pool(p); cmd = pr_cmd_alloc(tmp_pool, 2, C_PBSZ, "0"); cmd->arg = pstrdup(tmp_pool, "0"); res = 0; resp = send_recv(tmp_pool, proxy_sess->backend_ctrl_conn, cmd, &resp_nlines); xerrno = errno; if (resp == NULL) { res = -1; } else { if (resp->num[0] != '2') { pr_trace_msg(trace_channel, 4, "received unexpected %s response code %s from backend", (char *) cmd->argv[0], resp->num); xerrno = EPERM; res = -1; } } destroy_pool(tmp_pool); if (have_feat_pbsz == TRUE && res < 0) { errno = xerrno; return -1; } /* PROT */ if (pr_table_get(proxy_sess->backend_features, C_PROT, NULL) != NULL) { have_feat_prot = TRUE; } /* If our protection policy overrides that of the client, then we will * send our own PROT command. Otherwise, we don't send PROT, because the * frontend client will. * * However, what if the frontend client is NOT using FTPS? In that case, * we should send PROT, even for the "client" policy. */ switch (tls_xfer_prot_policy) { case PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_CLEAR: case PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_REQUIRED: send_prot = TRUE; break; case PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_CLIENT: send_prot = FALSE; if (session.rfc2228_mech == NULL) { send_prot = TRUE; } break; } if (send_prot == TRUE) { const char *prot; resp_nlines = 0; tmp_pool = make_sub_pool(p); switch (tls_xfer_prot_policy) { case PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_CLEAR: prot = "C"; break; case PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_CLIENT: case PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_REQUIRED: default: prot = "P"; break; } cmd = pr_cmd_alloc(tmp_pool, 2, C_PROT, prot); cmd->arg = pstrdup(tmp_pool, prot); res = 0; resp = send_recv(tmp_pool, proxy_sess->backend_ctrl_conn, cmd, &resp_nlines); xerrno = errno; if (resp == NULL) { res = -1; } else { if (resp->num[0] != '2') { pr_trace_msg(trace_channel, 4, "received unexpected %s response code %s from backend", (char *) cmd->argv[0], resp->num); xerrno = EPERM; res = -1; } } destroy_pool(tmp_pool); if (have_feat_prot == TRUE && res < 0) { errno = xerrno; return -1; } } return 0; } proftpd-mod_proxy-0.9.5/lib/proxy/ftp/xfer.c000066400000000000000000000470571475737016700211070ustar00rootroot00000000000000/* * ProFTPD - mod_proxy FTP data transfer routines * Copyright (c) 2013-2025 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "include/proxy/conn.h" #include "include/proxy/inet.h" #include "include/proxy/ftp/conn.h" #include "include/proxy/ftp/ctrl.h" #include "include/proxy/ftp/msg.h" #include "include/proxy/ftp/xfer.h" /* From response.c */ extern pr_response_t *resp_list, *resp_err_list; static const char *trace_channel = "proxy.ftp.xfer"; int proxy_ftp_xfer_prepare_active(int policy_id, cmd_rec *cmd, const char *error_code, struct proxy_session *proxy_sess, int flags) { int backend_family, bind_family, ipv6_backend, res, xerrno = 0; cmd_rec *actv_cmd; const pr_netaddr_t *backend_addr, *bind_addr = NULL; pr_response_t *resp; unsigned int resp_nlines = 0; conn_t *data_conn = NULL; char *active_cmd; const char *resp_msg = NULL; if (cmd == NULL || error_code == NULL || proxy_sess == NULL || proxy_sess->backend_ctrl_conn == NULL) { errno = EINVAL; return -1; } ipv6_backend = FALSE; backend_addr = proxy_conn_get_addr(proxy_sess->dst_pconn, NULL); if (pr_netaddr_get_family(backend_addr) != AF_INET) { ipv6_backend = TRUE; } switch (policy_id) { case PR_CMD_PORT_ID: /* If we have an IPv6 address for the backend server, automatically switch * to using EPRT by falling through to the EPRT case. */ if (ipv6_backend == FALSE) { active_cmd = C_PORT; break; } case PR_CMD_EPRT_ID: /* If the remote host does not mention EPRT in its features, fall back * to using PORT. */ active_cmd = C_EPRT; if (pr_table_get(proxy_sess->backend_features, C_EPRT, NULL) == NULL) { pr_trace_msg(trace_channel, 19, "EPRT not supported by backend server (via FEAT), using PORT"); if (proxy_sess->dataxfer_policy == PR_CMD_EPRT_ID) { proxy_sess->dataxfer_policy = PR_CMD_PORT_ID; } active_cmd = C_PORT; policy_id = PR_CMD_PORT_ID; } break; default: /* In this case, the cmd we were given is the one we should send to * the backend server -- but only if it is either EPRT or PORT. */ if (pr_cmd_cmp(cmd, PR_CMD_EPRT_ID) != 0 && pr_cmd_cmp(cmd, PR_CMD_PORT_ID) != 0) { pr_trace_msg(trace_channel, 9, "illegal FTP active transfer command '%s'", (char *) cmd->argv[0]); errno = EINVAL; return -1; } active_cmd = cmd->argv[0]; /* If we have an IPv6 address for the backend server, automatically switch * to using EPRT. */ if (pr_cmd_cmp(cmd, PR_CMD_PORT_ID) == 0 && ipv6_backend == TRUE) { pr_trace_msg(trace_channel, 19, "automatically switching from %s to %s for IPv6 backend server", active_cmd, C_EPRT); active_cmd = C_EPRT; policy_id = PR_CMD_EPRT_ID; } if (pr_cmd_cmp(cmd, PR_CMD_EPRT_ID) == 0) { /* If the remote host does not mention EPRT in its features, fall back * to using PORT. */ if (pr_table_get(proxy_sess->backend_features, C_EPRT, NULL) == NULL) { pr_trace_msg(trace_channel, 19, "EPRT not supported by backend server (via FEAT), using PORT"); if (proxy_sess->dataxfer_policy == PR_CMD_EPRT_ID) { proxy_sess->dataxfer_policy = PR_CMD_PORT_ID; } active_cmd = C_PORT; policy_id = PR_CMD_PORT_ID; } } break; } bind_addr = proxy_sess->src_addr; if (bind_addr == NULL) { bind_addr = session.c->local_addr; } if (pr_netaddr_is_loopback(bind_addr) == TRUE && pr_netaddr_is_loopback(proxy_sess->backend_ctrl_conn->remote_addr) != TRUE) { const char *local_name; const pr_netaddr_t *local_addr; local_name = pr_netaddr_get_localaddr_str(cmd->pool); local_addr = pr_netaddr_get_addr(cmd->pool, local_name, NULL); if (local_addr != NULL) { pr_trace_msg(trace_channel, 14, "%s is a loopback address, using %s instead", pr_netaddr_get_ipstr(bind_addr), pr_netaddr_get_ipstr(local_addr)); bind_addr = local_addr; } } /* Need to check the family of the bind addr against the family of the * backend server. */ backend_family = pr_netaddr_get_family(proxy_sess->backend_ctrl_conn->remote_addr); bind_family = pr_netaddr_get_family(bind_addr); if (bind_family == backend_family) { #ifdef PR_USE_IPV6 if (pr_netaddr_use_ipv6()) { /* Make sure that the family is NOT IPv6, even though the local and * remote families match. The PORT command cannot be used for IPv6 * addresses -- but EPRT CAN be used for IPv6 addresses. */ if (bind_family == AF_INET6 && policy_id == PR_CMD_PORT_ID) { pr_netaddr_t *mapped_addr; mapped_addr = pr_netaddr_v6tov4(cmd->pool, bind_addr); if (mapped_addr != NULL) { pr_trace_msg(trace_channel, 9, "converting local IPv6 address '%s' to IPv4 address '%s' for " "active transfer using PORT", pr_netaddr_get_ipstr(bind_addr), pr_netaddr_get_ipstr(mapped_addr)); bind_addr = mapped_addr; } else { pr_trace_msg(trace_channel, 3, "unable to convert IPv6 address '%s' to IPv4: %s", pr_netaddr_get_ipstr(bind_addr), strerror(errno)); } } } #endif /* PR_USE_IPV6 */ } else { if (backend_family == AF_INET) { pr_netaddr_t *mapped_addr; /* In this scenario, the remote peer is an IPv4 (or IPv4-mapped IPv6) * peer, so make sure we use an IPv4 local address. */ mapped_addr = pr_netaddr_v6tov4(cmd->pool, bind_addr); if (mapped_addr != NULL) { pr_trace_msg(trace_channel, 9, "converting local IPv6 address '%s' to IPv4 address '%s' for " "active transfer with IPv4 peer", pr_netaddr_get_ipstr(bind_addr), pr_netaddr_get_ipstr(mapped_addr)); bind_addr = mapped_addr; } else { pr_trace_msg(trace_channel, 3, "unable to convert IPv6 address '%s' to IPv4: %s", pr_netaddr_get_ipstr(bind_addr), strerror(errno)); } } } if (proxy_sess->backend_data_conn != NULL) { /* Make sure that we only have one backend data connection. */ proxy_inet_close(session.pool, proxy_sess->backend_data_conn); pr_inet_close(session.pool, proxy_sess->backend_data_conn); proxy_sess->backend_data_conn = NULL; } data_conn = proxy_ftp_conn_listen(cmd->tmp_pool, bind_addr, FALSE); if (data_conn == NULL) { xerrno = errno; pr_response_add_err(error_code, _("Unable to build data connection: Internal error")); pr_response_flush(&resp_err_list); errno = xerrno; return -1; } proxy_sess->backend_data_conn = data_conn; switch (pr_cmd_get_id(active_cmd)) { case PR_CMD_PORT_ID: resp_msg = proxy_ftp_msg_fmt_addr(cmd->tmp_pool, data_conn->local_addr, data_conn->local_port, FALSE); break; case PR_CMD_EPRT_ID: resp_msg = proxy_ftp_msg_fmt_ext_addr(cmd->tmp_pool, data_conn->local_addr, data_conn->local_port, PR_CMD_EPRT_ID, FALSE); break; } actv_cmd = pr_cmd_alloc(cmd->tmp_pool, 2, active_cmd, resp_msg); actv_cmd->arg = (char *) resp_msg; pr_cmd_clear_cache(actv_cmd); res = proxy_ftp_ctrl_send_cmd(cmd->tmp_pool, proxy_sess->backend_ctrl_conn, actv_cmd); if (res < 0) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error sending %s to backend: %s", (char *) actv_cmd->argv[0], strerror(xerrno)); proxy_inet_close(session.pool, proxy_sess->backend_data_conn); pr_inet_close(session.pool, proxy_sess->backend_data_conn); proxy_sess->backend_data_conn = NULL; pr_response_add_err(error_code, "%s: %s", (char *) cmd->argv[0], strerror(xerrno)); pr_response_flush(&resp_err_list); errno = xerrno; return -1; } resp = proxy_ftp_ctrl_recv_resp(cmd->tmp_pool, proxy_sess->backend_ctrl_conn, &resp_nlines, flags); if (resp == NULL) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error receiving %s response from backend: %s", (char *) actv_cmd->argv[0], strerror(xerrno)); proxy_inet_close(session.pool, proxy_sess->backend_data_conn); pr_inet_close(session.pool, proxy_sess->backend_data_conn); proxy_sess->backend_data_conn = NULL; pr_response_add_err(error_code, "%s: %s", (char *) cmd->argv[0], strerror(xerrno)); pr_response_flush(&resp_err_list); errno = xerrno; return -1; } if (resp->num[0] != '2') { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "received non-2xx response from backend for %s: %s %s", (char *) actv_cmd->argv[0], resp->num, resp->msg); proxy_inet_close(session.pool, proxy_sess->backend_data_conn); pr_inet_close(session.pool, proxy_sess->backend_data_conn); proxy_sess->backend_data_conn = NULL; if (policy_id == PR_CMD_EPRT_ID) { /* If using EPRT failed, try again using PORT, and switch the * DataTransferPolicy (if EPRT) to be PORT, for future attempts. */ if (proxy_sess->dataxfer_policy == PR_CMD_EPRT_ID) { pr_trace_msg(trace_channel, 15, "falling back from EPRT to PORT DataTransferPolicy"); proxy_sess->dataxfer_policy = PR_CMD_PORT_ID; } return proxy_ftp_xfer_prepare_active(PR_CMD_PORT_ID, cmd, error_code, proxy_sess, flags); } pr_response_add_err(error_code, "%s", resp->msg); pr_response_flush(&resp_err_list); errno = EINVAL; return -1; } return 0; } const pr_netaddr_t *proxy_ftp_xfer_prepare_passive(int policy_id, cmd_rec *cmd, const char *error_code, struct proxy_session *proxy_sess, int flags) { int ipv6_backend, res, xerrno = 0; cmd_rec *pasv_cmd; const pr_netaddr_t *backend_addr, *remote_addr = NULL; pr_response_t *resp; unsigned int resp_nlines = 0; unsigned short remote_port; char *passive_cmd, *passive_respcode = NULL; if (cmd == NULL || error_code == NULL || proxy_sess == NULL || proxy_sess->backend_ctrl_conn == NULL) { errno = EINVAL; return NULL; } /* Whether we send a PASV (and expect 227) or an EPSV (and expect 229) * needs to depend on the policy_id, AND on the backend address. */ ipv6_backend = FALSE; backend_addr = proxy_conn_get_addr(proxy_sess->dst_pconn, NULL); if (pr_netaddr_get_family(backend_addr) != AF_INET) { ipv6_backend = TRUE; } switch (policy_id) { case PR_CMD_PASV_ID: /* If we have an IPv6 address for the backend server, automatically switch * to using EPSV by falling through to the EPSV case. */ if (ipv6_backend == FALSE) { passive_cmd = C_PASV; break; } case PR_CMD_EPSV_ID: { int epsv_supported = TRUE; passive_cmd = C_EPSV; if (pr_table_get(proxy_sess->backend_features, C_EPSV, NULL) == NULL) { epsv_supported = FALSE; /* If the remote host does not mention EPSV in its features, fall back * to using PASV. Note, however, that some servers (e.g. pure-ftpd) * only mention EPRT in their FEAT to cover both EPRT and EPSV. */ if (pr_table_get(proxy_sess->backend_features, C_EPRT, NULL) != NULL) { epsv_supported = TRUE; } } if (epsv_supported == FALSE) { pr_trace_msg(trace_channel, 19, "EPSV not supported by backend server (via FEAT), using PASV"); if (proxy_sess->dataxfer_policy == PR_CMD_EPSV_ID) { proxy_sess->dataxfer_policy = PR_CMD_PASV_ID; } passive_cmd = C_PASV; policy_id = PR_CMD_PASV_ID; } break; } default: /* In this case, the cmd we were given is the one we should send to * the backend server -- but only if it is either EPSV or PASV. */ if (pr_cmd_cmp(cmd, PR_CMD_EPSV_ID) != 0 && pr_cmd_cmp(cmd, PR_CMD_PASV_ID) != 0) { pr_trace_msg(trace_channel, 9, "illegal FTP passive transfer command '%s'", (char *) cmd->argv[0]); errno = EINVAL; return NULL; } passive_cmd = cmd->argv[0]; /* If we have an IPv6 address for the backend server, automatically switch * to using EPSV. */ if (pr_cmd_cmp(cmd, PR_CMD_PASV_ID) == 0 && ipv6_backend == TRUE) { pr_trace_msg(trace_channel, 19, "automatically switching from %s to %s for IPv6 backend server", passive_cmd, C_EPSV); passive_cmd = C_EPSV; } if (pr_cmd_cmp(cmd, PR_CMD_EPSV_ID) == 0) { int epsv_supported = FALSE; if (pr_table_get(proxy_sess->backend_features, C_EPSV, NULL) == NULL) { /* If the remote host does not mention EPSV in its features, fall back * to using PASV. Note, however, that some servers (e.g. pure-ftpd) * only mention EPRT in their FEAT to cover both EPRT and EPSV. */ if (pr_table_get(proxy_sess->backend_features, C_EPRT, NULL) != NULL) { epsv_supported = TRUE; } } if (epsv_supported == FALSE) { pr_trace_msg(trace_channel, 19, "EPSV not supported by backend server (via FEAT), using PASV"); if (proxy_sess->dataxfer_policy == PR_CMD_EPSV_ID) { proxy_sess->dataxfer_policy = PR_CMD_PASV_ID; } passive_cmd = C_PASV; policy_id = PR_CMD_PASV_ID; } } break; } pasv_cmd = pr_cmd_alloc(cmd->tmp_pool, 1, passive_cmd); switch (pr_cmd_get_id(pasv_cmd->argv[0])) { case PR_CMD_PASV_ID: passive_respcode = R_227; break; case PR_CMD_EPSV_ID: passive_respcode = R_229; break; } res = proxy_ftp_ctrl_send_cmd(cmd->tmp_pool, proxy_sess->backend_ctrl_conn, pasv_cmd); if (res < 0) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error sending %s to backend: %s", (char *) pasv_cmd->argv[0], strerror(xerrno)); pr_response_add_err(error_code, "%s: %s", (char *) cmd->argv[0], strerror(xerrno)); pr_response_flush(&resp_err_list); errno = xerrno; return NULL; } resp = proxy_ftp_ctrl_recv_resp(cmd->tmp_pool, proxy_sess->backend_ctrl_conn, &resp_nlines, flags); if (resp == NULL) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error receiving %s response from backend: %s", (char *) pasv_cmd->argv[0], strerror(xerrno)); pr_response_add_err(error_code, "%s: %s", (char *) cmd->argv[0], strerror(xerrno)); pr_response_flush(&resp_err_list); errno = xerrno; return NULL; } /* We specifically expect a 227 or 229 response code here; anything else is * an error. Right? */ if (strncmp(resp->num, passive_respcode, 4) != 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "received response code %s, but expected %s for %s command", resp->num, passive_respcode, (char *) pasv_cmd->argv[0]); if (policy_id == PR_CMD_EPSV_ID) { /* If using EPSV failed, try again using PASV, and switch the * DataTransferPolicy (if EPSV) to be PASV, for future attempts. */ if (proxy_sess->dataxfer_policy == PR_CMD_EPSV_ID) { pr_trace_msg(trace_channel, 15, "falling back from EPSV to PASV DataTransferPolicy"); proxy_sess->dataxfer_policy = PR_CMD_PASV_ID; } return proxy_ftp_xfer_prepare_passive(PR_CMD_PASV_ID, cmd, error_code, proxy_sess, flags); } res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool, proxy_sess->frontend_ctrl_conn, resp, resp_nlines); errno = EPERM; return NULL; } switch (pr_cmd_get_id(pasv_cmd->argv[0])) { case PR_CMD_PASV_ID: { int remote_family; remote_family = pr_netaddr_get_family(proxy_sess->backend_ctrl_conn->remote_addr); remote_addr = proxy_ftp_msg_parse_addr(proxy_sess->dataxfer_pool, resp->msg, remote_family); break; } case PR_CMD_EPSV_ID: remote_addr = proxy_ftp_msg_parse_ext_addr(proxy_sess->dataxfer_pool, resp->msg, backend_addr, PR_CMD_EPSV_ID, NULL); break; } if (remote_addr == NULL) { xerrno = errno; pr_trace_msg(trace_channel, 2, "error parsing %s response '%s': %s", (char *) pasv_cmd->argv[0], resp->msg, strerror(xerrno)); xerrno = EPERM; pr_response_add_err(error_code, "%s: %s", (char *) cmd->argv[0], strerror(xerrno)); pr_response_flush(&resp_err_list); errno = xerrno; return NULL; } remote_port = ntohs(pr_netaddr_get_port(remote_addr)); /* See if the given address matches the address to which we originally * connected. */ if (pr_netaddr_cmp(remote_addr, proxy_sess->backend_ctrl_conn->remote_addr) != 0) { pr_trace_msg(trace_channel, 2, "backend passive transfer address %s does not match backend control " "connection address %s", pr_netaddr_get_ipstr(remote_addr), pr_netaddr_get_ipstr(proxy_sess->backend_ctrl_conn->remote_addr)); if (proxy_opts & PROXY_OPT_IGNORE_FOREIGN_ADDRESS) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "Ignoring %s address %s per IgnoreForeignAddress ProxyOption, using %s " "instead", (char *) pasv_cmd->argv[0], pr_netaddr_get_ipstr(remote_addr), pr_netaddr_get_ipstr(proxy_sess->backend_ctrl_conn->remote_addr)); remote_addr = pr_netaddr_dup(proxy_sess->dataxfer_pool, proxy_sess->backend_ctrl_conn->remote_addr); pr_netaddr_set_port2((pr_netaddr_t *) remote_addr, remote_port); } else { if (!(proxy_opts & PROXY_OPT_ALLOW_FOREIGN_ADDRESS)) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "Refused %s address %s (address mismatch with %s)", (char *) pasv_cmd->argv[0], pr_netaddr_get_ipstr(remote_addr), pr_netaddr_get_ipstr(proxy_sess->backend_ctrl_conn->remote_addr)); xerrno = EPERM; pr_response_add_err(error_code, "%s: %s", (char *) cmd->argv[0], strerror(xerrno)); pr_response_flush(&resp_err_list); errno = xerrno; return NULL; } } } if (remote_port < 1024) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "Refused %s port %hu (below 1024)", (char *) pasv_cmd->argv[0], remote_port); xerrno = EPERM; pr_response_add_err(error_code, "%s: %s", (char *) cmd->argv[0], strerror(xerrno)); pr_response_flush(&resp_err_list); errno = xerrno; return NULL; } pr_trace_msg(trace_channel, 12, "obtained address %s#%d for passive data transfer", pr_netaddr_get_ipstr(remote_addr), ntohs(pr_netaddr_get_port(remote_addr))); return remote_addr; } proftpd-mod_proxy-0.9.5/lib/proxy/inet.c000066400000000000000000000116511475737016700203000ustar00rootroot00000000000000/* * ProFTPD - mod_proxy Inet implementation * Copyright (c) 2015-2021 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "proxy/netio.h" #include "proxy/inet.h" conn_t *proxy_inet_accept(pool *p, conn_t *data_conn, conn_t *ctrl_conn, int rfd, int wfd, int resolve) { int xerrno; conn_t *conn; pr_netio_t *curr_netio; curr_netio = proxy_netio_unset(PR_NETIO_STRM_DATA, "inet_accept"); conn = pr_inet_accept(p, data_conn, ctrl_conn, rfd, wfd, (unsigned char) resolve); xerrno = errno; proxy_netio_set(PR_NETIO_STRM_DATA, curr_netio); errno = xerrno; return conn; } void proxy_inet_close(pool *p, conn_t *conn) { if (conn != NULL) { /* Note that we do our own close here, rather than relying on the * core Inet's close, as that one simply relies on the connection * cleanup callback -- and we want to use our own Proxy Netio API * functions for closing, too. */ /* Shutdowns first, then closes. */ if (conn->instrm != NULL) { proxy_netio_shutdown(conn->instrm, 0); } if (conn->outstrm != NULL) { proxy_netio_shutdown(conn->outstrm, 1); } if (conn->instrm != NULL) { proxy_netio_close(conn->instrm); conn->instrm = NULL; } if (conn->outstrm != NULL) { proxy_netio_close(conn->outstrm); conn->outstrm = NULL; } if (conn->listen_fd != -1) { (void) close(conn->listen_fd); conn->listen_fd = -1; } if (conn->rfd != -1) { (void) close(conn->rfd); conn->rfd = -1; } if (conn->wfd != -1) { (void) close(conn->wfd); conn->wfd = -1; } } } int proxy_inet_connect(pool *p, conn_t *conn, const pr_netaddr_t *addr, int port) { int instrm_type = -1, outstrm_type = -1, res, xerrno; pr_netio_t *in_netio = NULL, *out_netio = NULL; if (conn != NULL) { if (conn->instrm != NULL) { instrm_type = conn->instrm->strm_type; in_netio = proxy_netio_unset(instrm_type, "inet_connect"); } if (conn->outstrm != NULL) { outstrm_type = conn->outstrm->strm_type; if (outstrm_type != instrm_type) { out_netio = proxy_netio_unset(outstrm_type, "inet_connect"); } } } res = pr_inet_connect(p, conn, addr, port); xerrno = errno; if (in_netio != NULL) { proxy_netio_set(instrm_type, in_netio); } if (out_netio != NULL) { proxy_netio_set(outstrm_type, out_netio); } errno = xerrno; return res; } int proxy_inet_listen(pool *p, conn_t *conn, int backlog, int flags) { int instrm_type = -1, outstrm_type = -1, res, xerrno; pr_netio_t *in_netio = NULL, *out_netio = NULL; if (conn != NULL) { if (conn->instrm != NULL) { instrm_type = conn->instrm->strm_type; in_netio = proxy_netio_unset(instrm_type, "inet_listen"); } if (conn->outstrm != NULL) { outstrm_type = conn->outstrm->strm_type; if (outstrm_type != instrm_type) { out_netio = proxy_netio_unset(outstrm_type, "inet_listen"); } } } res = pr_inet_listen(p, conn, backlog, flags); xerrno = errno; if (in_netio != NULL) { proxy_netio_set(instrm_type, in_netio); } if (out_netio != NULL) { proxy_netio_set(outstrm_type, out_netio); } errno = xerrno; return res; } conn_t *proxy_inet_openrw(pool *p, conn_t *conn, const pr_netaddr_t *addr, int strm_type, int fd, int rfd, int wfd, int resolve) { int xerrno; conn_t *new_conn; pr_netio_t *curr_netio = NULL; curr_netio = proxy_netio_unset(strm_type, "inet_openrw"); new_conn = pr_inet_openrw(p, conn, addr, strm_type, fd, rfd, wfd, resolve); xerrno = errno; proxy_netio_set(strm_type, curr_netio); if (new_conn != NULL) { /* Note: pr_inet_openrw() calls pr_inet_copy_conn(), which registers * a cleanup on the create object. But we clean up our own data, * so that cleanup, when run, will attempt a double-free. Thus we * unregister that cleanup here. */ unregister_cleanup(new_conn->pool, new_conn, NULL); } errno = xerrno; return new_conn; } proftpd-mod_proxy-0.9.5/lib/proxy/netio.c000066400000000000000000000207311475737016700204560ustar00rootroot00000000000000/* * ProFTPD - mod_proxy NetIO implementation * Copyright (c) 2015-2021 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "proxy/netio.h" static const char *trace_channel = "proxy.netio"; static pr_netio_t *ctrl_netio = NULL; static pr_netio_t *data_netio = NULL; static const char *netio_strm_typestr(int strm_type) { const char *typestr = "(unknown)"; switch (strm_type) { case PR_NETIO_STRM_CTRL: typestr = "ctrl"; break; case PR_NETIO_STRM_DATA: typestr = "data"; break; case PR_NETIO_STRM_OTHR: typestr = "othr"; break; default: break; } return typestr; } int proxy_netio_use(int strm_type, pr_netio_t *netio) { int res; switch (strm_type) { case PR_NETIO_STRM_CTRL: ctrl_netio = netio; res = 0; break; case PR_NETIO_STRM_DATA: data_netio = netio; res = 0; break; default: errno = ENOSYS; res = -1; } return res; } int proxy_netio_using(int strm_type, pr_netio_t **netio) { int res; if (netio == NULL) { errno = EINVAL; return -1; } switch (strm_type) { case PR_NETIO_STRM_CTRL: *netio = ctrl_netio; res = 0; break; case PR_NETIO_STRM_DATA: *netio = data_netio; res = 0; break; default: errno = ENOENT; res = -1; } return res; } pr_netio_t *proxy_netio_unset(int strm_type, const char *fn) { pr_netio_t *netio = NULL; if (fn == NULL) { errno = EINVAL; return NULL; } netio = pr_get_netio(strm_type); if (netio != NULL) { const char *owner_name = "core", *typestr; if (netio->owner_name != NULL) { owner_name = netio->owner_name; } typestr = netio_strm_typestr(strm_type); pr_trace_msg(trace_channel, 18, "(%s) found %s %s NetIO", fn, owner_name, typestr); if (pr_unregister_netio(strm_type) < 0) { pr_trace_msg(trace_channel, 3, "(%s) error unregistering %s NetIO: %s", fn, typestr, strerror(errno)); } } /* Regardless of whether we found a previously registered NetIO, make * sure to use our own NetIO, if any. */ switch (strm_type) { case PR_NETIO_STRM_CTRL: if (ctrl_netio != NULL) { if (pr_register_netio(ctrl_netio, strm_type) < 0) { pr_trace_msg(trace_channel, 3, "(%s) error registering proxy %s NetIO: %s", fn, netio_strm_typestr(strm_type), strerror(errno)); } else { pr_trace_msg(trace_channel, 19, "(%s) using proxy %s NetIO", fn, netio_strm_typestr(strm_type)); } } break; case PR_NETIO_STRM_DATA: if (data_netio != NULL) { if (pr_register_netio(data_netio, strm_type) < 0) { pr_trace_msg(trace_channel, 3, "(%s) error registering proxy %s NetIO: %s", fn, netio_strm_typestr(strm_type), strerror(errno)); } else { pr_trace_msg(trace_channel, 19, "(%s) using proxy %s NetIO", fn, netio_strm_typestr(strm_type)); } } break; default: break; } return netio; } int proxy_netio_set(int strm_type, pr_netio_t *netio) { /* Note: we DO want to unregister the registered stream type, assuming we * have a NetIO of our own to use for that type. */ switch (strm_type) { case PR_NETIO_STRM_CTRL: if (ctrl_netio != NULL) { (void) pr_unregister_netio(strm_type); } break; case PR_NETIO_STRM_DATA: if (data_netio != NULL) { (void) pr_unregister_netio(strm_type); } break; default: break; } if (netio != NULL) { if (pr_register_netio(netio, strm_type) < 0) { pr_trace_msg(trace_channel, 3, "error registering previous %s NetIO: %s", netio_strm_typestr(strm_type), strerror(errno)); } } return 0; } int proxy_netio_close(pr_netio_stream_t *nstrm) { int strm_type = -1, res, xerrno; pr_netio_t *curr_netio = NULL; if (nstrm != NULL) { strm_type = nstrm->strm_type; curr_netio = proxy_netio_unset(strm_type, "netio_close"); } res = pr_netio_close(nstrm); xerrno = errno; if (strm_type != -1) { proxy_netio_set(strm_type, curr_netio); } errno = xerrno; return res; } pr_netio_stream_t *proxy_netio_open(pool *p, int strm_type, int fd, int mode) { int xerrno; pr_netio_stream_t *nstrm = NULL; pr_netio_t *curr_netio; curr_netio = proxy_netio_unset(strm_type, "netio_open"); nstrm = pr_netio_open(p, strm_type, fd, mode); xerrno = errno; proxy_netio_set(strm_type, curr_netio); errno = xerrno; return nstrm; } int proxy_netio_poll(pr_netio_stream_t *nstrm) { int res, xerrno; pr_netio_t *curr_netio; if (nstrm == NULL) { errno = EINVAL; return -1; } curr_netio = proxy_netio_unset(nstrm->strm_type, "netio_poll"); res = pr_netio_poll(nstrm); xerrno = errno; proxy_netio_set(nstrm->strm_type, curr_netio); errno = xerrno; return res; } int proxy_netio_postopen(pr_netio_stream_t *nstrm) { int res = 0, xerrno; pr_netio_t *curr_netio = NULL; if (nstrm == NULL) { errno = EINVAL; return -1; } curr_netio = proxy_netio_unset(nstrm->strm_type, "netio_postpopen"); res = pr_netio_postopen(nstrm); xerrno = errno; proxy_netio_set(nstrm->strm_type, curr_netio); errno = xerrno; return res; } int proxy_netio_printf(pr_netio_stream_t *nstrm, const char *fmt, ...) { int res, xerrno; va_list msg; pr_netio_t *curr_netio = NULL; if (nstrm == NULL) { errno = EINVAL; return -1; } curr_netio = proxy_netio_unset(nstrm->strm_type, "netio_printf"); va_start(msg, fmt); res = pr_netio_vprintf(nstrm, fmt, msg); xerrno = errno; va_end(msg); proxy_netio_set(nstrm->strm_type, curr_netio); errno = xerrno; return res; } int proxy_netio_read(pr_netio_stream_t *nstrm, char *buf, size_t bufsz, int bufmin) { int res, xerrno; pr_netio_t *curr_netio = NULL; if (nstrm == NULL) { errno = EINVAL; return -1; } curr_netio = proxy_netio_unset(nstrm->strm_type, "netio_read"); res = pr_netio_read(nstrm, buf, bufsz, bufmin); xerrno = errno; proxy_netio_set(nstrm->strm_type, curr_netio); errno = xerrno; return res; } void proxy_netio_reset_poll_interval(pr_netio_stream_t *nstrm) { pr_netio_t *curr_netio = NULL; if (nstrm == NULL) { return; } curr_netio = proxy_netio_unset(nstrm->strm_type, "netio_reset_poll_interval"); pr_netio_reset_poll_interval(nstrm); proxy_netio_set(nstrm->strm_type, curr_netio); } void proxy_netio_set_poll_interval(pr_netio_stream_t *nstrm, unsigned int secs) { pr_netio_t *curr_netio = NULL; if (nstrm == NULL) { return; } curr_netio = proxy_netio_unset(nstrm->strm_type, "netio_set_poll_interval"); pr_netio_set_poll_interval(nstrm, secs); proxy_netio_set(nstrm->strm_type, curr_netio); } int proxy_netio_shutdown(pr_netio_stream_t *nstrm, int how) { int res, xerrno; pr_netio_t *curr_netio = NULL; if (nstrm == NULL) { errno = EINVAL; return -1; } curr_netio = proxy_netio_unset(nstrm->strm_type, "netio_shutdown"); res = pr_netio_shutdown(nstrm, how); xerrno = errno; proxy_netio_set(nstrm->strm_type, curr_netio); errno = xerrno; return res; } int proxy_netio_write(pr_netio_stream_t *nstrm, char *buf, size_t bufsz) { int res, xerrno; pr_netio_t *curr_netio = NULL; if (nstrm == NULL) { errno = EINVAL; return -1; } curr_netio = proxy_netio_unset(nstrm->strm_type, "netio_write"); res = pr_netio_write(nstrm, buf, bufsz); xerrno = errno; proxy_netio_set(nstrm->strm_type, curr_netio); errno = xerrno; return res; } proftpd-mod_proxy-0.9.5/lib/proxy/random.c000066400000000000000000000036341475737016700206230ustar00rootroot00000000000000/* * ProFTPD - mod_proxy random number implementation * Copyright (c) 2013-2020 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "proxy/random.h" static const char *trace_channel = "proxy.random"; /* If random(3) is supported on this platform, seed it. rand(3) is already * seeded by the core proftpd code. */ int proxy_random_init(void) { #ifdef HAVE_RANDOM struct timeval tv; gettimeofday(&tv, NULL); srandom(getpid() ^ tv.tv_usec); #endif /* HAVE_RANDOM */ return 0; } long proxy_random_next(long min, long max) { long r, scaled; #if defined(HAVE_RANDOM) r = random(); pr_trace_msg(trace_channel, 22, "obtained r = %ld from random(3)", r); #else r = (long) rand(); pr_trace_msg(trace_channel, 22, "obtained r = %ld from rand(3)", r); #endif /* HAVE_RANDOM */ scaled = r % (max - min + 1) + min; pr_trace_msg(trace_channel, 15, "yielding scaled r = %ld (r = %ld, max = %ld, min = %ld)", scaled, r, max, min); return scaled; } proftpd-mod_proxy-0.9.5/lib/proxy/reverse.c000066400000000000000000001502201475737016700210100ustar00rootroot00000000000000/* * ProFTPD - mod_proxy reverse proxy implementation * Copyright (c) 2012-2022 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "json.h" #include "proxy/db.h" #include "proxy/conn.h" #include "proxy/netio.h" #include "proxy/inet.h" #include "proxy/reverse.h" #include "proxy/reverse/db.h" #include "proxy/reverse/redis.h" #include "proxy/random.h" #include "proxy/tls.h" #include "proxy/ftp/ctrl.h" #include "proxy/ftp/sess.h" #include extern xaset_t *server_list; static array_header *default_backends = NULL, *reverse_backends = NULL; static int reverse_backend_id = -1; static int reverse_backend_updated = FALSE; static int reverse_connect_policy = PROXY_REVERSE_CONNECT_POLICY_ROUND_ROBIN; static unsigned long reverse_flags = 0UL; static int reverse_retry_count = PROXY_DEFAULT_RETRY_COUNT; static struct proxy_reverse_datastore reverse_ds; /* Flag that indicates that we should select/connect to the backend server * at session init time, i.e. when proxy auth is not required, and we're using * a balancing policy. */ #define PROXY_REVERSE_FL_CONNECT_AT_SESS_INIT 1 /* Flag that indicates that we should select/connect to the backend server * at USER time, i.e. when proxy auth is not required, and we're using a * sticky policy. */ #define PROXY_REVERSE_FL_CONNECT_AT_USER 2 /* Flag that indicates that we should select/connect to the backend server * at PASS time, i.e. when proxy auth IS required (balancing/sticky policy * doesn't really matter). */ #define PROXY_REVERSE_FL_CONNECT_AT_PASS 3 /* JSON handling */ #define PROXY_REVERSE_JSON_MAX_FILE_SIZE (1024 * 1024 * 5) #define PROXY_REVERSE_JSON_MAX_ITEMS 1000 static const char *trace_channel = "proxy.reverse"; static void clear_user_creds(void) { register unsigned int i; if (reverse_backends == NULL || reverse_backends->nelts == 0) { /* Nothing to do. */ return; } for (i = 0; i < reverse_backends->nelts; i++) { struct proxy_conn *pconn; pconn = ((struct proxy_conn **) reverse_backends->elts)[i]; proxy_conn_clear_username(pconn); proxy_conn_clear_password(pconn); } } static int check_parent_dir_perms(pool *p, const char *path) { struct stat st; int res; char *dir_path, *ptr = NULL; ptr = strrchr(path, '/'); if (ptr != path) { dir_path = pstrndup(p, path, ptr - path); } else { dir_path = "/"; } res = stat(dir_path, &st); if (res < 0) { int xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to check ProxyReverseServers %s directory '%s': %s", path, dir_path, strerror(xerrno)); pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": unable to check ProxyReverseServers %s directory '%s': %s", path, dir_path, strerror(xerrno)); errno = xerrno; return -1; } if (!(proxy_opts & PROXY_OPT_IGNORE_CONFIG_PERMS) && (st.st_mode & S_IWOTH)) { int xerrno = EPERM; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to use ProxyReverseServers '%s' from world-writable " "directory '%s' (perms %04o): %s", path, dir_path, st.st_mode & ~S_IFMT, strerror(xerrno)); pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": unable to use ProxyReverseServers '%s' from world-writable " "directory '%s' (perms %04o): %s", path, dir_path, st.st_mode & ~S_IFMT, strerror(xerrno)); errno = xerrno; return -1; } return 0; } static int check_file_perms(pool *p, const char *path) { struct stat st; int res; const char *orig_path; orig_path = path; res = lstat(path, &st); if (res < 0) { int xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to check ProxyReverseServers '%s': %s", path, strerror(xerrno)); pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": unable to check ProxyReverseServers '%s': %s", path, strerror(xerrno)); errno = xerrno; return -1; } if (S_ISLNK(st.st_mode)) { char buf[PR_TUNABLE_PATH_MAX+1]; /* Check the permissions on the parent directory; if they're world-writable, * then this symlink can be deleted/pointed somewhere else. */ res = check_parent_dir_perms(p, path); if (res < 0) { return -1; } /* Follow the link to the target path; that path will then have its * parent directory checked. */ memset(buf, '\0', sizeof(buf)); res = readlink(path, buf, sizeof(buf)-1); if (res > 0) { path = pstrdup(p, buf); } res = stat(orig_path, &st); if (res < 0) { int xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to check ProxyReverseServers '%s': %s", orig_path, strerror(xerrno)); pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": unable to check ProxyReverseServers '%s': %s", orig_path, strerror(xerrno)); errno = xerrno; return -1; } } if (S_ISDIR(st.st_mode)) { int xerrno = EISDIR; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to use ProxyReverseServers '%s': %s", orig_path, strerror(xerrno)); pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": unable to use ProxyReverseServers '%s': %s", orig_path, strerror(xerrno)); errno = xerrno; return -1; } /* World-writable files are insecure, and are thus not usable/trusted. */ if (st.st_mode & S_IWOTH) { int xerrno = EPERM; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to use world-writable ProxyReverseServers '%s' " "(perms %04o): %s", orig_path, st.st_mode & ~S_IFMT, strerror(xerrno)); pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": unable to use world-writable ProxyReverseServers '%s' " "(perms %04o): %s", orig_path, st.st_mode & ~S_IFMT, strerror(xerrno)); errno = xerrno; return -1; } /* TODO: This will warn about files such as FIFOs, BUT will leave them * usable. Good idea, or bad idea? */ if (!S_ISREG(st.st_mode)) { pr_log_pri(PR_LOG_WARNING, MOD_PROXY_VERSION ": ProxyReverseServers '%s' is not a regular file", orig_path); } /* Check the parent directory of this file. If the parent directory * is world-writable, that too is insecure. */ res = check_parent_dir_perms(p, path); if (res < 0) { return -1; } return 0; } /* Shared/common routines for PerUser and PerGroup. */ static array_header *reverse_db_parse_uris(pool *p, array_header *uris) { array_header *pconns = NULL; register unsigned int i; pconns = make_array(p, 0, sizeof(struct proxy_conn *)); for (i = 0; i < uris->nelts; i++) { char *uri; const struct proxy_conn *pconn; pr_signals_handle(); uri = ((char **) uris->elts)[i]; /* Skip blank/empty URIs. */ if (*uri == '\0') { continue; } pconn = proxy_conn_create(p, uri, 0); if (pconn == NULL) { pr_trace_msg(trace_channel, 9, "skipping malformed URL '%s'", uri); continue; } *((const struct proxy_conn **) push_array(pconns)) = pconn; } return pconns; } /* SQL support routines. */ static cmd_rec *reverse_db_sql_cmd_create(pool *parent_pool, unsigned int argc, ...) { pool *cmd_pool = NULL; cmd_rec *cmd = NULL; register unsigned int i = 0; va_list argp; cmd_pool = make_sub_pool(parent_pool); cmd = (cmd_rec *) pcalloc(cmd_pool, sizeof(cmd_rec)); cmd->pool = cmd_pool; cmd->argc = argc; cmd->argv = pcalloc(cmd->pool, argc * sizeof(void *)); /* Hmmm... */ cmd->tmp_pool = cmd->pool; va_start(argp, argc); for (i = 0; i < argc; i++) { cmd->argv[i] = va_arg(argp, char *); } va_end(argp); return cmd; } static const char *reverse_db_sql_quote_str(pool *p, char *str) { size_t len; cmdtable *cmdtab; cmd_rec *cmd; modret_t *res; len = strlen(str); if (len == 0) { return str; } cmdtab = pr_stash_get_symbol2(PR_SYM_HOOK, "sql_escapestr", NULL, NULL, NULL); if (cmdtab == NULL) { return str; } cmd = reverse_db_sql_cmd_create(p, 1, pr_str_strip(p, str)); res = pr_module_call(cmdtab->m, cmdtab->handler, cmd); if (MODRET_ISDECLINED(res) || MODRET_ISERROR(res)) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing 'sql_escapestr'"); return str; } return res->data; } /* Look for any user/group-specific ProxyReverseServers and load them, either * from file or SQL or whatever. Randomly choose from one of those * backends. If no user/group-specific backends are found, use the existing * "global" list. */ static array_header *reverse_db_pername_sql_parse_uris(pool *p, cmdtable *sql_cmdtab, const char *name, int per_user, const char *named_query) { array_header *backends, *results; pool *tmp_pool; cmd_rec *cmd; modret_t *res; tmp_pool = make_sub_pool(p); cmd = reverse_db_sql_cmd_create(tmp_pool, 3, "sql_lookup", named_query, name); res = pr_module_call(sql_cmdtab->m, sql_cmdtab->handler, cmd); if (res == NULL || MODRET_ISERROR(res)) { destroy_pool(tmp_pool); (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error processing SQLNamedQuery '%s'", named_query); errno = EPERM; return NULL; } results = res->data; if (results->nelts == 0) { destroy_pool(tmp_pool); pr_trace_msg(trace_channel, 10, "SQLNamedQuery '%s' returned zero rows for %s '%s'", named_query, per_user ? "user" : "group", name); errno = ENOENT; return NULL; } backends = reverse_db_parse_uris(p, results); destroy_pool(tmp_pool); if (backends != NULL) { if (backends->nelts == 0) { errno = ENOENT; return NULL; } pr_trace_msg(trace_channel, 10, "SQLNamedQuery '%s' returned %d %s for %s '%s'", named_query, backends->nelts, backends->nelts != 1 ? "URLs" : "URL", per_user ? "user" : "group", name); } return backends; } static array_header *reverse_db_pername_backends_by_sql(pool *p, const char *name, int per_user) { config_rec *c; array_header *sql_backends = NULL; const char *quoted_name = NULL; cmdtable *sql_cmdtab; sql_cmdtab = pr_stash_get_symbol2(PR_SYM_HOOK, "sql_lookup", NULL, NULL, NULL); if (sql_cmdtab == NULL) { /* No mod_sql backend loaded; no lookups to do. */ pr_trace_msg(trace_channel, 18, "no 'sql_lookup' symbol found (mod_sql not loaded?), skipping " "%s SQL lookups", per_user ? "per-user" : "per-group"); errno = EPERM; return NULL; } c = find_config(main_server->conf, CONF_PARAM, "ProxyReverseServers", FALSE); while (c != NULL) { const char *named_query, *uri; array_header *backends = NULL; pr_signals_handle(); uri = c->argv[1]; if (uri == NULL) { c = find_config_next(c, c->next, CONF_PARAM, "ProxyReverseServers", FALSE); continue; } if (strncmp(uri, "sql:/", 5) != 0) { c = find_config_next(c, c->next, CONF_PARAM, "ProxyReverseServers", FALSE); continue; } named_query = uri + 5; if (quoted_name == NULL) { quoted_name = reverse_db_sql_quote_str(p, (char *) name); } pr_trace_msg(trace_channel, 17, "loading %s-specific ProxyReverseServers SQLNamedQuery '%s'", per_user ? "user" : "group", named_query); backends = reverse_db_pername_sql_parse_uris(p, sql_cmdtab, quoted_name, per_user, named_query); if (backends == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error reading ProxyReverseServers SQLNamedQuery '%s': %s", named_query, strerror(errno)); c = find_config_next(c, c->next, CONF_PARAM, "ProxyReverseServers", FALSE); continue; } if (backends->nelts == 0) { pr_trace_msg(trace_channel, 3, "no usable URLs found by ProxyReverseServers SQLNamedQuery '%s', " "ignoring", named_query); } else { if (sql_backends == NULL) { sql_backends = backends; } else { array_cat(sql_backends, backends); } } c = find_config_next(c, c->next, CONF_PARAM, "ProxyReverseServers", FALSE); } return sql_backends; } static array_header *reverse_db_pername_backends_by_json(pool *p, const char *name, int per_user) { config_rec *c; array_header *file_backends = NULL; c = find_config(main_server->conf, CONF_PARAM, "ProxyReverseServers", FALSE); while (c != NULL) { const char *path, *uri; int xerrno; array_header *backends = NULL; pr_signals_handle(); uri = c->argv[1]; if (uri == NULL) { c = find_config_next(c, c->next, CONF_PARAM, "ProxyReverseServers", FALSE); continue; } if (per_user) { if (strstr(uri, "%U") == NULL) { c = find_config_next(c, c->next, CONF_PARAM, "ProxyReverseServers", FALSE); continue; } } else { if (strstr(uri, "%g") == NULL) { c = find_config_next(c, c->next, CONF_PARAM, "ProxyReverseServers", FALSE); continue; } } if (strncmp(uri, "file:", 5) != 0) { c = find_config_next(c, c->next, CONF_PARAM, "ProxyReverseServers", FALSE); continue; } if (per_user) { path = sreplace(p, (char *) (uri + 5), "%U", name, NULL); } else { path = sreplace(p, (char *) (uri + 5), "%g", name, NULL); } pr_trace_msg(trace_channel, 17, "loading %s-specific ProxyReverseServers file '%s'", per_user ? "user" : "group", path); PRIVS_ROOT backends = proxy_reverse_json_parse_uris(p, path, 0); xerrno = errno; PRIVS_RELINQUISH if (backends == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error reading ProxyReverseServers file '%s': %s", path, strerror(xerrno)); if (xerrno == ENOENT) { /* No file for this user? We're done looking, then. */ break; } c = find_config_next(c, c->next, CONF_PARAM, "ProxyReverseServers", FALSE); continue; } if (backends->nelts == 0) { pr_trace_msg(trace_channel, 3, "no usable URLs found in ProxyReverseServers file '%s', ignoring", path); } else { if (file_backends == NULL) { file_backends = backends; } else { array_cat(file_backends, backends); } } c = find_config_next(c, c->next, CONF_PARAM, "ProxyReverseServers", FALSE); } return file_backends; } array_header *proxy_reverse_pername_backends(pool *p, const char *name, int per_user) { array_header *file_backends, *sql_backends, *backends = NULL; file_backends = reverse_db_pername_backends_by_json(p, name, per_user); if (file_backends != NULL) { backends = file_backends; } sql_backends = reverse_db_pername_backends_by_sql(p, name, per_user); if (sql_backends != NULL) { if (backends != NULL) { array_cat(backends, sql_backends); } else { backends = sql_backends; } } if (backends == NULL) { if (default_backends == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "no %s servers found for %s '%s', and no global " "ProxyReverseServers configured", per_user ? "PerUser" : "PerGroup", per_user ? "user" : "group", name); errno = ENOENT; return NULL; } pr_trace_msg(trace_channel, 11, "using global ProxyReverseServers list for %s '%s'", per_user ? "user" : "group", name); backends = default_backends; } return backends; } int proxy_reverse_policy_is_sticky(int policy_id) { int sticky = FALSE; switch (policy_id) { case PROXY_REVERSE_CONNECT_POLICY_PER_USER: case PROXY_REVERSE_CONNECT_POLICY_PER_GROUP: case PROXY_REVERSE_CONNECT_POLICY_PER_HOST: sticky = TRUE; break; default: break; } return sticky; } const char *proxy_reverse_policy_name(int policy_id) { const char *name; switch (policy_id) { case PROXY_REVERSE_CONNECT_POLICY_RANDOM: name = "Random"; break; case PROXY_REVERSE_CONNECT_POLICY_ROUND_ROBIN: name = "RoundRobin"; break; case PROXY_REVERSE_CONNECT_POLICY_SHUFFLE: name = "Shuffle"; break; case PROXY_REVERSE_CONNECT_POLICY_PER_USER: name = "PerUser"; break; case PROXY_REVERSE_CONNECT_POLICY_PER_GROUP: name = "PerGroup"; break; case PROXY_REVERSE_CONNECT_POLICY_LEAST_CONNS: name = "LeastConns"; break; case PROXY_REVERSE_CONNECT_POLICY_LEAST_RESPONSE_TIME: name = "LeastResponseTime"; break; case PROXY_REVERSE_CONNECT_POLICY_PER_HOST: name = "PerHost"; break; default: name = "unknown/unsupported"; break; } return name; } static int reverse_connect_index_used(pool *p, unsigned int vhost_id, int idx, long connect_ms) { int res; if (reverse_backends != NULL && reverse_backends->nelts == 1) { return 0; } res = (reverse_ds.policy_update_backend)(p, reverse_ds.dsh, reverse_connect_policy, vhost_id, idx, 1, connect_ms); if (res < 0) { int xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error updating database entry for backend ID %d: %s", idx, strerror(xerrno)); errno = xerrno; return -1; } reverse_backend_updated = TRUE; res = (reverse_ds.policy_used_backend)(p, reverse_ds.dsh, reverse_connect_policy, vhost_id, idx); if (res < 0) { int xerrno = errno; errno = xerrno; return -1; } return 0; } static const struct proxy_conn *get_reverse_server_conn(pool *p, struct proxy_session *proxy_sess, int *backend_id, const void *policy_data) { const struct proxy_conn *pconn; pconn = (reverse_ds.policy_next_backend)(p, reverse_ds.dsh, reverse_connect_policy, main_server->sid, default_backends, policy_data, backend_id); if (pconn == NULL) { int xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error selecting backend server: %s", strerror(xerrno)); errno = xerrno; return NULL; } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "selected backend server '%s'", proxy_conn_get_uri(pconn)); reverse_backend_id = *backend_id; return pconn; } static int reverse_tls_postopen(pool *p, struct proxy_session *proxy_sess, conn_t *server_conn) { int xerrno; if (proxy_netio_postopen(server_conn->instrm) < 0) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "postopen error for backend control connection input stream: %s", strerror(xerrno)); proxy_inet_close(session.pool, server_conn); proxy_sess->backend_ctrl_conn = NULL; pr_response_block(FALSE); /* Note that we explicitly return EINVAL here, to indicate to the calling * code in mod_proxy that it should return e.g. "Login incorrect." */ errno = EINVAL; return -1; } if (proxy_netio_postopen(server_conn->outstrm) < 0) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "postopen error for backend control connection output stream: %s", strerror(xerrno)); proxy_inet_close(session.pool, server_conn); proxy_sess->backend_ctrl_conn = NULL; pr_response_block(FALSE); /* Note that we explicitly return EINVAL here, to indicate to the calling * code in mod_proxy that it should return e.g. "Login incorrect." */ errno = EINVAL; return -1; } return 0; } static int reverse_try_connect(pool *p, struct proxy_session *proxy_sess, const void *connect_data) { int backend_id = -1, uri_tls, use_tls, xerrno = 0; conn_t *server_conn = NULL; pr_response_t *resp = NULL; unsigned int resp_nlines = 0; const struct proxy_conn *pconn; const pr_netaddr_t *dst_addr; array_header *other_addrs = NULL; uint64_t connecting_ms, connected_ms; char port_text[32]; pconn = get_reverse_server_conn(p, proxy_sess, &backend_id, connect_data); if (pconn == NULL) { return -1; } dst_addr = proxy_conn_get_addr(pconn, &other_addrs); proxy_sess->dst_addr = dst_addr; proxy_sess->dst_pconn = pconn; proxy_sess->other_addrs = other_addrs; /* Carefully handle FTP-isms here; we may be proxying SSH instead. * * NOTE: All of these FTP-specific cases should be refactored into the * the FTP client API, e.g. proxy_ftp_ctrl_on_connect(). * * NOTE: Should we consider using the SSH client API to do the version * exchange there? */ if (proxy_sess->use_ftp == TRUE) { if (proxy_tls_using_tls() == PROXY_TLS_ENGINE_MATCH_CLIENT) { proxy_tls_match_client_tls(); } uri_tls = proxy_conn_get_tls(pconn); if (uri_tls == PROXY_TLS_ENGINE_IMPLICIT) { pr_trace_msg(trace_channel, 9, "%s#%u requesting, using implicit FTPS", pr_netaddr_get_ipstr(dst_addr), (unsigned int) ntohs(pr_netaddr_get_port(dst_addr))); proxy_tls_set_tls(uri_tls); } } pr_gettimeofday_millis(&connecting_ms); server_conn = proxy_conn_get_server_conn(p, proxy_sess, dst_addr); if (server_conn == NULL) { xerrno = errno; if (other_addrs != NULL) { register unsigned int i; /* Try the other IP addresses for the configured name (if any) as well. */ for (i = 0; i < other_addrs->nelts; i++) { dst_addr = ((pr_netaddr_t **) other_addrs->elts)[i]; pr_gettimeofday_millis(&connecting_ms); pr_trace_msg(trace_channel, 8, "attempting to connect to other address #%u (%s) for requested " "URI '%.100s'", i+1, pr_netaddr_get_ipstr(dst_addr), proxy_conn_get_uri(proxy_sess->dst_pconn)); server_conn = proxy_conn_get_server_conn(p, proxy_sess, dst_addr); if (server_conn != NULL) { proxy_sess->dst_addr = dst_addr; break; } } } if (server_conn == NULL) { xerrno = errno; /* TODO: Under what errno values will we mark this backend/idx as * "unhealthy"? When we do, how will that unhealthy flag be taken into * account with the existing queries? JOIN the index on the backend table * to get that unhealthy flag? */ if (reverse_connect_index_used(p, main_server->sid, backend_id, -1) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error updating database for backend server index %d: %s", backend_id, strerror(errno)); } } errno = xerrno; return -1; } if (proxy_opts & PROXY_OPT_USE_PROXY_PROTOCOL_V1) { pr_trace_msg(trace_channel, 17, "sending PROXY V1 protocol message to %s#%u", pr_netaddr_get_ipstr(server_conn->remote_addr), ntohs(pr_netaddr_get_port(server_conn->remote_addr))); if (proxy_conn_send_proxy_v1(p, server_conn) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error sending PROXY V1 message to %s#%u: %s", pr_netaddr_get_ipstr(server_conn->remote_addr), ntohs(pr_netaddr_get_port(server_conn->remote_addr)), strerror(errno)); } } else if (proxy_opts & PROXY_OPT_USE_PROXY_PROTOCOL_V2) { pr_trace_msg(trace_channel, 17, "sending PROXY V2 protocol message to %s#%u", pr_netaddr_get_ipstr(server_conn->remote_addr), ntohs(pr_netaddr_get_port(server_conn->remote_addr))); if (proxy_conn_send_proxy_v2(p, server_conn) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error sending PROXY V2 message to %s#%u: %s", pr_netaddr_get_ipstr(server_conn->remote_addr), ntohs(pr_netaddr_get_port(server_conn->remote_addr)), strerror(errno)); } } proxy_sess->frontend_ctrl_conn = session.c; proxy_sess->backend_ctrl_conn = server_conn; if (proxy_sess->use_ftp == TRUE) { use_tls = proxy_tls_using_tls(); /* Handle implicit FTPS connects. */ if (use_tls == PROXY_TLS_ENGINE_IMPLICIT) { if (reverse_tls_postopen(p, proxy_sess, server_conn) < 0) { return -1; } } /* XXX Support/send a CLNT command of our own? Configurable via e.g. * "UserAgent" string? */ resp = proxy_ftp_ctrl_recv_resp(p, server_conn, &resp_nlines, 0); if (resp == NULL) { xerrno = errno; pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to read banner from server %s:%u: %s", pr_netaddr_get_ipstr(server_conn->remote_addr), ntohs(pr_netaddr_get_port(server_conn->remote_addr)), strerror(xerrno)); errno = xerrno; return -1; } else { int banner_ok = TRUE; pr_gettimeofday_millis(&connected_ms); if (resp->num[0] != '2') { banner_ok = FALSE; } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "received banner from backend %s:%u%s: %s %s", pr_netaddr_get_ipstr(server_conn->remote_addr), ntohs(pr_netaddr_get_port(server_conn->remote_addr)), banner_ok ? "" : ", DISCONNECTING", resp->num, resp->msg); if (banner_ok == FALSE) { pr_inet_close(p, server_conn); proxy_sess->backend_ctrl_conn = NULL; errno = EPERM; return -1; } } } else { pr_gettimeofday_millis(&connected_ms); use_tls = PROXY_TLS_ENGINE_OFF; } pr_trace_msg(trace_channel, 8, "connected to backend '%.100s' in %ld ms", proxy_conn_get_uri(proxy_sess->dst_pconn), (long) (connected_ms - connecting_ms)); if (reverse_connect_index_used(p, main_server->sid, backend_id, (long) (connected_ms - connecting_ms)) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error updating database for backend server index %d: %s", backend_id, strerror(errno)); } if (proxy_sess->use_ftp == TRUE) { /* Get the features supported by the backend server. */ if (proxy_ftp_sess_get_feat(p, proxy_sess) < 0) { if (errno != EPERM) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to determine features of backend server: %s", strerror(errno)); } } pr_response_block(TRUE); if (use_tls != PROXY_TLS_ENGINE_OFF && use_tls != PROXY_TLS_ENGINE_IMPLICIT) { if (proxy_ftp_sess_send_auth_tls(p, proxy_sess) < 0 && errno != ENOSYS) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error enabling TLS on control connection to backend server: %s", strerror(xerrno)); pr_inet_close(p, server_conn); proxy_sess->backend_ctrl_conn = NULL; pr_response_block(FALSE); errno = xerrno; return -1; } use_tls = proxy_tls_using_tls(); } if (use_tls != PROXY_TLS_ENGINE_OFF && use_tls != PROXY_TLS_ENGINE_IMPLICIT) { if (reverse_tls_postopen(p, proxy_sess, server_conn) < 0) { return -1; } } if (use_tls != PROXY_TLS_ENGINE_OFF) { if (proxy_sess_state & PROXY_SESS_STATE_BACKEND_HAS_CTRL_TLS) { /* NOTE: should this be a fatal error? */ (void) proxy_ftp_sess_send_pbsz_prot(p, proxy_sess); } } if (reverse_flags == PROXY_REVERSE_FL_CONNECT_AT_SESS_INIT) { pr_response_block(FALSE); } /* Only send the banner if we haven't done so already; the banner * might already have been sent due to e.g. TLS SNI. */ if (pr_table_get(session.notes, "mod_tls.sni", NULL) == NULL) { if (proxy_ftp_ctrl_send_resp(p, session.c, resp, resp_nlines) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to send banner to client: %s", strerror(errno)); } } if (reverse_flags == PROXY_REVERSE_FL_CONNECT_AT_SESS_INIT) { pr_response_block(TRUE); } (void) proxy_ftp_sess_send_host(p, proxy_sess); } /* Populate the session notes about this connection. */ memset(port_text, '\0', sizeof(port_text)); pr_snprintf(port_text, sizeof(port_text)-1, "%d", proxy_conn_get_port(proxy_sess->dst_pconn)); (void) pr_table_add_dup(session.notes, "mod_proxy.backend-ip", pr_netaddr_get_ipstr(dst_addr), 0); (void) pr_table_remove(session.notes, "mod_proxy.backend-port", NULL); (void) pr_table_add_dup(session.notes, "mod_proxy.backend-port", port_text, 0); (void) pr_table_add_dup(session.notes, "mod_proxy.backend-url", proxy_conn_get_uri(proxy_sess->dst_pconn), 0); proxy_sess_state |= PROXY_SESS_STATE_CONNECTED; return 0; } int proxy_reverse_connect(pool *p, struct proxy_session *proxy_sess, const void *connect_data) { register int i; int res; if (p == NULL || proxy_sess == NULL) { errno = EINVAL; return -1; } for (i = 0; i < reverse_retry_count; i++) { pr_signals_handle(); res = reverse_try_connect(p, proxy_sess, connect_data); if (res == 0) { return 0; } } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "ProxyRetryCount %d reached with no successful connection, failing", reverse_retry_count); errno = EPERM; return -1; } int proxy_reverse_use_proxy_auth(void) { if (proxy_opts & PROXY_OPT_USE_REVERSE_PROXY_AUTH) { return TRUE; } return FALSE; } int proxy_reverse_init(pool *p, const char *tables_dir, int flags) { const char *ds_name = "(unknown/unsupported)"; int res, xerrno; void *dsh = NULL; server_rec *s = NULL; if (p == NULL) { errno = EINVAL; return -1; } memset(&reverse_ds, 0, sizeof(reverse_ds)); reverse_ds.backend_id = -1; switch (proxy_datastore) { case PROXY_DATASTORE_SQLITE: ds_name = "SQLite"; res = proxy_reverse_db_as_datastore(&reverse_ds, proxy_datastore_data, proxy_datastore_datasz); xerrno = errno; break; case PROXY_DATASTORE_REDIS: ds_name = "Redis"; res = proxy_reverse_redis_as_datastore(&reverse_ds, proxy_datastore_data, proxy_datastore_datasz); xerrno = errno; break; default: res = -1; xerrno = errno = EINVAL; break; } if (res < 0) { return -1; } dsh = (reverse_ds.init)(p, tables_dir, flags); if (dsh == NULL) { pr_log_pri(PR_LOG_NOTICE, MOD_PROXY_VERSION ": failed to initialize %s datastore: %s", ds_name, strerror(xerrno)); errno = xerrno; return -1; } for (s = (server_rec *) server_list->xas_list; s; s = s->next) { config_rec *c; array_header *backends = NULL; int connect_policy = reverse_connect_policy; unsigned long opts = 0UL; c = find_config(s->conf, CONF_PARAM, "ProxyReverseServers", FALSE); while (c != NULL) { const char *uri; pr_signals_handle(); uri = c->argv[1]; if (uri != NULL) { int defer = FALSE; /* Handling of sql:// URIs is done later, in the session init * call, assuming we've connected to a SQL server. */ if (strncmp(uri, "sql:/", 5) == 0) { defer = TRUE; } /* Skip any %U- or %g-bearing URIs. */ if (defer == FALSE && (strstr(uri, "%U") != NULL || strstr(uri, "%g") != NULL)) { defer = TRUE; } if (defer) { c = find_config_next(c, c->next, CONF_PARAM, "ProxyReverseServers", FALSE); continue; } } if (backends == NULL) { backends = c->argv[0]; } else { array_cat(backends, c->argv[0]); } c = find_config_next(c, c->next, CONF_PARAM, "ProxyReverseServers", FALSE); } c = find_config(s->conf, CONF_PARAM, "ProxyReverseConnectPolicy", FALSE); if (c != NULL) { connect_policy = *((int *) c->argv[0]); } c = find_config(s->conf, CONF_PARAM, "ProxyOptions", FALSE); while (c != NULL) { unsigned long o; pr_signals_handle(); o = *((unsigned long *) c->argv[0]); opts |= o; c = find_config_next(c, c->next, CONF_PARAM, "ProxyOptions", FALSE); } res = (reverse_ds.policy_init)(p, dsh, connect_policy, s->sid, backends, opts); if (res < 0) { xerrno = errno; break; } } (void) (reverse_ds.close)(p, dsh); if (res < 0) { errno = xerrno; return -1; } return 0; } int proxy_reverse_free(pool *p) { if (p == NULL) { errno = EINVAL; return -1; } /* TODO: Implement any necessary cleanup */ if (reverse_ds.dsh != NULL) { (void) (reverse_ds.close)(p, reverse_ds.dsh); reverse_ds.dsh = NULL; } return 0; } int proxy_reverse_sess_exit(pool *p) { if (reverse_backends != NULL && reverse_backend_id >= 0) { if (reverse_backend_updated == TRUE) { int res; res = (reverse_ds.policy_update_backend)(p, reverse_ds.dsh, reverse_connect_policy, main_server->sid, reverse_ds.backend_id, -1, -1); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error updating backend ID %d: %s", reverse_ds.backend_id, strerror(errno)); } } } return 0; } static int set_reverse_flags(void) { if (proxy_opts & PROXY_OPT_USE_REVERSE_PROXY_AUTH) { reverse_flags = PROXY_REVERSE_FL_CONNECT_AT_PASS; } else { if (reverse_connect_policy == PROXY_REVERSE_CONNECT_POLICY_PER_USER) { reverse_flags = PROXY_REVERSE_FL_CONNECT_AT_USER; } else if (reverse_connect_policy == PROXY_REVERSE_CONNECT_POLICY_PER_GROUP) { /* Incompatible configuration: PerGroup balancing requires that the USER * name be authenticated, in order to discovery the primary group name. */ (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "ReverseProxyConnectPolicy PerGroup requires the UseReverseProxyAuth ProxyOption, rejecting connection due to incompatible configuration"); pr_log_pri(PR_LOG_NOTICE, MOD_PROXY_VERSION ": ReverseProxyConnectPolicy PerGroup requires the UseReverseProxyAuth ProxyOption, rejecting connection due to incompatible configuration"); errno = EINVAL; return -1; } else { reverse_flags = PROXY_REVERSE_FL_CONNECT_AT_SESS_INIT; } } return 0; } int proxy_reverse_sess_free(pool *p, struct proxy_session *proxy_sess) { /* Reset any state. */ reverse_backends = NULL; reverse_backend_id = -1; reverse_connect_policy = PROXY_REVERSE_CONNECT_POLICY_ROUND_ROBIN; reverse_flags = 0UL; reverse_retry_count = PROXY_DEFAULT_RETRY_COUNT; if (reverse_ds.dsh != NULL) { (void) (reverse_ds.close)(p, reverse_ds.dsh); reverse_ds.dsh = NULL; } return 0; } int proxy_reverse_sess_init(pool *p, const char *tables_dir, struct proxy_session *proxy_sess, int flags) { int res; config_rec *c; void *dsh; if (p == NULL) { errno = EINVAL; return - 1; } c = find_config(main_server->conf, CONF_PARAM, "ProxyRetryCount", FALSE); if (c != NULL) { reverse_retry_count = *((int *) c->argv[0]); } c = find_config(main_server->conf, CONF_PARAM, "ProxyReverseServers", FALSE); if (c == NULL) { pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "'ProxyRole reverse' in effect, but no ProxyReverseServers configured"); pr_log_pri(PR_LOG_NOTICE, MOD_PROXY_VERSION ": 'ProxyRole reverse' in effect, but no ProxyReverseServers configured"); errno = EPERM; return -1; } /* We need to find the ProxyReverseServers that are NOT user/group-specific. */ while (c != NULL) { const char *uri; pr_signals_handle(); uri = c->argv[1]; if (uri == NULL) { if (default_backends == NULL) { default_backends = c->argv[0]; } else { array_cat(default_backends, c->argv[0]); } break; } c = find_config_next(c, c->next, CONF_PARAM, "ProxyReverseServers", FALSE); } c = find_config(main_server->conf, CONF_PARAM, "ProxyReverseConnectPolicy", FALSE); if (c != NULL) { reverse_connect_policy = *((int *) c->argv[0]); } dsh = (reverse_ds.open)(p, tables_dir, default_backends); if (dsh == NULL) { return -1; } reverse_ds.dsh = dsh; if (set_reverse_flags() < 0) { return -1; } if (reverse_flags == PROXY_REVERSE_FL_CONNECT_AT_SESS_INIT) { res = proxy_reverse_connect(p, proxy_sess, NULL); if (res < 0) { return -1; } } return 0; } int proxy_reverse_have_authenticated(cmd_rec *cmd) { int authd = FALSE; /* Authenticated here means authenticated *to the proxy*, i.e. should we * allow more commands, or reject them because the client hasn't authenticated * yet. */ if (proxy_sess_state & PROXY_SESS_STATE_BACKEND_AUTHENTICATED) { authd = TRUE; } if (authd == FALSE) { pr_response_send(R_530, _("Please login with USER and PASS")); } return authd; } static pr_json_array_t *read_json_array(pool *p, pr_fh_t *fh, off_t filesz) { pr_json_array_t *json = NULL; char *buf, *ptr; int res; off_t len; len = filesz; buf = ptr = palloc(p, len+1); buf[len] = '\0'; res = pr_fsio_read(fh, buf, len); while (res != len) { if (res < 0) { if (errno == EINTR) { pr_signals_handle(); res = pr_fsio_read(fh, buf, len); continue; } return NULL; } if (res == 0) { /* EOF, but we shouldn't reach this. */ pr_trace_msg(trace_channel, 14, "unexpectedly reached EOF when reading '%s'", fh->fh_path); errno = EOF; return NULL; } /* Paranoia, paranoia...*/ if (len > res) { errno = EIO; return NULL; } /* Short read: advance the buffer, decrement the length, and read more. */ buf += res; len -= res; pr_signals_handle(); res = pr_fsio_read(fh, buf, len); } json = pr_json_array_from_text(p, ptr); if (json == NULL) { pr_trace_msg(trace_channel, 3, "invalid JSON format found in '%s'", fh->fh_path); errno = EINVAL; return NULL; } return json; } array_header *proxy_reverse_json_parse_uris(pool *p, const char *path, unsigned int flags) { register unsigned int i, nelts; int count = 0, reached_eol = TRUE, res, xerrno = 0; pr_fh_t *fh; array_header *uris = NULL; struct stat st; pool *tmp_pool; pr_json_array_t *json = NULL; if (p == NULL || path == NULL) { errno = EINVAL; return NULL; } if (*path != '/') { /* A relative path? Unacceptable. */ errno = EINVAL; return NULL; } res = check_file_perms(p, path); if (res < 0) { return NULL; } /* Use a nonblocking open() for the path; it could be a FIFO, and we don't * want to block forever if the other end of the FIFO is not running. */ fh = pr_fsio_open(path, O_RDONLY|O_NONBLOCK); if (fh == NULL) { xerrno = errno; pr_trace_msg(trace_channel, 7, "error opening ProxyReverseServers file '%s': %s", path, strerror(xerrno)); errno = xerrno; return NULL; } pr_fsio_set_block(fh); /* Stat the file to find the optimal buffer size for reading. */ res = pr_fsio_fstat(fh, &st); if (res < 0) { xerrno = errno; pr_trace_msg(trace_channel, 3, "unable to fstat '%s': %s", path, strerror(xerrno)); (void) pr_fsio_close(fh); errno = xerrno; return NULL; } if (st.st_size == 0) { /* Return an empty array for this empty file. */ pr_trace_msg(trace_channel, 15, "found no items in empty file '%s'", fh->fh_path); (void) pr_fsio_close(fh); uris = make_array(p, 1, sizeof(struct proxy_conn *)); return uris; } if (st.st_size > PROXY_REVERSE_JSON_MAX_FILE_SIZE) { pr_trace_msg(trace_channel, 1, "'%s' file size (%lu bytes) exceeds max JSON file size (%lu bytes)", path, (unsigned long) st.st_size, (unsigned long) PROXY_REVERSE_JSON_MAX_FILE_SIZE); (void) pr_fsio_close(fh); errno = EPERM; return NULL; } fh->fh_iosz = st.st_blksize; tmp_pool = make_sub_pool(p); json = read_json_array(tmp_pool, fh, st.st_size); xerrno = errno; (void) pr_fsio_close(fh); if (json == NULL) { pr_trace_msg(trace_channel, 1, "unable to read JSON array from '%s': %s", path, strerror(xerrno)); destroy_pool(tmp_pool); errno = xerrno; return NULL; } count = pr_json_array_count(json); if (count >= 0) { pr_trace_msg(trace_channel, 12, "found items (count %d) in JSON file '%s'", count, path); } uris = make_array(p, 1, sizeof(struct proxy_conn *)); nelts = count; if (nelts > PROXY_REVERSE_JSON_MAX_ITEMS) { nelts = PROXY_REVERSE_JSON_MAX_ITEMS; reached_eol = FALSE; } for (i = 0; i < nelts; i++) { char *uri = NULL; const struct proxy_conn *pconn; pr_signals_handle(); if (pr_json_array_get_string(p, json, i, &uri) == 0) { pconn = proxy_conn_create(p, uri, flags); if (pconn == NULL) { pr_trace_msg(trace_channel, 9, "skipping malformed URL '%s' found in file '%s'", uri, path); continue; } *((const struct proxy_conn **) push_array(uris)) = pconn; } else { pr_trace_msg(trace_channel, 2, "error getting string from JSON array at index %u: %s", i, strerror(errno)); } } (void) pr_json_array_free(json); destroy_pool(tmp_pool); if (reached_eol == FALSE) { pr_trace_msg(trace_channel, 3, "warning: skipped ProxyReverseServers '%s' data (only used " "first %u items)", path, i); } pr_trace_msg(trace_channel, 12, "created URIs (count %u) from JSON file '%s'", uris->nelts, path); return uris; } int proxy_reverse_get_connect_policy(void) { return reverse_connect_policy; } int proxy_reverse_connect_get_policy_id(const char *policy) { if (policy == NULL) { errno = EINVAL; return -1; } if (strcasecmp(policy, "Random") == 0) { return PROXY_REVERSE_CONNECT_POLICY_RANDOM; } else if (strcasecmp(policy, "RoundRobin") == 0) { return PROXY_REVERSE_CONNECT_POLICY_ROUND_ROBIN; } else if (strcasecmp(policy, "Shuffle") == 0) { return PROXY_REVERSE_CONNECT_POLICY_SHUFFLE; } else if (strcasecmp(policy, "LeastConns") == 0) { return PROXY_REVERSE_CONNECT_POLICY_LEAST_CONNS; } else if (strcasecmp(policy, "PerUser") == 0) { return PROXY_REVERSE_CONNECT_POLICY_PER_USER; } else if (strcasecmp(policy, "PerGroup") == 0) { return PROXY_REVERSE_CONNECT_POLICY_PER_GROUP; } else if (strcasecmp(policy, "PerHost") == 0) { return PROXY_REVERSE_CONNECT_POLICY_PER_HOST; } else if (strcasecmp(policy, "LeastResponseTime") == 0) { return PROXY_REVERSE_CONNECT_POLICY_LEAST_RESPONSE_TIME; } errno = ENOENT; return -1; } static int send_user(struct proxy_session *proxy_sess, cmd_rec *cmd, int *successful) { int res, xerrno; pr_response_t *resp; unsigned int resp_nlines = 0; char *orig_user; const char *uri_user; orig_user = cmd->arg; uri_user = proxy_conn_get_username(proxy_sess->dst_pconn); if (uri_user != NULL) { /* We have URI-specific USER name to use, instead of the client-provided * one. */ pr_trace_msg(trace_channel, 18, "using URI-specific username '%s' instead of client-provided '%s'", uri_user, orig_user); cmd->argv[1] = cmd->arg = pstrdup(cmd->pool, uri_user); } res = proxy_ftp_ctrl_send_cmd(cmd->tmp_pool, proxy_sess->backend_ctrl_conn, cmd); cmd->argv[1] = cmd->arg = orig_user; if (res < 0) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error sending %s to backend: %s", (char *) cmd->argv[0], strerror(xerrno)); errno = xerrno; return -1; } resp = proxy_ftp_ctrl_recv_resp(cmd->tmp_pool, proxy_sess->backend_ctrl_conn, &resp_nlines, 0); if (resp == NULL) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error receiving %s response from backend: %s", (char *) cmd->argv[0], strerror(xerrno)); errno = xerrno; return -1; } /* Note that the response message may contain the per-URI user name we * sent; be sure to preserve the illusion, and re-write the response as * necessary. */ if (uri_user != NULL) { /* TODO: handle the case where there are multiple response lines. */ if (strstr(resp->msg, uri_user) != NULL) { resp->msg = sreplace(cmd->pool, resp->msg, uri_user, orig_user, NULL); } } if (resp->num[0] == '2' || resp->num[0] == '3') { *successful = TRUE; if (strcmp(resp->num, R_232) == 0) { proxy_sess_state |= PROXY_SESS_STATE_BACKEND_AUTHENTICATED; clear_user_creds(); pr_timer_remove(PR_TIMER_LOGIN, ANY_MODULE); } } res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool, proxy_sess->frontend_ctrl_conn, resp, resp_nlines); if (res < 0) { xerrno = errno; pr_response_block(TRUE); errno = xerrno; return -1; } return 0; } int proxy_reverse_handle_user(cmd_rec *cmd, struct proxy_session *proxy_sess, int *successful, int *block_responses) { int res; if (reverse_flags == PROXY_REVERSE_FL_CONNECT_AT_PASS) { /* If we're already connected, then proxy this USER command through to the * backend, otherwise we let the proftpd internals deal with it locally, * leading to proxy auth. */ if (!(proxy_sess_state & PROXY_SESS_STATE_PROXY_AUTHENTICATED)) { *block_responses = FALSE; return 0; } } if (reverse_flags == PROXY_REVERSE_FL_CONNECT_AT_USER) { int connected = FALSE, xerrno = 0; res = proxy_reverse_connect(proxy_pool, proxy_sess, cmd->arg); xerrno = errno; if (res == 0) { connected = TRUE; } pr_response_block(FALSE); if (connected == FALSE) { *successful = FALSE; if (xerrno != EINVAL) { errno = EPERM; } else { errno = xerrno; } return -1; } } res = send_user(proxy_sess, cmd, successful); if (res < 0) { return -1; } if (reverse_flags == PROXY_REVERSE_FL_CONNECT_AT_USER) { /* Restore the normal response blocking, undone for the PerUser policy. */ pr_response_block(TRUE); } return 1; } static int send_pass(struct proxy_session *proxy_sess, cmd_rec *cmd, int *successful) { int res, xerrno; pr_response_t *resp; unsigned int resp_nlines = 0; const char *uri_user, *uri_pass; char *orig_pass; if (proxy_sess == NULL || proxy_sess->backend_ctrl_conn == NULL) { pr_trace_msg(trace_channel, 4, "unable to send PASS to backend server: No backend control connection"); errno = EPERM; return -1; } orig_pass = cmd->arg; uri_user = proxy_conn_get_username(proxy_sess->dst_pconn); uri_pass = proxy_conn_get_password(proxy_sess->dst_pconn); if (uri_pass != NULL) { size_t uri_passlen; uri_passlen = strlen(uri_pass); if (uri_passlen > 0) { /* We have URI-specific password to use, instead of the client-provided * one. */ pr_trace_msg(trace_channel, 18, "using URI-specific password instead of client-provided one"); cmd->argv[1] = cmd->arg = pstrdup(cmd->pool, uri_pass); } } res = proxy_ftp_ctrl_send_cmd(cmd->tmp_pool, proxy_sess->backend_ctrl_conn, cmd); cmd->argv[1] = cmd->arg = orig_pass; if (res < 0) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error sending %s to backend: %s", (char *) cmd->argv[0], strerror(xerrno)); errno = xerrno; return -1; } resp = proxy_ftp_ctrl_recv_resp(cmd->tmp_pool, proxy_sess->backend_ctrl_conn, &resp_nlines, 0); if (resp == NULL) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error receiving %s response from backend: %s", (char *) cmd->argv[0], strerror(xerrno)); /* If we receive an EPERM here, it is probably because the backend * closed its control connection, yielding an EOF. To better indicate * this situation, propagate the error using EPIPE. */ if (xerrno == EPERM) { xerrno = EPIPE; } errno = xerrno; return -1; } /* Note that the response message may contain the per-URI user name we * sent; be sure to preserve the illusion, and re-write the response as * necessary. */ if (uri_user != NULL) { const char *orig_user; orig_user = pr_table_get(session.notes, "mod_auth.orig-user", NULL); if (orig_user != NULL) { /* TODO: handle the case where there are multiple response lines. */ if (strstr(resp->msg, uri_user) != NULL) { resp->msg = sreplace(cmd->pool, resp->msg, uri_user, orig_user, NULL); } } } /* XXX What about other response codes for PASS? */ if (resp->num[0] == '2') { *successful = TRUE; proxy_sess_state |= PROXY_SESS_STATE_BACKEND_AUTHENTICATED; clear_user_creds(); pr_timer_remove(PR_TIMER_LOGIN, ANY_MODULE); } res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool, proxy_sess->frontend_ctrl_conn, resp, resp_nlines); if (res < 0) { xerrno = errno; pr_response_block(TRUE); errno = xerrno; return -1; } return 0; } int proxy_reverse_handle_pass(cmd_rec *cmd, struct proxy_session *proxy_sess, int *successful, int *block_responses) { int res, xerrno = 0; /* This CONNECT_AT_PASS flag indicates that we are using proxy auth when * reverse proxying. */ if (reverse_flags == PROXY_REVERSE_FL_CONNECT_AT_PASS) { if (!(proxy_sess_state & PROXY_SESS_STATE_PROXY_AUTHENTICATED)) { const char *user = NULL; user = pr_table_get(session.notes, "mod_auth.orig-user", NULL); res = proxy_session_check_password(cmd->pool, user, cmd->arg); if (res < 0) { errno = EINVAL; return -1; } res = proxy_session_setup_env(proxy_pool, user, PROXY_SESSION_FL_CHECK_LOGIN_ACL); if (res < 0) { errno = EINVAL; return -1; } if (session.auth_mech != NULL) { pr_log_debug(DEBUG2, "user '%s' authenticated by %s", user, session.auth_mech); } } if (!(proxy_sess_state & PROXY_SESS_STATE_CONNECTED)) { int connected = FALSE; const char *user = NULL, *connect_name = NULL; cmd_rec *user_cmd; /* If we're using a sticky policy, we need to know the USER name that was * sent. */ if (proxy_reverse_policy_is_sticky(reverse_connect_policy) == TRUE) { user = connect_name = pr_table_get(session.notes, "mod_auth.orig-user", NULL); /* If the sticky policy in question is PerGroup, then we also need * to know the authenticated user's primary group name. */ if (proxy_sess_state & PROXY_SESS_STATE_PROXY_AUTHENTICATED) { if (reverse_connect_policy == PROXY_REVERSE_CONNECT_POLICY_PER_GROUP) { connect_name = session.group; } } } res = proxy_reverse_connect(proxy_pool, proxy_sess, connect_name); xerrno = errno; if (res == 0) { connected = TRUE; } pr_response_block(FALSE); if (connected == FALSE) { *successful = FALSE; if (xerrno != EINVAL) { errno = EPERM; } else { errno = xerrno; } return -1; } if (user == NULL) { user = pr_table_get(session.notes, "mod_auth.orig-user", NULL); } user_cmd = pr_cmd_alloc(cmd->tmp_pool, 2, C_USER, user); user_cmd->arg = pstrdup(cmd->tmp_pool, user); /* Since we're replaying the USER command here, we want to make sure * that the USER response from the backend isn't played back to the * frontend client. */ pr_response_block(TRUE); res = send_user(proxy_sess, user_cmd, successful); xerrno = errno; pr_response_block(FALSE); if (res < 0) { errno = xerrno; return -1; } } } res = send_pass(proxy_sess, cmd, successful); if (res < 0) { return -1; } if (reverse_flags != PROXY_REVERSE_FL_CONNECT_AT_PASS && *successful == TRUE) { const char *user = NULL; /* If we're not using proxy auth, still make sure that everything is * set up properly. */ user = pr_table_get(session.notes, "mod_auth.orig-user", NULL); res = proxy_session_setup_env(proxy_pool, user, 0); if (res < 0) { errno = EINVAL; return -1; } } return 1; } proftpd-mod_proxy-0.9.5/lib/proxy/reverse/000077500000000000000000000000001475737016700206445ustar00rootroot00000000000000proftpd-mod_proxy-0.9.5/lib/proxy/reverse/db.c000066400000000000000000001554431475737016700214110ustar00rootroot00000000000000/* * ProFTPD - mod_proxy reverse datastore implementation * Copyright (c) 2012-2025 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "proxy/db.h" #include "proxy/conn.h" #include "proxy/reverse.h" #include "proxy/reverse/db.h" #include "proxy/random.h" #include "proxy/tls.h" #include "proxy/ftp/ctrl.h" #include extern xaset_t *server_list; #define PROXY_REVERSE_DB_SCHEMA_NAME "proxy_reverse" #define PROXY_REVERSE_DB_SCHEMA_VERSION 6 /* PerHost/PerUser/PerGroup table limits */ #define PROXY_REVERSE_DB_PERHOST_MAX_ENTRIES 8192 #define PROXY_REVERSE_DB_PERUSER_MAX_ENTRIES 8192 #define PROXY_REVERSE_DB_PERGROUP_MAX_ENTRIES 8192 static array_header *db_backends = NULL; static const char *trace_channel = "proxy.reverse.db"; static unsigned int str2hash(const void *key, size_t keysz) { unsigned int i = 0; size_t sz = !keysz ? strlen((const char *) key) : keysz; while (sz--) { const char *k = key; unsigned int c; pr_signals_handle(); c = k[sz]; i = (i * 33) + c; } return i; } static int reverse_db_add_schema(pool *p, struct proxy_dbh *dbh, const char *db_path) { int res; const char *stmt, *errstr = NULL; /* CREATE TABLE proxy_vhosts ( * vhost_id INTEGER NOT NULL PRIMARY KEY, * vhost_name TEXT NOT NULL * ); */ stmt = "CREATE TABLE IF NOT EXISTS proxy_vhosts (vhost_id INTEGER NOT NULL PRIMARY KEY, vhost_name TEXT NOT NULL);"; res = proxy_db_exec_stmt(p, dbh, stmt, &errstr); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr); errno = EPERM; return -1; } /* TODO: Add these columns: * unhealthy BOOLEAN, * unhealthy_ms BIGINT, * unhealthy_reason TEXT */ /* CREATE TABLE proxy_vhost_backends ( * vhost_id INTEGER NOT NULL, * backend_id INTEGER NOT NULL, * backend_uri TEXT NOT NULL, * conn_count INTEGER NOT NULL, * connect_ms INTEGER * ); * * Note: while it might be tempting to have a FOREIGN KEY constraint on * vhost_id to the proxy_vhosts.vhost_id column, doing so also means that * vhost_id MUST be unique. And there will be vhosts that have MULTIPLE * backend URIs, which would violate that uniqueness constraint. Thus we * create our own separate index on the vhost_id column. */ stmt = "CREATE TABLE IF NOT EXISTS proxy_vhost_backends (vhost_id INTEGER NOT NULL, backend_id INTEGER NOT NULL, backend_uri TEXT NOT NULL, conn_count INTEGER NOT NULL, connect_ms INTEGER);"; res = proxy_db_exec_stmt(p, dbh, stmt, &errstr); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr); errno = EPERM; return -1; } /* CREATE INDEX proxy_vhost_backends_vhost_id_idx */ stmt = "CREATE INDEX IF NOT EXISTS proxy_vhost_backends_vhost_id_idx ON proxy_vhost_backends (vhost_id);"; res = proxy_db_exec_stmt(p, dbh, stmt, &errstr); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr); errno = EPERM; return -1; } /* CREATE TABLE proxy_vhost_reverse_roundrobin ( * vhost_id INTEGER NOT NULL, * current_backend_id INTEGER NOT NULL, * FOREIGN KEY (vhost_id) REFERENCES proxy_vhosts (vhost_id), * FOREIGN KEY (current_backend_id) REFERENCES proxy_vhost_backends (backend_id) * ); */ stmt = "CREATE TABLE IF NOT EXISTS proxy_vhost_reverse_roundrobin (vhost_id INTEGER NOT NULL, current_backend_id INTEGER NOT NULL, FOREIGN KEY (vhost_id) REFERENCES proxy_vhosts (vhost_id), FOREIGN KEY (current_backend_id) REFERENCES proxy_vhost_backends (backend_id));"; res = proxy_db_exec_stmt(p, dbh, stmt, &errstr); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr); errno = EPERM; return -1; } /* CREATE TABLE proxy_vhost_reverse_shuffle ( * vhost_id INTEGER NOT NULL, * avail_backend_id INTEGER NOT NULL, * FOREIGN KEY (vhost_id) REFERENCES proxy_vhosts (vhost_id), * FOREIGN KEY (avail_backend_id) REFERENCES proxy_vhost_backends (backend_id) * ); */ stmt = "CREATE TABLE IF NOT EXISTS proxy_vhost_reverse_shuffle (vhost_id INTEGER NOT NULL, avail_backend_id INTEGER NOT NULL, FOREIGN KEY (vhost_id) REFERENCES proxy_vhosts (vhost_id), FOREIGN KEY (avail_backend_id) REFERENCES proxy_vhost_backends (backend_id));"; res = proxy_db_exec_stmt(p, dbh, stmt, &errstr); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr); errno = EPERM; return -1; } /* CREATE TABLE proxy_vhost_reverse_per_user ( * vhost_id INTEGER NOT NULL, * user_name TEXT NOT NULL, * backend_uri TEXT, * FOREIGN KEY (vhost_id) REFERENCES proxy_vhosts (vhost_id), * UNIQUE (vhost_id, user_name) * ); */ stmt = "CREATE TABLE IF NOT EXISTS proxy_vhost_reverse_per_user (vhost_id INTEGER NOT NULL, user_name TEXT NOT NULL, backend_uri TEXT, FOREIGN KEY (vhost_id) REFERENCES proxy_vhosts (vhost_id), UNIQUE (vhost_id, user_name));"; res = proxy_db_exec_stmt(p, dbh, stmt, &errstr); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr); errno = EPERM; return -1; } /* CREATE INDEX proxy_vhost_reverse_per_user_name_idx */ stmt = "CREATE INDEX IF NOT EXISTS proxy_vhost_reverse_per_user_name_idx ON proxy_vhost_reverse_per_user (user_name);"; res = proxy_db_exec_stmt(p, dbh, stmt, &errstr); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr); errno = EPERM; return -1; } /* CREATE TABLE proxy_vhost_reverse_per_group ( * vhost_id INTEGER NOT NULL, * group_name TEXT NOT NULL, * backend_uri TEXT, * FOREIGN KEY (vhost_id) REFERENCES proxy_vhosts (vhost_id), * UNIQUE (vhost_id, group_name) * ); */ stmt = "CREATE TABLE IF NOT EXISTS proxy_vhost_reverse_per_group (vhost_id INTEGER NOT NULL, group_name TEXT NOT NULL, backend_uri TEXT, FOREIGN KEY (vhost_id) REFERENCES proxy_vhosts (vhost_id), UNIQUE (vhost_id, group_name));"; res = proxy_db_exec_stmt(p, dbh, stmt, &errstr); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr); errno = EPERM; return -1; } /* CREATE INDEX proxy_vhost_reverse_per_group_name_idx */ stmt = "CREATE INDEX IF NOT EXISTS proxy_vhost_reverse_per_group_name_idx ON proxy_vhost_reverse_per_group (group_name);"; res = proxy_db_exec_stmt(p, dbh, stmt, &errstr); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr); errno = EPERM; return -1; } /* CREATE TABLE proxy_vhost_reverse_per_host ( * vhost_id INTEGER NOT NULL, * ip_addr TEXT NOT NULL, * backend_uri TEXT, * FOREIGN KEY (vhost_id) REFERENCES proxy_vhosts (vhost_id), * UNIQUE (vhost_id, ip_addr) * ); */ stmt = "CREATE TABLE IF NOT EXISTS proxy_vhost_reverse_per_host (vhost_id INTEGER NOT NULL, ip_addr TEXT NOT NULL, backend_uri TEXT, FOREIGN KEY (vhost_id) REFERENCES proxy_vhosts (vhost_id), UNIQUE (vhost_id, ip_addr));"; res = proxy_db_exec_stmt(p, dbh, stmt, &errstr); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr); errno = EPERM; return -1; } /* CREATE INDEX proxy_vhost_reverse_per_host_ipaddr_idx */ stmt = "CREATE INDEX IF NOT EXISTS proxy_vhost_reverse_per_host_ipaddr_idx ON proxy_vhost_reverse_per_host (ip_addr);"; res = proxy_db_exec_stmt(p, dbh, stmt, &errstr); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr); errno = EPERM; return -1; } return 0; } static int reverse_db_truncate_tables(pool *p, struct proxy_dbh *dbh) { int res; const char *index_name, *stmt, *errstr = NULL; stmt = "DELETE FROM proxy_vhosts;"; res = proxy_db_exec_stmt(p, dbh, stmt, &errstr); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr); errno = EPERM; return -1; } stmt = "DELETE FROM proxy_vhost_backends;"; res = proxy_db_exec_stmt(p, dbh, stmt, &errstr); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr); errno = EPERM; return -1; } stmt = "DELETE FROM proxy_vhost_reverse_roundrobin;"; res = proxy_db_exec_stmt(p, dbh, stmt, &errstr); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr); errno = EPERM; return -1; } stmt = "DELETE FROM proxy_vhost_reverse_shuffle;"; res = proxy_db_exec_stmt(p, dbh, stmt, &errstr); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr); errno = EPERM; return -1; } stmt = "DELETE FROM proxy_vhost_reverse_per_user;"; res = proxy_db_exec_stmt(p, dbh, stmt, &errstr); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr); errno = EPERM; return -1; } stmt = "DELETE FROM proxy_vhost_reverse_per_group;"; res = proxy_db_exec_stmt(p, dbh, stmt, &errstr); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr); errno = EPERM; return -1; } stmt = "DELETE FROM proxy_vhost_reverse_per_host;"; res = proxy_db_exec_stmt(p, dbh, stmt, &errstr); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr); errno = EPERM; return -1; } /* Note: don't forget to rebuild the indices, too! */ index_name = "proxy_vhost_backends_vhost_id_idx"; res = proxy_db_reindex(p, dbh, index_name, &errstr); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error reindexing '%s': %s", index_name, errstr); errno = EPERM; return -1; } index_name = "proxy_vhost_reverse_per_user_name_idx"; res = proxy_db_reindex(p, dbh, index_name, &errstr); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error reindexing '%s': %s", index_name, errstr); errno = EPERM; return -1; } index_name = "proxy_vhost_reverse_per_group_name_idx"; res = proxy_db_reindex(p, dbh, index_name, &errstr); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error reindexing '%s': %s", index_name, errstr); errno = EPERM; return -1; } index_name = "proxy_vhost_reverse_per_host_ipaddr_idx"; res = proxy_db_reindex(p, dbh, index_name, &errstr); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error reindexing '%s': %s", index_name, errstr); errno = EPERM; return -1; } return 0; } static int reverse_db_add_vhost(pool *p, struct proxy_dbh *dbh, server_rec *s) { int res, xerrno = 0; const char *stmt, *errstr = NULL; array_header *results; stmt = "INSERT INTO proxy_vhosts (vhost_id, vhost_name) VALUES (?, ?);"; res = proxy_db_prepare_stmt(p, dbh, stmt); if (res < 0) { xerrno = errno; (void) pr_log_debug(DEBUG3, MOD_PROXY_VERSION ": error preparing statement '%s': %s", stmt, strerror(xerrno)); errno = xerrno; return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT, (void *) &(s->sid), 0); if (res < 0) { return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT, (void *) s->ServerName, -1); if (res < 0) { return -1; } results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr); if (results == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr ? errstr : strerror(errno)); errno = EPERM; return -1; } return 0; } static int reverse_db_add_backend(pool *p, struct proxy_dbh *dbh, unsigned int vhost_id, const char *backend_uri, int backend_id) { int res; const char *stmt, *errstr = NULL; array_header *results; stmt = "INSERT INTO proxy_vhost_backends (vhost_id, backend_uri, backend_id, conn_count) VALUES (?, ?, ?, 0);"; res = proxy_db_prepare_stmt(p, dbh, stmt); if (res < 0) { return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT, (void *) &vhost_id, 0); if (res < 0) { return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT, (void *) backend_uri, -1); if (res < 0) { return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 3, PROXY_DB_BIND_TYPE_INT, (void *) &backend_id, 0); if (res < 0) { return -1; } pr_trace_msg(trace_channel, 13, "adding backend '%.100s' to database table at index %d", backend_uri, backend_id); results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr); if (results == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr ? errstr : strerror(errno)); errno = EPERM; return -1; } return 0; } static int reverse_db_add_backends(pool *p, struct proxy_dbh *dbh, unsigned int vhost_id, array_header *backends) { register unsigned int i; for (i = 0; i < backends->nelts; i++) { int res; struct proxy_conn *pconn; const char *backend_uri; pconn = ((struct proxy_conn **) backends->elts)[i]; backend_uri = proxy_conn_get_uri(pconn); res = reverse_db_add_backend(p, dbh, vhost_id, backend_uri, i); if (res < 0) { int xerrno = errno; pr_trace_msg(trace_channel, 6, "error adding database entry for backend '%.100s': %s", backend_uri, strerror(xerrno)); errno = xerrno; return -1; } pr_trace_msg(trace_channel, 18, "added database entry for backend '%.100s' (ID %u)", backend_uri, i); } return 0; } /* ProxyReverseConnectPolicy: Shuffle */ static int reverse_db_add_shuffle(pool *p, struct proxy_dbh *dbh, unsigned int vhost_id, int backend_id) { int res; const char *stmt, *errstr = NULL; array_header *results; stmt = "INSERT INTO proxy_vhost_reverse_shuffle (vhost_id, avail_backend_id) VALUES (?, ?);"; res = proxy_db_prepare_stmt(p, dbh, stmt); if (res < 0) { return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT, (void *) &vhost_id, 0); if (res < 0) { return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_INT, (void *) &backend_id, 0); if (res < 0) { return -1; } results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr); if (results == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr ? errstr : strerror(errno)); errno = EPERM; return -1; } return 0; } static int reverse_db_shuffle_init(pool *p, struct proxy_dbh *dbh, unsigned int vhost_id, array_header *backends) { register unsigned int i; for (i = 0; i < backends->nelts; i++) { int res; res = reverse_db_add_shuffle(p, dbh, vhost_id, i); if (res < 0) { int xerrno = errno; pr_trace_msg(trace_channel, 6, "error adding shuffle database entry for ID %d: %s", i, strerror(xerrno)); errno = xerrno; return -1; } } return 0; } static int reverse_db_shuffle_next(pool *p, struct proxy_dbh *dbh, unsigned int vhost_id) { int backend_id = -1, res; const char *stmt, *errstr = NULL; array_header *results; unsigned int nrows = 0; stmt = "SELECT COUNT(*) FROM proxy_vhost_reverse_shuffle WHERE vhost_id = ?;"; res = proxy_db_prepare_stmt(p, dbh, stmt); if (res < 0) { return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT, (void *) &vhost_id, 0); if (res < 0) { return -1; } results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr); if (results == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr ? errstr : strerror(errno)); errno = EPERM; return -1; } if (results->nelts != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "expected 1 result from statement '%s', got %d", stmt, results->nelts); errno = EINVAL; return -1; } nrows = atoi(((char **) results->elts)[0]); if (nrows == 0) { res = reverse_db_shuffle_init(p, dbh, vhost_id, db_backends); if (res < 0) { return -1; } nrows = db_backends->nelts; } backend_id = (int) proxy_random_next(0, nrows-1); return backend_id; } static int reverse_db_shuffle_used(pool *p, struct proxy_dbh *dbh, unsigned int vhost_id, int backend_id) { int res, xerrno = 0; const char *stmt, *errstr = NULL; array_header *results; stmt = "DELETE FROM proxy_vhost_reverse_shuffle WHERE vhost_id = ? AND avail_backend_id = ?;"; res = proxy_db_prepare_stmt(p, dbh, stmt); if (res < 0) { xerrno = errno; (void) pr_log_debug(DEBUG3, MOD_PROXY_VERSION ": error preparing statement '%s': %s", stmt, strerror(xerrno)); errno = xerrno; return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT, (void *) &vhost_id, 0); if (res < 0) { return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_INT, (void *) &backend_id, 0); if (res < 0) { return -1; } results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr); if (results == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr ? errstr : strerror(errno)); errno = EPERM; return -1; } return 0; } /* ProxyReverseConnectPolicy: RoundRobin */ static int reverse_db_roundrobin_update(pool *p, struct proxy_dbh *dbh, unsigned int vhost_id, int backend_id) { int res; const char *stmt, *errstr = NULL; array_header *results; stmt = "UPDATE proxy_vhost_reverse_roundrobin SET current_backend_id = ? WHERE vhost_id = ?;"; res = proxy_db_prepare_stmt(p, dbh, stmt); if (res < 0) { return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT, (void *) &backend_id, 0); if (res < 0) { return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_INT, (void *) &vhost_id, 0); if (res < 0) { return -1; } results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr); if (results == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr ? errstr : strerror(errno)); errno = EPERM; return -1; } return 0; } static int reverse_db_roundrobin_init(pool *p, struct proxy_dbh *dbh, unsigned int vhost_id, int backend_id) { int res; const char *stmt, *errstr = NULL; array_header *results; stmt = "INSERT INTO proxy_vhost_reverse_roundrobin (vhost_id, current_backend_id) VALUES (?, ?);"; res = proxy_db_prepare_stmt(p, dbh, stmt); if (res < 0) { return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT, (void *) &vhost_id, 0); if (res < 0) { return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_INT, (void *) &backend_id, 0); if (res < 0) { return -1; } results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr); if (results == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr ? errstr : strerror(errno)); errno = EPERM; return -1; } return 0; } static int reverse_db_roundrobin_next(pool *p, struct proxy_dbh *dbh, unsigned int vhost_id) { int backend_id = 0, res; const char *stmt, *errstr = NULL; array_header *results; stmt = "SELECT current_backend_id FROM proxy_vhost_reverse_roundrobin WHERE vhost_id = ?;"; res = proxy_db_prepare_stmt(p, dbh, stmt); if (res < 0) { return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT, (void *) &vhost_id, 0); if (res < 0) { return -1; } results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr); if (results == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr ? errstr : strerror(errno)); errno = EPERM; return -1; } if (results->nelts != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "expected 1 result from statement '%s', got %d", stmt, results->nelts); errno = EINVAL; return -1; } backend_id = atoi(((char **) results->elts)[0]); /* If the current backend ID is the last one, wrap around to index 0. */ if (backend_id == ((int) db_backends->nelts-1)) { backend_id = 0; } else { backend_id++; } return backend_id; } static int reverse_db_roundrobin_used(pool *p, struct proxy_dbh *dbh, unsigned int vhost_id, int backend_id) { return reverse_db_roundrobin_update(p, dbh, vhost_id, backend_id); } /* ProxyReverseConnectPolicy: LeastConns */ static int reverse_db_leastconns_next(pool *p, struct proxy_dbh *dbh, unsigned int vhost_id) { int backend_id = 0, res; const char *stmt, *errstr = NULL; array_header *results; stmt = "SELECT backend_id FROM proxy_vhost_backends WHERE vhost_id = ? ORDER BY conn_count ASC LIMIT 1;"; res = proxy_db_prepare_stmt(p, dbh, stmt); if (res < 0) { return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT, (void *) &vhost_id, 0); if (res < 0) { return -1; } results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr); if (results == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr ? errstr : strerror(errno)); errno = EPERM; return -1; } if (results->nelts == 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "expected results from statement '%s', got %d", stmt, results->nelts); errno = EINVAL; return -1; } /* Just pick the first index/backend returned. */ backend_id = atoi(((char **) results->elts)[0]); return backend_id; } static int reverse_db_leastconns_used(pool *p, struct proxy_dbh *dbh, unsigned int vhost_id, int backend_id) { /* TODO: anything to do here? */ return 0; } /* ProxyReverseConnectPolicy: LeastResponseTime */ /* Note: "least response time" is determined by calculating the following * for each backend server: * * N = connection count * connect time (ms) * * and choosing the backend with the lowest value for N. If there are no * backend servers with connect time values, choose the one with the lowest * connection count. */ static int reverse_db_leastresponsetime_next(pool *p, struct proxy_dbh *dbh, unsigned int vhost_id) { int backend_id = 0, res; const char *stmt, *errstr = NULL; array_header *results; stmt = "SELECT backend_id FROM proxy_vhost_backends WHERE vhost_id = ? ORDER BY (conn_count * connect_ms) ASC LIMIT 1;"; res = proxy_db_prepare_stmt(p, dbh, stmt); if (res < 0) { return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT, (void *) &vhost_id, 0); if (res < 0) { return -1; } results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr); if (results == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr ? errstr : strerror(errno)); errno = EPERM; return -1; } if (results->nelts == 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "expected results from statement '%s', got %d", stmt, results->nelts); errno = EINVAL; return -1; } /* Just pick the first index/backend returned. */ backend_id = atoi(((char **) results->elts)[0]); return backend_id; } static int reverse_db_leastresponsetime_used(pool *p, struct proxy_dbh *dbh, unsigned int vhost_id, int backend_id) { /* TODO: anything to do here? */ return 0; } /* ProxyReverseConnectPolicy: PerUser */ static array_header *reverse_db_peruser_get(pool *p, struct proxy_dbh *dbh, unsigned int vhost_id, const char *user) { int res; const char *stmt, *errstr = NULL; array_header *results; stmt = "SELECT backend_uri FROM proxy_vhost_reverse_per_user WHERE vhost_id = ? AND user_name = ?;"; res = proxy_db_prepare_stmt(p, dbh, stmt); if (res < 0) { return NULL; } res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT, (void *) &vhost_id, 0); if (res < 0) { return NULL; } res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT, (void *) user, -1); if (res < 0) { return NULL; } results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr); if (results == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr ? errstr : strerror(errno)); errno = EPERM; return NULL; } return results; } static const struct proxy_conn *reverse_db_peruser_init(pool *p, struct proxy_dbh *dbh, unsigned int vhost_id, const char *user) { const struct proxy_conn *pconn = NULL; struct proxy_conn **conns = NULL; int backend_count = 0, res; const char *stmt, *uri, *errstr = NULL; array_header *backends, *results; backends = proxy_reverse_pername_backends(p, user, TRUE); if (backends == NULL) { return NULL; } backend_count = backends->nelts; conns = backends->elts; if (backend_count == 1) { pconn = conns[0]; } else { size_t user_len; unsigned int h; int idx; user_len = strlen(user); h = str2hash(user, user_len); idx = h % backend_count; pconn = conns[idx]; } /* TODO: What happens if the chosen backend URI cannot be used, e.g. * because it is down/unreachable? In reverse_try_connect(), we'll know * that it failed to connect, but how to tunnel that back down here, to * choose another? */ stmt = "INSERT OR IGNORE INTO proxy_vhost_reverse_per_user (vhost_id, user_name, backend_uri) VALUES (?, ?, ?);"; res = proxy_db_prepare_stmt(p, dbh, stmt); if (res < 0) { return NULL; } res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT, (void *) &vhost_id, 0); if (res < 0) { return NULL; } res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT, (void *) user, -1); if (res < 0) { return NULL; } uri = proxy_conn_get_uri(pconn); res = proxy_db_bind_stmt(p, dbh, stmt, 3, PROXY_DB_BIND_TYPE_TEXT, (void *) uri, -1); if (res < 0) { return NULL; } results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr); if (results == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr ? errstr : strerror(errno)); errno = EPERM; return NULL; } return pconn; } static const struct proxy_conn *reverse_db_peruser_next(pool *p, struct proxy_dbh *dbh, unsigned int vhost_id, const char *user) { array_header *results; const struct proxy_conn *pconn = NULL; pconn = reverse_db_peruser_init(p, dbh, vhost_id, user); if (pconn == NULL && errno != ENOENT) { results = reverse_db_peruser_get(p, dbh, vhost_id, user); if (results != NULL && results->nelts > 0) { char **vals; vals = results->elts; pconn = proxy_conn_create(p, vals[0], 0); } } if (pconn != NULL) { return pconn; } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error preparing database for ProxyReverseConnectPolicy PerUser for " "user '%s': %s", user, strerror(ENOENT)); errno = EPERM; return NULL; } static int reverse_db_peruser_used(pool *p, struct proxy_dbh *dbh, unsigned int vhost_id, int backend_id) { int count, res; const char *stmt, *errstr = NULL; array_header *results; /* To prevent database bloating too much, delete all of the entries * in the table if we're over our limit. */ stmt = "SELECT COUNT(*) FROM proxy_vhost_reverse_per_user;"; res = proxy_db_prepare_stmt(p, dbh, stmt); if (res < 0) { return -1; } results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr); if (results == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr ? errstr : strerror(errno)); errno = EPERM; return -1; } if (results->nelts != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "expected 1 result from statement '%s', got %d", stmt, results->nelts); errno = EINVAL; return -1; } count = atoi(((char **) results->elts)[0]); if (count <= PROXY_REVERSE_DB_PERUSER_MAX_ENTRIES) { return 0; } pr_trace_msg(trace_channel, 5, "PerUser entry count (%d) exceeds max (%d), purging", count, PROXY_REVERSE_DB_PERUSER_MAX_ENTRIES); stmt = "DELETE FROM proxy_vhost_reverse_per_user;"; res = proxy_db_prepare_stmt(p, dbh, stmt); if (res < 0) { return -1; } results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr); if (results == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr ? errstr : strerror(errno)); errno = EPERM; return -1; } return 0; } /* ProxyReverseConnectPolicy: PerGroup */ static array_header *reverse_db_pergroup_get(pool *p, struct proxy_dbh *dbh, unsigned int vhost_id, const char *group) { int res; const char *stmt, *errstr = NULL; array_header *results; stmt = "SELECT backend_uri FROM proxy_vhost_reverse_per_group WHERE vhost_id = ? AND group_name = ?;"; res = proxy_db_prepare_stmt(p, dbh, stmt); if (res < 0) { return NULL; } res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT, (void *) &vhost_id, 0); if (res < 0) { return NULL; } res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT, (void *) group, -1); if (res < 0) { return NULL; } results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr); if (results == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr ? errstr : strerror(errno)); errno = EPERM; return NULL; } return results; } static const struct proxy_conn *reverse_db_pergroup_init(pool *p, struct proxy_dbh *dbh, unsigned int vhost_id, const char *group) { const struct proxy_conn *pconn = NULL; struct proxy_conn **conns = NULL; int backend_count = 0, res; const char *stmt, *uri, *errstr = NULL; array_header *backends, *results; backends = proxy_reverse_pername_backends(p, group, FALSE); if (backends == NULL) { return NULL; } backend_count = backends->nelts; conns = backends->elts; if (backend_count == 1) { pconn = conns[0]; } else { size_t group_len; unsigned int h; int idx; group_len = strlen(group); h = str2hash(group, group_len); idx = h % backend_count; pconn = conns[idx]; } /* TODO: What happens if the chosen backend URI cannot be used, e.g. * because it is down/unreachable? In reverse_try_connect(), we'll know * that it failed to connect, but how to tunnel that back down here, to * choose another? */ stmt = "INSERT OR IGNORE INTO proxy_vhost_reverse_per_group (vhost_id, group_name, backend_uri) VALUES (?, ?, ?);"; res = proxy_db_prepare_stmt(p, dbh, stmt); if (res < 0) { return NULL; } res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT, (void *) &vhost_id, 0); if (res < 0) { return NULL; } res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT, (void *) group, -1); if (res < 0) { return NULL; } uri = proxy_conn_get_uri(pconn); res = proxy_db_bind_stmt(p, dbh, stmt, 3, PROXY_DB_BIND_TYPE_TEXT, (void *) uri, -1); if (res < 0) { return NULL; } results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr); if (results == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr ? errstr : strerror(errno)); errno = EPERM; return NULL; } return pconn; } static const struct proxy_conn *reverse_db_pergroup_next(pool *p, struct proxy_dbh *dbh, unsigned int vhost_id, const char *group) { array_header *results; const struct proxy_conn *pconn = NULL; pconn = reverse_db_pergroup_init(p, dbh, vhost_id, group); if (pconn == NULL && errno != ENOENT) { results = reverse_db_pergroup_get(p, dbh, vhost_id, group); if (results != NULL && results->nelts > 0) { char **vals; vals = results->elts; pconn = proxy_conn_create(p, vals[0], 0); } } if (pconn != NULL) { return pconn; } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error preparing database for ProxyReverseConnectPolicy PerGroup for " "group '%s': %s", group, strerror(ENOENT)); errno = EPERM; return NULL; } static int reverse_db_pergroup_used(pool *p, struct proxy_dbh *dbh, unsigned int vhost_id, int backend_id) { int count, res; const char *stmt, *errstr = NULL; array_header *results; /* To prevent database bloating too much, delete all of the entries * in the table if we're over our limit. */ stmt = "SELECT COUNT(*) FROM proxy_vhost_reverse_per_group;"; res = proxy_db_prepare_stmt(p, dbh, stmt); if (res < 0) { return -1; } results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr); if (results == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr ? errstr : strerror(errno)); errno = EPERM; return -1; } if (results->nelts != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "expected 1 result from statement '%s', got %d", stmt, results->nelts); errno = EINVAL; return -1; } count = atoi(((char **) results->elts)[0]); if (count <= PROXY_REVERSE_DB_PERGROUP_MAX_ENTRIES) { return 0; } pr_trace_msg(trace_channel, 5, "PerGroup entry count (%d) exceeds max (%d), purging", count, PROXY_REVERSE_DB_PERGROUP_MAX_ENTRIES); stmt = "DELETE FROM proxy_vhost_reverse_per_group;"; res = proxy_db_prepare_stmt(p, dbh, stmt); if (res < 0) { return -1; } results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr); if (results == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr ? errstr : strerror(errno)); errno = EPERM; return -1; } return 0; } /* ProxyReverseConnectPolicy: PerHost */ static array_header *reverse_db_perhost_get(pool *p, struct proxy_dbh *dbh, unsigned int vhost_id, const pr_netaddr_t *addr) { int res; const char *stmt, *errstr = NULL, *ip; array_header *results; stmt = "SELECT backend_uri FROM proxy_vhost_reverse_per_host WHERE vhost_id = ? AND ip_addr = ?;"; res = proxy_db_prepare_stmt(p, dbh, stmt); if (res < 0) { return NULL; } res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT, (void *) &vhost_id, 0); if (res < 0) { return NULL; } ip = pr_netaddr_get_ipstr(addr); res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT, (void *) ip, -1); if (res < 0) { return NULL; } results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr); if (results == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr ? errstr : strerror(errno)); errno = EPERM; return NULL; } return results; } static const struct proxy_conn *reverse_db_perhost_init(pool *p, struct proxy_dbh *dbh, unsigned int vhost_id, array_header *backends, const pr_netaddr_t *addr) { const struct proxy_conn *pconn = NULL; struct proxy_conn **conns; int res; const char *ip, *stmt, *uri, *errstr = NULL; array_header *results; ip = pr_netaddr_get_ipstr(addr); conns = backends->elts; if (backends->nelts == 1) { pconn = conns[0]; } else { size_t iplen; unsigned int h; int idx; iplen = strlen(ip); h = str2hash(ip, iplen); idx = h % backends->nelts; pconn = conns[idx]; } stmt = "INSERT OR IGNORE INTO proxy_vhost_reverse_per_host (vhost_id, ip_addr, backend_uri) VALUES (?, ?, ?);"; res = proxy_db_prepare_stmt(p, dbh, stmt); if (res < 0) { return NULL; } res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT, (void *) &vhost_id, 0); if (res < 0) { return NULL; } res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT, (void *) ip, -1); if (res < 0) { return NULL; } uri = proxy_conn_get_uri(pconn); res = proxy_db_bind_stmt(p, dbh, stmt, 3, PROXY_DB_BIND_TYPE_TEXT, (void *) uri, -1); if (res < 0) { return NULL; } results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr); if (results == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr ? errstr : strerror(errno)); errno = EPERM; return NULL; } return pconn; } static const struct proxy_conn *reverse_db_perhost_next(pool *p, struct proxy_dbh *dbh, unsigned int vhost_id, const pr_netaddr_t *addr) { array_header *results; const struct proxy_conn *pconn = NULL; results = reverse_db_perhost_get(p, dbh, vhost_id, addr); if (results == NULL) { return NULL; } if (results->nelts == 0) { /* This can happen the very first time; perform an on-demand discovery * of the backends for this host, and try again. */ pconn = reverse_db_perhost_init(p, dbh, vhost_id, db_backends, addr); if (pconn == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error preparing database for ProxyReverseConnectPolicy " "PerHost for host '%s': %s", pr_netaddr_get_ipstr(addr), strerror(errno)); errno = EPERM; return NULL; } } else { char **vals; vals = results->elts; pconn = proxy_conn_create(p, vals[0], 0); } return pconn; } static int reverse_db_perhost_used(pool *p, struct proxy_dbh *dbh, unsigned int vhost_id, int backend_id) { int count, res; const char *stmt, *errstr = NULL; array_header *results; /* To prevent database bloating too much, delete all of the entries * in the table if we're over our limit. */ stmt = "SELECT COUNT(*) FROM proxy_vhost_reverse_per_host;"; res = proxy_db_prepare_stmt(p, dbh, stmt); if (res < 0) { return -1; } results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr); if (results == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr ? errstr : strerror(errno)); errno = EPERM; return -1; } if (results->nelts != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "expected 1 result from statement '%s', got %d", stmt, results->nelts); errno = EINVAL; return -1; } count = atoi(((char **) results->elts)[0]); if (count <= PROXY_REVERSE_DB_PERHOST_MAX_ENTRIES) { return 0; } pr_trace_msg(trace_channel, 5, "PerHost entry count (%d) exceeds max (%d), purging", count, PROXY_REVERSE_DB_PERHOST_MAX_ENTRIES); stmt = "DELETE FROM proxy_vhost_reverse_per_host;"; res = proxy_db_prepare_stmt(p, dbh, stmt); if (res < 0) { return -1; } results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr); if (results == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr ? errstr : strerror(errno)); errno = EPERM; return -1; } return 0; } /* ProxyReverseServers API/handling */ static int reverse_db_policy_init(pool *p, void *dbh, int policy_id, unsigned int vhost_id, array_header *backends, unsigned long opts) { int res, xerrno; switch (policy_id) { case PROXY_REVERSE_CONNECT_POLICY_RANDOM: case PROXY_REVERSE_CONNECT_POLICY_LEAST_CONNS: case PROXY_REVERSE_CONNECT_POLICY_LEAST_RESPONSE_TIME: case PROXY_REVERSE_CONNECT_POLICY_PER_USER: case PROXY_REVERSE_CONNECT_POLICY_PER_HOST: /* No preparation needed at this time. */ res = 0; break; case PROXY_REVERSE_CONNECT_POLICY_ROUND_ROBIN: { int backend_id = 0; if (backends != NULL) { backend_id = backends->nelts-1; } res = reverse_db_roundrobin_init(p, dbh, vhost_id, backend_id); if (res < 0) { xerrno = errno; pr_log_debug(DEBUG3, MOD_PROXY_VERSION ": error preparing database for ProxyReverseConnectPolicy " "RoundRobin: %s", strerror(xerrno)); errno = xerrno; } break; } case PROXY_REVERSE_CONNECT_POLICY_SHUFFLE: if (backends != NULL) { res = reverse_db_shuffle_init(p, dbh, vhost_id, backends); if (res < 0) { xerrno = errno; pr_log_debug(DEBUG3, MOD_PROXY_VERSION ": error preparing database for ProxyReverseConnectPolicy " "Shuffle: %s", strerror(xerrno)); errno = xerrno; } } else { res = 0; } break; case PROXY_REVERSE_CONNECT_POLICY_PER_GROUP: if (!(opts & PROXY_OPT_USE_REVERSE_PROXY_AUTH)) { pr_log_pri(PR_LOG_NOTICE, MOD_PROXY_VERSION ": PerGroup ProxyReverseConnectPolicy requires the " "UseReverseProxyAuth ProxyOption"); errno = EPERM; res = -1; } else { res = 0; } break; default: errno = EINVAL; res = -1; break; } return res; } static const struct proxy_conn *reverse_db_policy_next_backend(pool *p, void *dbh, int policy_id, unsigned int vhost_id, array_header *default_backends, const void *policy_data, int *backend_id) { const struct proxy_conn *pconn = NULL; struct proxy_conn **conns = NULL; int idx = -1, nelts = 0; if (db_backends != NULL) { conns = db_backends->elts; nelts = db_backends->nelts; } if (proxy_reverse_policy_is_sticky(policy_id) != TRUE) { if (conns == NULL && db_backends == NULL) { if (default_backends != NULL) { conns = default_backends->elts; nelts = default_backends->nelts; } else { /* Prevent possible null pointer dereferences later due to missing * default URIs for non-sticky ConnectPolicy configurations. */ pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "missing required default/global ProxyReverseServers"); errno = EPERM; return NULL; } } } switch (policy_id) { case PROXY_REVERSE_CONNECT_POLICY_RANDOM: idx = (int) proxy_random_next(0, nelts-1); if (idx >= 0) { pr_trace_msg(trace_channel, 11, "%s policy: selected index %d of %u", proxy_reverse_policy_name(policy_id), idx, nelts-1); pconn = conns[idx]; } break; case PROXY_REVERSE_CONNECT_POLICY_ROUND_ROBIN: idx = reverse_db_roundrobin_next(p, dbh, vhost_id); if (idx >= 0) { pr_trace_msg(trace_channel, 11, "%s policy: selected index %d of %u", proxy_reverse_policy_name(policy_id), idx, nelts-1); pconn = conns[idx]; } break; case PROXY_REVERSE_CONNECT_POLICY_SHUFFLE: idx = reverse_db_shuffle_next(p, dbh, vhost_id); if (idx >= 0) { pr_trace_msg(trace_channel, 11, "%s policy: selected index %d of %u", proxy_reverse_policy_name(policy_id), idx, nelts-1); pconn = conns[idx]; } break; case PROXY_REVERSE_CONNECT_POLICY_LEAST_CONNS: idx = reverse_db_leastconns_next(p, dbh, vhost_id); if (idx >= 0) { pr_trace_msg(trace_channel, 11, "%s policy: selected index %d of %u", proxy_reverse_policy_name(policy_id), idx, nelts-1); pconn = conns[idx]; } break; case PROXY_REVERSE_CONNECT_POLICY_LEAST_RESPONSE_TIME: idx = reverse_db_leastresponsetime_next(p, dbh, vhost_id); if (idx >= 0) { pr_trace_msg(trace_channel, 11, "%s policy: selected index %d of %u", proxy_reverse_policy_name(policy_id), idx, nelts-1); pconn = conns[idx]; } break; case PROXY_REVERSE_CONNECT_POLICY_PER_USER: pconn = reverse_db_peruser_next(p, dbh, vhost_id, policy_data); if (pconn != NULL) { pr_trace_msg(trace_channel, 11, "%s policy: selected backend '%.100s' for user '%s'", proxy_reverse_policy_name(policy_id), proxy_conn_get_uri(pconn), (const char *) policy_data); } break; case PROXY_REVERSE_CONNECT_POLICY_PER_GROUP: pconn = reverse_db_pergroup_next(p, dbh, vhost_id, policy_data); if (pconn != NULL) { pr_trace_msg(trace_channel, 11, "%s policy: selected backend '%.100s' for user '%s'", proxy_reverse_policy_name(policy_id), proxy_conn_get_uri(pconn), (const char *) policy_data); } break; case PROXY_REVERSE_CONNECT_POLICY_PER_HOST: pconn = reverse_db_perhost_next(p, dbh, vhost_id, session.c->remote_addr); if (pconn != NULL) { pr_trace_msg(trace_channel, 11, "%s policy: selected backend '%.100s' for host '%s'", proxy_reverse_policy_name(policy_id), proxy_conn_get_uri(pconn), pr_netaddr_get_ipstr(session.c->remote_addr)); } break; default: errno = ENOSYS; return NULL; } if (backend_id != NULL) { *backend_id = idx; } return pconn; } static int reverse_db_policy_update_backend(pool *p, void *dbh, int policy_id, unsigned vhost_id, int backend_id, int conn_incr, long connect_ms) { /* Increment the conn count for this backend ID. */ int res, idx = 1; const char *stmt, *errstr = NULL; array_header *results; /* If our ReverseConnectPolicy is one of PerUser, PerGroup, or PerHost, * we can skip this step: those policies do not use the connection count/time. * This also helps avoid database contention under load for these policies. */ if (proxy_reverse_policy_is_sticky(policy_id) == TRUE) { pr_trace_msg(trace_channel, 17, "sticky policy %s does not require updates, skipping", proxy_reverse_policy_name(policy_id)); return 0; } /* TODO: Right now, we simply overwrite/track the very latest connect ms. * But this could unfairly skew policies such as LeastResponseTime, as when * the server in question had higher latency for that particular connection, * due to e.g. OCSP response cache expiration. * * Another way would to be average the given connect ms with the previous * one (if present), and store that. Something to ponder for the future. */ if (connect_ms > 0) { stmt = "UPDATE proxy_vhost_backends SET conn_count = conn_count + ?, connect_ms = ? WHERE vhost_id = ? AND backend_id = ?;"; } else { stmt = "UPDATE proxy_vhost_backends SET conn_count = conn_count + ? WHERE vhost_id = ? AND backend_id = ?;"; } res = proxy_db_prepare_stmt(p, dbh, stmt); if (res < 0) { return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, idx, PROXY_DB_BIND_TYPE_INT, (void *) &conn_incr, 0); if (res < 0) { return -1; } idx++; if (connect_ms > 0) { res = proxy_db_bind_stmt(p, dbh, stmt, idx, PROXY_DB_BIND_TYPE_LONG, (void *) &connect_ms, 0); if (res < 0) { return -1; } idx++; } res = proxy_db_bind_stmt(p, dbh, stmt, idx, PROXY_DB_BIND_TYPE_INT, (void *) &vhost_id, 0); if (res < 0) { return -1; } idx++; res = proxy_db_bind_stmt(p, dbh, stmt, idx, PROXY_DB_BIND_TYPE_INT, (void *) &backend_id, 0); if (res < 0) { return -1; } results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr); if (results == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr ? errstr : strerror(errno)); errno = EPERM; return -1; } return 0; } static int reverse_db_policy_used_backend(pool *p, void *dbh, int policy_id, unsigned int vhost_id, int idx) { int res; switch (policy_id) { case PROXY_REVERSE_CONNECT_POLICY_RANDOM: res = 0; break; case PROXY_REVERSE_CONNECT_POLICY_ROUND_ROBIN: res = reverse_db_roundrobin_used(p, dbh, vhost_id, idx); break; case PROXY_REVERSE_CONNECT_POLICY_SHUFFLE: res = reverse_db_shuffle_used(p, dbh, vhost_id, idx); break; case PROXY_REVERSE_CONNECT_POLICY_LEAST_CONNS: res = reverse_db_leastconns_used(p, dbh, vhost_id, idx); break; case PROXY_REVERSE_CONNECT_POLICY_LEAST_RESPONSE_TIME: res = reverse_db_leastresponsetime_used(p, dbh, vhost_id, idx); break; case PROXY_REVERSE_CONNECT_POLICY_PER_USER: res = reverse_db_peruser_used(p, dbh, vhost_id, idx); break; case PROXY_REVERSE_CONNECT_POLICY_PER_GROUP: res = reverse_db_pergroup_used(p, dbh, vhost_id, idx); break; case PROXY_REVERSE_CONNECT_POLICY_PER_HOST: res = reverse_db_perhost_used(p, dbh, vhost_id, idx); break; default: errno = ENOSYS; return -1; } if (res < 0) { int xerrno = errno; errno = xerrno; return -1; } return 0; } static void *reverse_db_init(pool *p, const char *tables_path, int flags) { int db_flags, res, xerrno = 0; const char *db_path = NULL; server_rec *s; struct proxy_dbh *dbh; if (tables_path == NULL) { errno = EINVAL; return NULL; } db_path = pdircat(p, tables_path, "proxy-reverse.db", NULL); db_flags = PROXY_DB_OPEN_FL_SCHEMA_VERSION_CHECK|PROXY_DB_OPEN_FL_INTEGRITY_CHECK|PROXY_DB_OPEN_FL_VACUUM; if (flags & PROXY_DB_OPEN_FL_SKIP_VACUUM) { /* If the caller needs us to skip the vacuum, we will. */ db_flags &= ~PROXY_DB_OPEN_FL_VACUUM; } PRIVS_ROOT dbh = proxy_db_open_with_version(p, db_path, PROXY_REVERSE_DB_SCHEMA_NAME, PROXY_REVERSE_DB_SCHEMA_VERSION, db_flags); xerrno = errno; PRIVS_RELINQUISH if (dbh == NULL) { (void) pr_log_pri(PR_LOG_NOTICE, MOD_PROXY_VERSION ": error opening database '%s' for schema '%s', version %u: %s", db_path, PROXY_REVERSE_DB_SCHEMA_NAME, PROXY_REVERSE_DB_SCHEMA_VERSION, strerror(xerrno)); errno = xerrno; return NULL; } res = reverse_db_add_schema(p, dbh, db_path); if (res < 0) { xerrno = errno; (void) pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": error creating schema in database '%s' for '%s': %s", db_path, PROXY_REVERSE_DB_SCHEMA_NAME, strerror(xerrno)); (void) proxy_db_close(p, dbh); errno = xerrno; return NULL; } res = reverse_db_truncate_tables(p, dbh); if (res < 0) { xerrno = errno; (void) proxy_db_close(p, dbh); errno = xerrno; return NULL; } for (s = (server_rec *) server_list->xas_list; s; s = s->next) { config_rec *c; array_header *backends = NULL; res = reverse_db_add_vhost(p, dbh, s); if (res < 0) { xerrno = errno; (void) pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": error adding database entry for server '%s' in schema '%s': %s", s->ServerName, PROXY_REVERSE_DB_SCHEMA_NAME, strerror(xerrno)); (void) proxy_db_close(p, dbh); errno = xerrno; return NULL; } c = find_config(s->conf, CONF_PARAM, "ProxyReverseServers", FALSE); while (c != NULL) { const char *uri; pr_signals_handle(); uri = c->argv[1]; if (uri != NULL) { int defer = FALSE; /* Handling of sql:// URIs is done later, in the session init * call, assuming we've connected to a SQL server. */ if (strncmp(uri, "sql:/", 5) == 0) { defer = TRUE; } /* Skip any %U- or %g-bearing URIs. */ if (defer == FALSE && (strstr(uri, "%U") != NULL || strstr(uri, "%g") != NULL)) { defer = TRUE; } if (defer) { c = find_config_next(c, c->next, CONF_PARAM, "ProxyReverseServers", FALSE); continue; } } if (backends == NULL) { backends = c->argv[0]; } else { array_cat(backends, c->argv[0]); } c = find_config_next(c, c->next, CONF_PARAM, "ProxyReverseServers", FALSE); } /* What if ALL of the ProxyReverseServers are deferred? In that case, we * have no backend servers to add at this time. */ if (backends != NULL) { res = reverse_db_add_backends(p, dbh, s->sid, backends); if (res < 0) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error adding database entries for ProxyReverseServers: %s", strerror(xerrno)); (void) proxy_db_close(p, dbh); errno = xerrno; return NULL; } } } return dbh; } static int reverse_db_close(pool *p, void *dbh) { if (p == NULL) { errno = EINVAL; return -1; } /* TODO: Implement any necessary cleanup */ if (dbh != NULL) { if (proxy_db_close(p, dbh) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error detaching database with schema '%s': %s", PROXY_REVERSE_DB_SCHEMA_NAME, strerror(errno)); } } return 0; } static void *reverse_db_open(pool *p, const char *tables_path, array_header *backends) { int xerrno = 0; struct proxy_dbh *dbh; const char *db_path; db_path = pdircat(p, tables_path, "proxy-reverse.db", NULL); /* Make sure we have our own per-session database handle, per SQLite3 * recommendation. */ PRIVS_ROOT dbh = proxy_db_open_with_version(p, db_path, PROXY_REVERSE_DB_SCHEMA_NAME, PROXY_REVERSE_DB_SCHEMA_VERSION, 0); xerrno = errno; PRIVS_RELINQUISH if (dbh == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error opening database '%s' for schema '%s', version %u: %s", db_path, PROXY_REVERSE_DB_SCHEMA_NAME, PROXY_REVERSE_DB_SCHEMA_VERSION, strerror(xerrno)); errno = xerrno; return NULL; } db_backends = backends; return dbh; } int proxy_reverse_db_as_datastore(struct proxy_reverse_datastore *ds, void *ds_data, size_t ds_datasz) { if (ds == NULL) { errno = EINVAL; return -1; } (void) ds_data; (void) ds_datasz; ds->policy_init = reverse_db_policy_init; ds->policy_next_backend = reverse_db_policy_next_backend; ds->policy_used_backend = reverse_db_policy_used_backend; ds->policy_update_backend = reverse_db_policy_update_backend; ds->init = reverse_db_init; ds->open = reverse_db_open; ds->close = reverse_db_close; return 0; } proftpd-mod_proxy-0.9.5/lib/proxy/reverse/redis.c000066400000000000000000000773251475737016700221340ustar00rootroot00000000000000/* * ProFTPD - mod_proxy reverse datastore implementation * Copyright (c) 2012-2020 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "redis.h" #include "proxy/conn.h" #include "proxy/reverse.h" #include "proxy/reverse/redis.h" #include "proxy/random.h" #include "proxy/tls.h" #include "proxy/ftp/ctrl.h" /* PerHost/PerUser/PerGroup table limits */ #define PROXY_REVERSE_REDIS_PERHOST_MAX_ENTRIES 8192 #define PROXY_REVERSE_REDIS_PERUSER_MAX_ENTRIES 8192 #define PROXY_REVERSE_REDIS_PERGROUP_MAX_ENTRIES 8192 static array_header *redis_backends = NULL; static const char *trace_channel = "proxy.reverse.redis"; static void *redis_prefix = NULL; static size_t redis_prefixsz = 0; static char *make_key(pool *p, const char *policy, unsigned int vhost_id, const char *name) { char *key; size_t keysz; /* It's 21 characters for "proxy_reverse:" and ":vhost#", and one for the * trailing NUL. Allocate enough room for a large vhost ID, e.g. * optimistically in the thousands. */ keysz = 22 + 6 + strlen(policy); if (name != NULL) { keysz += strlen(name) + 1; } key = pcalloc(p, keysz + 1); if (name == NULL) { snprintf(key, keysz, "proxy_reverse:%s:vhost#%u", policy, vhost_id); } else { snprintf(key, keysz, "proxy_reverse:%s:vhost#%u:%s", policy, vhost_id, name); } return key; } static unsigned int str2hash(const void *key, size_t keysz) { unsigned int i = 0; size_t sz = !keysz ? strlen((const char *) key) : keysz; while (sz--) { const char *k = key; unsigned int c; pr_signals_handle(); c = k[sz]; i = (i * 33) + c; } return i; } /* Given an index into the array_header of backend pconns, return the URI * for the indexed conn. */ static const char *backend_uri_by_idx(int idx) { const struct proxy_conn *pconn; if (redis_backends == NULL) { errno = EPERM; return NULL; } if (idx < 0) { errno = EPERM; return NULL; } pconn = ((struct proxy_conn **) redis_backends->elts)[idx]; return proxy_conn_get_uri(pconn); } /* Redis List helpers */ static array_header *redis_get_list_backend_uris(pool *p, pr_redis_t *redis, const char *policy, unsigned int vhost_id, const char *name) { int res; pool *tmp_pool; char *key; array_header *backend_uris, *values = NULL, *valueszs = NULL; tmp_pool = make_sub_pool(p); key = make_key(tmp_pool, policy, vhost_id, name); res = pr_redis_list_getall(tmp_pool, redis, &proxy_module, key, &values, &valueszs); if (res < 0) { int xerrno = errno; pr_trace_msg(trace_channel, 3, "error retrieving %s Redis entries using key '%s': %s", policy, key, strerror(xerrno)); destroy_pool(tmp_pool); errno = xerrno; return NULL; } backend_uris = copy_array_str(p, values); destroy_pool(tmp_pool); return backend_uris; } static int redis_set_list_backends(pool *p, pr_redis_t *redis, const char *policy, unsigned int vhost_id, const char *name, array_header *backends) { register unsigned int i; int res = 0, xerrno; pool *tmp_pool; char *key; array_header *backend_uris, *backend_uriszs; tmp_pool = make_sub_pool(p); key = make_key(tmp_pool, policy, vhost_id, name); backend_uris = make_array(tmp_pool, 0, sizeof(char *)); backend_uriszs = make_array(tmp_pool, 0, sizeof(size_t)); for (i = 0; i < backends->nelts; i++) { struct proxy_conn *pconn; const char *backend_uri; size_t backend_urisz; pconn = ((struct proxy_conn **) backends->elts)[i]; backend_uri = proxy_conn_get_uri(pconn); *((char **) push_array(backend_uris)) = pstrdup(tmp_pool, backend_uri); backend_urisz = strlen(backend_uri); *((size_t *) push_array(backend_uriszs)) = backend_urisz; pr_trace_msg(trace_channel, 19, "adding %s list backend #%u: '%.*s'", policy, i+1, (int) backend_urisz, backend_uri); } res = pr_redis_list_setall(redis, &proxy_module, key, backend_uris, backend_uriszs); xerrno = errno; if (res < 0) { pr_trace_msg(trace_channel, 6, "error adding %s Redis entries: %s", policy, strerror(xerrno)); } destroy_pool(tmp_pool); errno = xerrno; return res; } /* Redis Sorted Set helpers */ static int redis_set_sorted_set_backends(pool *p, pr_redis_t *redis, const char *policy, unsigned int vhost_id, array_header *backends, float init_score) { register unsigned int i; int res = 0, xerrno; pool *tmp_pool; char *key; array_header *backend_uris, *backend_uriszs, *backend_scores; tmp_pool = make_sub_pool(p); key = make_key(tmp_pool, policy, vhost_id, NULL); backend_uris = make_array(tmp_pool, 0, sizeof(char *)); backend_uriszs = make_array(tmp_pool, 0, sizeof(size_t)); backend_scores = make_array(tmp_pool, 0, sizeof(float)); for (i = 0; i < backends->nelts; i++) { struct proxy_conn *pconn; const char *backend_uri; size_t backend_urisz; pconn = ((struct proxy_conn **) backends->elts)[i]; backend_uri = proxy_conn_get_uri(pconn); *((char **) push_array(backend_uris)) = pstrdup(tmp_pool, backend_uri); backend_urisz = strlen(backend_uri); *((size_t *) push_array(backend_uriszs)) = backend_urisz; *((float *) push_array(backend_scores)) = init_score; pr_trace_msg(trace_channel, 19, "adding %s sorted set backend #%u: '%.*s' (%0.3f)", policy, i+1, (int) backend_urisz, backend_uri, init_score); } res = pr_redis_sorted_set_setall(redis, &proxy_module, key, backend_uris, backend_uriszs, backend_scores); xerrno = errno; if (res < 0) { pr_trace_msg(trace_channel, 6, "error adding %s Redis entries: %s", policy, strerror(xerrno)); } destroy_pool(tmp_pool); errno = xerrno; return res; } /* ProxyReverseConnectPolicy: Shuffle */ /* The implementation of shuffling here requires two Redis lists, the A and B * lists. URIs are consumed (via random selection) from the A list and added * to the B list, until the A list is empty. At which point, the B list is * renamed to the A list, and we start again. */ static int reverse_redis_shuffle_init(pool *p, pr_redis_t *redis, unsigned int vhost_id, array_header *backends) { return redis_set_list_backends(p, redis, "Shuffle", vhost_id, "A", backends); } static long reverse_redis_shuffle_next(pool *p, pr_redis_t *redis, unsigned int vhost_id) { int res, xerrno; pool *tmp_pool; char *akey, *bkey; const char *val; size_t valsz; uint64_t count = 0; long idx; tmp_pool = make_sub_pool(p); akey = make_key(tmp_pool, "Shuffle", vhost_id, "A"); res = pr_redis_list_count(redis, &proxy_module, akey, &count); xerrno = errno; if (res < 0) { if (xerrno != ENOENT) { pr_trace_msg(trace_channel, 6, "error getting count of Redis list '%s': %s", akey, strerror(xerrno)); destroy_pool(tmp_pool); errno = xerrno; return -1; } count = 0; } if (count == 0) { res = reverse_redis_shuffle_init(p, redis, vhost_id, redis_backends); xerrno = errno; if (res < 0) { destroy_pool(tmp_pool); errno = xerrno; return -1; } count = redis_backends->nelts; } idx = proxy_random_next(0, count-1); /* XXX Now we want to remove that backend URI from the A list, and add it to * the B list. */ val = backend_uri_by_idx((int) idx); xerrno = errno; if (val == NULL) { destroy_pool(tmp_pool); errno = xerrno; return -1; } valsz = strlen(val); res = pr_redis_list_delete(redis, &proxy_module, akey, (void *) val, valsz); xerrno = errno; if (res < 0) { destroy_pool(tmp_pool); errno = xerrno; return -1; } bkey = make_key(tmp_pool, "Shuffle", vhost_id, "B"); res = pr_redis_list_append(redis, &proxy_module, bkey, (void *) val, valsz); xerrno = errno; if (res < 0) { destroy_pool(tmp_pool); errno = xerrno; return -1; } /* If count is one, it means we just removed the last backend from the A * list. Thus rename the B list to be the A list. */ if (count == 1) { res = pr_redis_rename(redis, &proxy_module, bkey, akey); xerrno = errno; if (res < 0) { pr_trace_msg(trace_channel, 3, "error renaming Shuffle key '%s' to '%s': %s", bkey, akey, strerror(xerrno)); idx = -1; } } destroy_pool(tmp_pool); errno = xerrno; return idx; } static int reverse_redis_shuffle_used(pool *p, pr_redis_t *redis, unsigned int vhost_id, int backend_idx) { /* Nothing to do here. */ return 0; } /* ProxyReverseConnectPolicy: RoundRobin */ static int reverse_redis_roundrobin_init(pool *p, pr_redis_t *redis, unsigned int vhost_id, array_header *backends) { return redis_set_list_backends(p, redis, "RoundRobin", vhost_id, NULL, backends); } static const struct proxy_conn *reverse_redis_roundrobin_next(pool *p, pr_redis_t *redis, unsigned int vhost_id) { int res, xerrno; pool *tmp_pool; char *key, *backend_uri = NULL; size_t backend_urisz = 0; const struct proxy_conn *pconn; tmp_pool = make_sub_pool(p); key = make_key(tmp_pool, "RoundRobin", vhost_id, NULL); res = pr_redis_list_rotate(tmp_pool, redis, &proxy_module, key, (void **) &backend_uri, &backend_urisz); xerrno = errno; if (res < 0) { pr_trace_msg(trace_channel, 3, "error rotating RoundRobin Redis list using key '%s': %s", key, strerror(xerrno)); destroy_pool(tmp_pool); errno = xerrno; return NULL; } pconn = proxy_conn_create(p, pstrndup(tmp_pool, backend_uri, backend_urisz), 0); xerrno = errno; if (pconn == NULL) { pr_trace_msg(trace_channel, 3, "error creating proxy connection from URI '%.*s': %s", (int) backend_urisz, backend_uri, strerror(xerrno)); } destroy_pool(tmp_pool); errno = xerrno; return pconn; } static int reverse_redis_roundrobin_used(pool *p, pr_redis_t *redis, unsigned int vhost_id, int backend_idx) { /* Nothing to do here. */ return 0; } /* ProxyReverseConnectPolicy: LeastConns */ static int reverse_redis_leastconns_init(pool *p, pr_redis_t *redis, unsigned int vhost_id, array_header *backends) { return redis_set_sorted_set_backends(p, redis, "LeastConns", vhost_id, backends, 0.0); } static const struct proxy_conn *reverse_redis_leastconns_next(pool *p, pr_redis_t *redis, unsigned int vhost_id) { int res, xerrno; pool *tmp_pool; char *key; array_header *vals = NULL, *valszs = NULL; const struct proxy_conn *pconn = NULL; tmp_pool = make_sub_pool(p); key = make_key(tmp_pool, "LeastConns", vhost_id, NULL); res = pr_redis_sorted_set_getn(tmp_pool, redis, &proxy_module, key, 0, 1, &vals, &valszs, PR_REDIS_SORTED_SET_FL_ASC); xerrno = errno; if (res == 0) { char *backend_uri; backend_uri = ((char **) vals->elts)[0]; pconn = proxy_conn_create(p, backend_uri, 0); } destroy_pool(tmp_pool); errno = xerrno; return pconn; } static int reverse_redis_leastconns_used(pool *p, pr_redis_t *redis, unsigned int vhost_id, int backend_idx) { /* Nothing to do here. */ return 0; } static int reverse_redis_leastconns_update(pool *p, pr_redis_t *redis, unsigned int vhost_id, int backend_idx, int conn_incr, long connect_ms) { int res, xerrno; pool *tmp_pool; char *key; const char *val; float score; size_t valsz; val = backend_uri_by_idx(backend_idx); if (val == NULL) { return -1; } valsz = strlen(val); tmp_pool = make_sub_pool(p); key = make_key(tmp_pool, "LeastConns", vhost_id, NULL); score = (float) conn_incr; res = pr_redis_sorted_set_set(redis, &proxy_module, key, (void *) val, valsz, score); xerrno = errno; destroy_pool(tmp_pool); errno = xerrno; return res; } /* ProxyReverseConnectPolicy: LeastResponseTime */ /* Note: "least response time" is determined by calculating the following * for each backend server: * * N = connection count * connect time (ms) * * and choosing the backend with the lowest value for N. If there are no * backend servers with connect time values, choose the one with the lowest * connection count. */ static int reverse_redis_leastresponsetime_init(pool *p, pr_redis_t *redis, unsigned int vhost_id, array_header *backends) { return redis_set_sorted_set_backends(p, redis, "LeastResponseTime", vhost_id, backends, 0.0); } static const struct proxy_conn *reverse_redis_leastresponsetime_next(pool *p, pr_redis_t *redis, unsigned int vhost_id) { int res, xerrno; pool *tmp_pool; char *key; array_header *vals = NULL, *valszs = NULL; const struct proxy_conn *pconn = NULL; tmp_pool = make_sub_pool(p); key = make_key(tmp_pool, "LeastResponseTime", vhost_id, NULL); res = pr_redis_sorted_set_getn(tmp_pool, redis, &proxy_module, key, 0, 1, &vals, &valszs, PR_REDIS_SORTED_SET_FL_ASC); xerrno = errno; if (res == 0) { char *backend_uri; backend_uri = ((char **) vals->elts)[0]; pconn = proxy_conn_create(p, backend_uri, 0); } destroy_pool(tmp_pool); errno = xerrno; return pconn; } static int reverse_redis_leastresponsetime_used(pool *p, pr_redis_t *redis, unsigned int vhost_id, int backend_idx) { /* TODO: anything to do here? */ return 0; } static int reverse_redis_leastresponsetime_update(pool *p, pr_redis_t *redis, unsigned int vhost_id, int backend_idx, int conn_incr, long connect_ms) { int res, xerrno; pool *tmp_pool; char *key; const char *val; float score; size_t valsz; val = backend_uri_by_idx(backend_idx); if (val == NULL) { return -1; } valsz = strlen(val); tmp_pool = make_sub_pool(p); key = make_key(tmp_pool, "LeastResponseTime", vhost_id, NULL); score = (float) conn_incr; if (connect_ms > 0) { score *= (float) connect_ms; } res = pr_redis_sorted_set_set(redis, &proxy_module, key, (void *) val, valsz, score); xerrno = errno; destroy_pool(tmp_pool); errno = xerrno; return res; } /* ProxyReverseConnectPolicy: PerUser */ static array_header *reverse_redis_peruser_get(pool *p, pr_redis_t *redis, unsigned int vhost_id, const char *user) { return redis_get_list_backend_uris(p, redis, "PerUser", vhost_id, user); } static const struct proxy_conn *reverse_redis_peruser_init(pool *p, pr_redis_t *redis, unsigned int vhost_id, const char *user) { int res; const struct proxy_conn *pconn = NULL; struct proxy_conn **conns = NULL; unsigned int backend_count; array_header *backends; backends = proxy_reverse_pername_backends(p, user, TRUE); if (backends == NULL) { return NULL; } /* Store these backends for later use. */ res = redis_set_list_backends(p, redis, "PerUser", vhost_id, user, backends); if (res < 0) { return NULL; } backend_count = backends->nelts; conns = backends->elts; if (backend_count == 1) { pconn = conns[0]; } else { size_t user_len; unsigned int h; int idx; user_len = strlen(user); h = str2hash(user, user_len); idx = h % backend_count; pconn = conns[idx]; } return pconn; } static const struct proxy_conn *reverse_redis_peruser_next(pool *p, pr_redis_t *redis, unsigned int vhost_id, const char *user) { array_header *backend_uris; const struct proxy_conn *pconn = NULL; pconn = reverse_redis_peruser_init(p, redis, vhost_id, user); if (pconn == NULL && errno != ENOENT) { backend_uris = reverse_redis_peruser_get(p, redis, vhost_id, user); if (backend_uris != NULL) { char **vals; vals = backend_uris->elts; pconn = proxy_conn_create(p, vals[0], 0); } } if (pconn != NULL) { return pconn; } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error preparing PerUser Redis entries for user '%s': %s", user, strerror(ENOENT)); errno = EPERM; return NULL; } static int reverse_redis_peruser_used(pool *p, pr_redis_t *redis, unsigned int vhost_id, int backend_idx) { /* Nothing to do here. */ return 0; } /* ProxyReverseConnectPolicy: PerGroup */ static array_header *reverse_redis_pergroup_get(pool *p, pr_redis_t *redis, unsigned int vhost_id, const char *group) { return redis_get_list_backend_uris(p, redis, "PerGroup", vhost_id, group); } static const struct proxy_conn *reverse_redis_pergroup_init(pool *p, pr_redis_t *redis, unsigned int vhost_id, const char *group) { int res; const struct proxy_conn *pconn = NULL; struct proxy_conn **conns = NULL; unsigned int backend_count; array_header *backends; backends = proxy_reverse_pername_backends(p, group, FALSE); if (backends == NULL) { return NULL; } /* Store these backends for later use. */ res = redis_set_list_backends(p, redis, "PerGroup", vhost_id, group, backends); if (res < 0) { return NULL; } backend_count = backends->nelts; conns = backends->elts; if (backend_count == 1) { pconn = conns[0]; } else { size_t group_len; unsigned int h; int idx; group_len = strlen(group); h = str2hash(group, group_len); idx = h % backend_count; pconn = conns[idx]; } return pconn; } static const struct proxy_conn *reverse_redis_pergroup_next(pool *p, pr_redis_t *redis, unsigned int vhost_id, const char *group) { array_header *backend_uris; const struct proxy_conn *pconn = NULL; pconn = reverse_redis_pergroup_init(p, redis, vhost_id, group); if (pconn == NULL && errno != ENOENT) { backend_uris = reverse_redis_pergroup_get(p, redis, vhost_id, group); if (backend_uris != NULL) { char **vals; vals = backend_uris->elts; pconn = proxy_conn_create(p, vals[0], 0); } } if (pconn != NULL) { return pconn; } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error preparing PerGroup Redis entries for group '%s': %s", group, strerror(ENOENT)); errno = EPERM; return NULL; } static int reverse_redis_pergroup_used(pool *p, pr_redis_t *redis, unsigned int vhost_id, int backend_idx) { /* Nothing to do here. */ return 0; } /* ProxyReverseConnectPolicy: PerHost */ static array_header *reverse_redis_perhost_get(pool *p, pr_redis_t *redis, unsigned int vhost_id, const pr_netaddr_t *addr) { return redis_get_list_backend_uris(p, redis, "PerHost", vhost_id, pr_netaddr_get_ipstr(addr)); } static const struct proxy_conn *reverse_redis_perhost_init(pool *p, pr_redis_t *redis, unsigned int vhost_id, array_header *backends, const pr_netaddr_t *addr) { int res; const struct proxy_conn *pconn = NULL; struct proxy_conn **conns; const char *ip; ip = pr_netaddr_get_ipstr(addr); /* Store these backends for later use. */ res = redis_set_list_backends(p, redis, "PerHost", vhost_id, ip, backends); if (res < 0) { return NULL; } conns = backends->elts; if (backends->nelts == 1) { pconn = conns[0]; } else { size_t iplen; unsigned int h; int idx; iplen = strlen(ip); h = str2hash(ip, iplen); idx = h % backends->nelts; pconn = conns[idx]; } return pconn; } static const struct proxy_conn *reverse_redis_perhost_next(pool *p, pr_redis_t *redis, unsigned int vhost_id, const pr_netaddr_t *addr) { array_header *backend_uris; const struct proxy_conn *pconn = NULL; backend_uris = reverse_redis_perhost_get(p, redis, vhost_id, addr); if (backend_uris == NULL && errno == ENOENT) { /* This can happen the very first time; perform an on-demand discovery * of the backends for this host, and try again. */ pconn = reverse_redis_perhost_init(p, redis, vhost_id, redis_backends, addr); if (pconn == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error preparing PerHost Redis entries for host '%s': %s", pr_netaddr_get_ipstr(addr), strerror(errno)); errno = EPERM; return NULL; } } else { char **vals; vals = backend_uris->elts; pconn = proxy_conn_create(p, vals[0], 0); } return pconn; } static int reverse_redis_perhost_used(pool *p, pr_redis_t *redis, unsigned int vhost_id, int backend_idx) { /* Nothing to do here. */ return 0; } /* ProxyReverseServers API/handling */ static int reverse_redis_policy_init(pool *p, void *redis, int policy_id, unsigned int vhost_id, array_header *backends, unsigned long opts) { int res = 0, xerrno; switch (policy_id) { case PROXY_REVERSE_CONNECT_POLICY_RANDOM: case PROXY_REVERSE_CONNECT_POLICY_PER_USER: case PROXY_REVERSE_CONNECT_POLICY_PER_HOST: /* No preparation needed at this time. */ break; case PROXY_REVERSE_CONNECT_POLICY_ROUND_ROBIN: if (backends != NULL) { res = reverse_redis_roundrobin_init(p, redis, vhost_id, backends); if (res < 0) { xerrno = errno; pr_log_debug(DEBUG3, MOD_PROXY_VERSION ": error preparing %s Redis entries: %s", proxy_reverse_policy_name(policy_id), strerror(xerrno)); errno = xerrno; } } break; case PROXY_REVERSE_CONNECT_POLICY_SHUFFLE: if (backends != NULL) { res = reverse_redis_shuffle_init(p, redis, vhost_id, backends); if (res < 0) { xerrno = errno; pr_log_debug(DEBUG3, MOD_PROXY_VERSION ": error preparing %s Redis entries: %s", proxy_reverse_policy_name(policy_id), strerror(xerrno)); errno = xerrno; } } break; case PROXY_REVERSE_CONNECT_POLICY_LEAST_CONNS: if (backends != NULL) { res = reverse_redis_leastconns_init(p, redis, vhost_id, backends); if (res < 0) { xerrno = errno; pr_log_debug(DEBUG3, MOD_PROXY_VERSION ": error preparing %s Redis entries: %s", proxy_reverse_policy_name(policy_id), strerror(xerrno)); errno = xerrno; } } break; case PROXY_REVERSE_CONNECT_POLICY_LEAST_RESPONSE_TIME: if (backends != NULL) { res = reverse_redis_leastresponsetime_init(p, redis, vhost_id, backends); if (res < 0) { xerrno = errno; pr_log_debug(DEBUG3, MOD_PROXY_VERSION ": error preparing %s Redis entries: %s", proxy_reverse_policy_name(policy_id), strerror(xerrno)); errno = xerrno; } } break; case PROXY_REVERSE_CONNECT_POLICY_PER_GROUP: if (!(opts & PROXY_OPT_USE_REVERSE_PROXY_AUTH)) { pr_log_pri(PR_LOG_NOTICE, MOD_PROXY_VERSION ": PerGroup ProxyReverseConnectPolicy requires the " "UseReverseProxyAuth ProxyOption"); errno = EPERM; res = -1; } break; default: errno = EINVAL; res = -1; break; } return res; } static const struct proxy_conn *reverse_redis_policy_next_backend(pool *p, void *redis, int policy_id, unsigned int vhost_id, array_header *default_backends, const void *policy_data, int *backend_id) { const struct proxy_conn *pconn = NULL; struct proxy_conn **conns = NULL; int idx = -1, nelts = 0; if (redis_backends != NULL) { conns = redis_backends->elts; nelts = redis_backends->nelts; } if (proxy_reverse_policy_is_sticky(policy_id) != TRUE) { if (conns == NULL && default_backends != NULL && redis_backends == NULL) { conns = default_backends->elts; nelts = default_backends->nelts; } } switch (policy_id) { case PROXY_REVERSE_CONNECT_POLICY_RANDOM: idx = (int) proxy_random_next(0, nelts-1); if (idx >= 0) { pr_trace_msg(trace_channel, 11, "%s policy: selected index %d of %u", proxy_reverse_policy_name(policy_id), idx, nelts-1); pconn = conns[idx]; } break; case PROXY_REVERSE_CONNECT_POLICY_ROUND_ROBIN: pconn = reverse_redis_roundrobin_next(p, redis, vhost_id); if (pconn != NULL) { pr_trace_msg(trace_channel, 11, "%s policy: selected backend '%.100s'", proxy_reverse_policy_name(policy_id), proxy_conn_get_uri(pconn)); } break; case PROXY_REVERSE_CONNECT_POLICY_SHUFFLE: idx = (int) reverse_redis_shuffle_next(p, redis, vhost_id); if (idx >= 0) { pr_trace_msg(trace_channel, 11, "%s policy: selected index %d of %u", proxy_reverse_policy_name(policy_id), idx, nelts-1); pconn = conns[idx]; } break; break; case PROXY_REVERSE_CONNECT_POLICY_LEAST_CONNS: pconn = reverse_redis_leastconns_next(p, redis, vhost_id); if (pconn != NULL) { pr_trace_msg(trace_channel, 11, "%s policy: selected backend '%.100s'", proxy_reverse_policy_name(policy_id), proxy_conn_get_uri(pconn)); } break; case PROXY_REVERSE_CONNECT_POLICY_LEAST_RESPONSE_TIME: pconn = reverse_redis_leastresponsetime_next(p, redis, vhost_id); if (pconn != NULL) { pr_trace_msg(trace_channel, 11, "%s policy: selected backend '%.100s'", proxy_reverse_policy_name(policy_id), proxy_conn_get_uri(pconn)); } break; case PROXY_REVERSE_CONNECT_POLICY_PER_USER: pconn = reverse_redis_peruser_next(p, redis, vhost_id, policy_data); if (pconn != NULL) { pr_trace_msg(trace_channel, 11, "%s policy: selected backend '%.100s' for user '%s'", proxy_reverse_policy_name(policy_id), proxy_conn_get_uri(pconn), (const char *) policy_data); } break; case PROXY_REVERSE_CONNECT_POLICY_PER_GROUP: pconn = reverse_redis_pergroup_next(p, redis, vhost_id, policy_data); if (pconn != NULL) { pr_trace_msg(trace_channel, 11, "%s policy: selected backend '%.100s' for user '%s'", proxy_reverse_policy_name(policy_id), proxy_conn_get_uri(pconn), (const char *) policy_data); } break; case PROXY_REVERSE_CONNECT_POLICY_PER_HOST: pconn = reverse_redis_perhost_next(p, redis, vhost_id, session.c->remote_addr); if (pconn != NULL) { pr_trace_msg(trace_channel, 11, "%s policy: selected backend '%.100s' for host '%s'", proxy_reverse_policy_name(policy_id), proxy_conn_get_uri(pconn), pr_netaddr_get_ipstr(session.c->remote_addr)); } break; default: errno = ENOSYS; return NULL; } if (backend_id != NULL) { *backend_id = idx; } return pconn; } static int reverse_redis_policy_update_backend(pool *p, void *redis, int policy_id, unsigned vhost_id, int backend_idx, int conn_incr, long connect_ms) { int res = 0, xerrno = 0; /* If our ReverseConnectPolicy is one of PerUser, PerGroup, or PerHost, * we can skip this step: those policies do not use the connection count/time. * This also helps avoid contention under load for these policies. */ if (proxy_reverse_policy_is_sticky(policy_id) == TRUE) { pr_trace_msg(trace_channel, 17, "sticky policy %s does not require updates, skipping", proxy_reverse_policy_name(policy_id)); return 0; } /* TODO: Right now, we simply overwrite/track the very latest connect ms. * But this could unfairly skew policies such as LeastResponseTime, as when * the server in question had higher latency for that particular connection, * due to e.g. OCSP response cache expiration. * * Another way would to be average the given connect ms with the previous * one (if present), and store that. Something to ponder for the future. */ switch (policy_id) { case PROXY_REVERSE_CONNECT_POLICY_LEAST_CONNS: res = reverse_redis_leastconns_update(p, redis, vhost_id, backend_idx, conn_incr, connect_ms); xerrno = errno; break; case PROXY_REVERSE_CONNECT_POLICY_LEAST_RESPONSE_TIME: res = reverse_redis_leastresponsetime_update(p, redis, vhost_id, backend_idx, conn_incr, connect_ms); xerrno = errno; break; default: res = 0; break; } errno = xerrno; return res; } static int reverse_redis_policy_used_backend(pool *p, void *redis, int policy_id, unsigned int vhost_id, int backend_idx) { int res, xerrno = 0; switch (policy_id) { case PROXY_REVERSE_CONNECT_POLICY_RANDOM: res = 0; break; case PROXY_REVERSE_CONNECT_POLICY_ROUND_ROBIN: res = reverse_redis_roundrobin_used(p, redis, vhost_id, backend_idx); xerrno = errno; break; case PROXY_REVERSE_CONNECT_POLICY_SHUFFLE: res = reverse_redis_shuffle_used(p, redis, vhost_id, backend_idx); xerrno = errno; break; case PROXY_REVERSE_CONNECT_POLICY_LEAST_CONNS: res = reverse_redis_leastconns_used(p, redis, vhost_id, backend_idx); xerrno = errno; break; case PROXY_REVERSE_CONNECT_POLICY_LEAST_RESPONSE_TIME: res = reverse_redis_leastresponsetime_used(p, redis, vhost_id, backend_idx); xerrno = errno; break; case PROXY_REVERSE_CONNECT_POLICY_PER_USER: res = reverse_redis_peruser_used(p, redis, vhost_id, backend_idx); xerrno = errno; break; case PROXY_REVERSE_CONNECT_POLICY_PER_GROUP: res = reverse_redis_pergroup_used(p, redis, vhost_id, backend_idx); xerrno = errno; break; case PROXY_REVERSE_CONNECT_POLICY_PER_HOST: res = reverse_redis_perhost_used(p, redis, vhost_id, backend_idx); xerrno = errno; break; default: xerrno = ENOSYS; res = -1; break; } errno = xerrno; return res; } static void *reverse_redis_init(pool *p, const char *tables_path, int flags) { int xerrno = 0; pr_redis_t *redis; (void) tables_path; (void) flags; redis = pr_redis_conn_new(p, &proxy_module, 0); xerrno = errno; if (redis == NULL) { (void) pr_log_pri(PR_LOG_NOTICE, MOD_PROXY_VERSION ": error opening Redis connection: %s", strerror(xerrno)); errno = xerrno; return NULL; } (void) pr_redis_conn_set_namespace(redis, &proxy_module, redis_prefix, redis_prefixsz); return redis; } static int reverse_redis_close(pool *p, void *redis) { if (redis != NULL) { if (pr_redis_conn_close(redis) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error closing Redis connection: %s", strerror(errno)); } } return 0; } static void *reverse_redis_open(pool *p, const char *tables_path, array_header *backends) { int xerrno = 0; pr_redis_t *redis; redis = pr_redis_conn_new(p, &proxy_module, 0); xerrno = errno; if (redis == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error opening Redis connection: %s", strerror(xerrno)); errno = xerrno; return NULL; } (void) pr_redis_conn_set_namespace(redis, &proxy_module, redis_prefix, redis_prefixsz); redis_backends = backends; return redis; } int proxy_reverse_redis_as_datastore(struct proxy_reverse_datastore *ds, void *ds_data, size_t ds_datasz) { if (ds == NULL) { errno = EINVAL; return -1; } ds->policy_init = reverse_redis_policy_init; ds->policy_next_backend = reverse_redis_policy_next_backend; ds->policy_used_backend = reverse_redis_policy_used_backend; ds->policy_update_backend = reverse_redis_policy_update_backend; ds->init = reverse_redis_init; ds->open = reverse_redis_open; ds->close = reverse_redis_close; redis_prefix = ds_data; redis_prefixsz = ds_datasz; return 0; } proftpd-mod_proxy-0.9.5/lib/proxy/session.c000066400000000000000000000214151475737016700210230ustar00rootroot00000000000000/* * ProFTPD - mod_proxy session routines * Copyright (c) 2012-2024 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "proxy/session.h" static const char *trace_channel = "proxy.session"; const struct proxy_session *proxy_session_alloc(pool *p) { pool *sess_pool; struct proxy_session *proxy_sess; if (p == NULL) { errno = EINVAL; return NULL; } sess_pool = make_sub_pool(p); pr_pool_tag(sess_pool, "Proxy Session pool"); proxy_sess = pcalloc(sess_pool, sizeof(struct proxy_session)); proxy_sess->pool = sess_pool; /* This will be configured by the ProxySourceAddress directive, if present. */ proxy_sess->src_addr = NULL; proxy_session_reset_dataxfer(proxy_sess); /* This will be configured by the ProxyDataTransferPolicy directive, if * present. */ proxy_sess->dataxfer_policy = PROXY_SESS_DATA_TRANSFER_POLICY_DEFAULT; /* Fill in the defaults for the session members. */ proxy_sess->connect_timeout = -1; proxy_sess->connect_timerno = -1; proxy_sess->linger_timeout = -1; proxy_sess->use_ftp = TRUE; proxy_sess->use_ssh = FALSE; return proxy_sess; } int proxy_session_free(pool *p, const struct proxy_session *proxy_sess) { conn_t *conn; struct proxy_session *sess; if (p == NULL || proxy_sess == NULL) { errno = EINVAL; return -1; } /* Close any open connections. */ sess = (struct proxy_session *) proxy_sess; conn = proxy_sess->frontend_data_conn; if (conn != NULL) { pr_inet_close(p, conn); sess->frontend_data_conn = session.d = NULL; } conn = proxy_sess->backend_ctrl_conn; if (conn != NULL) { pr_inet_close(p, conn); sess->backend_ctrl_conn = NULL; } conn = proxy_sess->backend_data_conn; if (conn != NULL) { pr_inet_close(p, conn); sess->backend_data_conn = NULL; } destroy_pool(proxy_sess->pool); return 0; } int proxy_session_reset_dataxfer(struct proxy_session *proxy_sess) { if (proxy_sess == NULL) { errno = EINVAL; return -1; } if (proxy_sess->dataxfer_pool != NULL) { destroy_pool(proxy_sess->dataxfer_pool); } proxy_sess->dataxfer_pool = make_sub_pool(proxy_sess->pool); pr_pool_tag(proxy_sess->dataxfer_pool, "Proxy Session Data Transfer pool"); return 0; } int proxy_session_check_password(pool *p, const char *user, const char *passwd) { int res; pr_trace_msg(trace_channel, 18, "checking password for user '%s'", user); res = pr_auth_authenticate(p, user, passwd); switch (res) { case PR_AUTH_OK: break; case PR_AUTH_NOPWD: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "password authentication for user '%s' failed: No such user", user); pr_log_auth(PR_LOG_NOTICE, "USER %s (Login failed): No such user found", user); errno = ENOENT; return -1; case PR_AUTH_BADPWD: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "password authentication for user '%s' failed: Incorrect password", user); pr_log_auth(PR_LOG_NOTICE, "USER %s (Login failed): Incorrect password", user); errno = EACCES; return -1; case PR_AUTH_AGEPWD: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "password authentication for user '%s' failed: Password expired", user); pr_log_auth(PR_LOG_NOTICE, "USER %s (Login failed): Password expired", user); errno = EPERM; return -1; case PR_AUTH_DISABLEDPWD: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "password authentication for user '%s' failed: Account disabled", user); pr_log_auth(PR_LOG_NOTICE, "USER %s (Login failed): Account disabled", user); errno = EPERM; return -1; default: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unknown authentication value (%d), returning error", res); errno = EINVAL; return -1; } return 0; } int proxy_session_setup_env(pool *p, const char *user, int flags) { struct passwd *pw; config_rec *c; int i, res = 0, xerrno = 0; const char *xferlog = NULL; if (p == NULL || user == NULL) { errno = EINVAL; return -1; } session.hide_password = TRUE; /* Note: the given user name may not be known locally on the proxy; thus * having pr_auth_getpwnam() returning NULL here is not an unexpected * use case. */ pw = pr_auth_getpwnam(p, user); if (pw != NULL) { if (pw->pw_uid == PR_ROOT_UID) { int root_login = FALSE; pr_event_generate("mod_auth.root-login", NULL); c = find_config(main_server->conf, CONF_PARAM, "RootLogin", FALSE); if (c != NULL) { root_login = *((int *) c->argv[0]); } if (root_login == FALSE) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "root login attempted, denied by RootLogin configuration"); pr_log_auth(PR_LOG_NOTICE, "SECURITY VIOLATION: Root login attempted"); return -1; } pr_log_auth(PR_LOG_WARNING, "ROOT proxy login successful"); } res = pr_auth_is_valid_shell(main_server->conf, pw->pw_shell); if (res == FALSE) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "authentication for user '%s' failed: Invalid shell", user); pr_log_auth(PR_LOG_NOTICE, "USER %s (Login failed): Invalid shell: '%s'", user, pw->pw_shell); errno = EPERM; return -1; } res = pr_auth_banned_by_ftpusers(main_server->conf, pw->pw_name); if (res == TRUE) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "authentication for user '%s' failed: User in " PR_FTPUSERS_PATH, user); pr_log_auth(PR_LOG_NOTICE, "USER %s (Login failed): User in " PR_FTPUSERS_PATH, pw->pw_name); errno = EPERM; return -1; } session.user = pstrdup(p, pw->pw_name); session.group = pstrdup(p, pr_auth_gid2name(p, pw->pw_gid)); session.login_uid = pw->pw_uid; session.login_gid = pw->pw_gid; } else { session.user = pstrdup(session.pool, user); /* XXX What should session.group, session.login_uid, session.login_gid * be? Kept as is? */ } if (session.gids == NULL && session.groups == NULL) { res = pr_auth_getgroups(p, session.user, &session.gids, &session.groups); if (res < 1 && errno != ENOENT) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "no supplemental groups found for user '%s'", session.user); } } if (flags & PROXY_SESSION_FL_CHECK_LOGIN_ACL) { int login_acl; login_acl = login_check_limits(main_server->conf, FALSE, TRUE, &i); if (!login_acl) { pr_log_auth(PR_LOG_NOTICE, "USER %s (Login failed): Limit configuration " "denies login", user); return -1; } } /* XXX Will users want wtmp logging for a proxy login? */ session.wtmp_log = FALSE; c = find_config(main_server->conf, CONF_PARAM, "TransferLog", FALSE); if (c == NULL) { xferlog = PR_XFERLOG_PATH; } else { xferlog = c->argv[0]; } PRIVS_ROOT if (strcasecmp(xferlog, "none") == 0) { xferlog_open(NULL); } else { xferlog_open(xferlog); } res = xerrno = 0; if (pw != NULL) { res = set_groups(p, pw->pw_gid, session.gids); xerrno = errno; } PRIVS_RELINQUISH if (res < 0) { pr_log_pri(PR_LOG_WARNING, "unable to set process groups: %s", strerror(xerrno)); } session.proc_prefix = pstrdup(session.pool, session.c->remote_name); session.sf_flags = 0; pr_scoreboard_entry_update(session.pid, PR_SCORE_USER, session.user, PR_SCORE_CWD, pr_fs_getcwd(), NULL); if (session.group != NULL) { session.group = pstrdup(session.pool, session.group); } if (session.groups != NULL) { session.groups = copy_array_str(session.pool, session.groups); } proxy_sess_state |= PROXY_SESS_STATE_PROXY_AUTHENTICATED; pr_timer_remove(PR_TIMER_LOGIN, ANY_MODULE); return 0; } proftpd-mod_proxy-0.9.5/lib/proxy/ssh.c000066400000000000000000000701241475737016700201360ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH implementation * Copyright (c) 2021-2022 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "proxy/conn.h" #include "proxy/netio.h" #include "proxy/reverse.h" #include "proxy/session.h" #include "proxy/ssh.h" #include "proxy/ssh/ssh2.h" #include "proxy/ssh/msg.h" #include "proxy/ssh/auth.h" #include "proxy/ssh/db.h" #include "proxy/ssh/redis.h" #include "proxy/ssh/crypto.h" #include "proxy/ssh/packet.h" #include "proxy/ssh/interop.h" #include "proxy/ssh/kex.h" #include "proxy/ssh/keys.h" #include "proxy/ssh/cipher.h" #include "proxy/ssh/mac.h" #include "proxy/ssh/utf8.h" #if defined(PR_USE_OPENSSL) #include #include #include static const char *ssh_tables_path = NULL; static struct proxy_ssh_datastore ssh_ds; static const char *ssh_client_version = PROXY_SSH_ID_DEFAULT_STRING; static const char *ssh_server_version = NULL; static const char *trace_channel = "proxy.ssh"; /* The number of packets to handle, while polling the backend connection, is * tricky. Too many, and we risk stalling the frontend client. Too few, * and we risk losing backend packets (and deadlocking the frontend client). * * This is most noticeable when most of the packets flow one way during the * session, as for SFTP/SCP uploads/downloads. * * With the use of packet_mpoll(), we set this number quite high; perhaps * this limit should be removed altogether? */ #define MAX_POLL_PACKETS 5000 static unsigned long ssh_opts = 0UL; static void ssh_ssh2_read_poll_ev(const void *, void *); static int ssh_get_server_version(pool *p, const struct proxy_session *proxy_sess) { int res; /* 255 is the RFC-defined maximum banner/ID string size */ char buf[256], *banner = NULL; size_t buflen = 0; /* Read server version. This looks ugly, reading one byte at a time. * It is necessary, though. The banner sent by the server is not of any * guaranteed length. The server might also send the next SSH packet in * the exchange, such that both messages are in the socket buffer. If * we read too much of the banner, we'll read into the KEXINIT, for example, * and cause problems later. */ while (TRUE) { register unsigned int i; int bad_proto = FALSE; pr_signals_handle(); memset(buf, '\0', sizeof(buf)); for (i = 0; i < sizeof(buf) - 1; i++) { res = proxy_ssh_packet_conn_read(proxy_sess->backend_ctrl_conn, &buf[i], 1, 0); while (res <= 0) { int xerrno = errno; if (xerrno == EINTR) { pr_signals_handle(); res = proxy_ssh_packet_conn_read(proxy_sess->backend_ctrl_conn, &buf[i], 1, 0); continue; } if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error reading from server rfd %d: %s", proxy_sess->backend_ctrl_conn->rfd, strerror(xerrno)); } errno = xerrno; return res; } /* We continue reading until the server has sent the terminating * CRLF sequence. */ if (buf[i] == '\r') { buf[i] = '\0'; continue; } if (buf[i] == '\n') { buf[i] = '\0'; break; } } if (i == sizeof(buf)-1) { bad_proto = TRUE; } else { buf[sizeof(buf)-1] = '\0'; buflen = strlen(buf); } /* If the line does not begin with "SSH-2.0-", skip it. RFC4253, Section * 4.2 does not specify what should happen if the server sends data * other than the proper version string initially. * * If we have been configured for compatibility with old protocol * implementations, check for "SSH-1.99-" as well. * * OpenSSH simply disconnects the server after saying "Protocol mismatch" * if the server's version string does not begin with "SSH-2.0-" * (or "SSH-1.99-"). Works for me. */ if (bad_proto == FALSE) { if (strncmp(buf, "SSH-2.0-", 8) != 0) { bad_proto = TRUE; if (proxy_opts & PROXY_OPT_SSH_OLD_PROTO_COMPAT) { if (strncmp(buf, "SSH-1.99-", 9) == 0) { if (buflen == 9) { /* The client sent ONLY "SSH-1.99-". OpenSSH handles this as a * "Protocol mismatch", so shall we. */ bad_proto = TRUE; } else { banner = buf + 9; bad_proto = FALSE; } } } } else { if (buflen == 8) { /* The client sent ONLY "SSH-2.0-". OpenSSH handles this as a * "Protocol mismatch", so shall we. */ bad_proto = TRUE; } else { banner = buf + 8; } } } if (banner != NULL) { char *k, *v; k = pstrdup(session.pool, "PROXY_SSH_SERVER_BANNER"); v = pstrdup(session.pool, banner); pr_env_unset(session.pool, k); pr_env_set(session.pool, k, v); (void) pr_table_add(session.notes, k, v, 0); } if (bad_proto) { const char *errstr = "Protocol mismatch.\n"; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "Bad protocol version '%.100s' from %s", buf, pr_netaddr_get_ipstr(proxy_sess->backend_ctrl_conn->remote_addr)); if (write(proxy_sess->backend_ctrl_conn->wfd, errstr, strlen(errstr)) < 0) { pr_trace_msg(trace_channel, 9, "error sending 'Protocol mismatch' message to server: %s", strerror(errno)); } errno = EINVAL; return -1; } break; } ssh_server_version = pstrdup(p, buf); (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "received server version '%s'", ssh_server_version); if (proxy_ssh_interop_handle_version(session.pool, proxy_sess, ssh_server_version) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error checking server version '%s' for interoperability: %s", ssh_server_version, strerror(errno)); } return 0; } /* Event listeners */ static void ssh_restart_ev(const void *event_data, void *user_data) { /* Clear the host keys. */ proxy_ssh_keys_free(); /* Clear the client banner regexes. */ proxy_ssh_interop_free(); } static int ssh_handle_kexinit(pool *p, struct proxy_session *proxy_sess) { int res; if (proxy_opts & PROXY_OPT_SSH_PESSIMISTIC_KEXINIT) { /* If we are being pessimistic, we will send our version string to the * server now, and send our KEXINIT message later. */ res = proxy_ssh_packet_send_version(proxy_sess->backend_ctrl_conn); } else { /* If we are being optimistic, we can reduce the connection latency * by sending our KEXINIT message now; this will have the server version * string automatically prepended. */ res = proxy_ssh_kex_send_first_kexinit(session.pool, proxy_sess); } if (res < 0) { return -1; } /* Set the initial timeout for reading packets from servers. Using * a value of -1 sets the default timeout value (i.e. TimeoutIdle). */ proxy_ssh_packet_set_poll_timeout(-1, 0); res = ssh_get_server_version(proxy_pool, proxy_sess); if (res < 0) { return -1; } res = proxy_ssh_kex_init(session.pool, ssh_client_version, ssh_server_version); if (res < 0) { /* XXX Should we disconnect here? */ } /* If we didn't send our KEXINIT earlier, send it now. */ if (proxy_opts & PROXY_OPT_SSH_PESSIMISTIC_KEXINIT) { res = proxy_ssh_kex_send_first_kexinit(session.pool, proxy_sess); if (res < 0) { return -1; } } return 0; } static void ssh_handle_kex(pool *p, struct proxy_session *proxy_sess) { while (TRUE) { int res; pr_signals_handle(); if (proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_KEX) { /* We're done! */ break; } res = proxy_ssh_packet_process(proxy_pool, proxy_sess); if (res < 0) { destroy_pool(p); pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION, NULL); } } } static void ssh_ssh2_auth_completed_ev(const void *event_data, void *user_data) { int res; struct proxy_session *proxy_sess; const char *connect_data, *hook_symbol, *user; cmdtable *sftp_cmdtab; pool *tmp_pool; cmd_rec *cmd; modret_t *result; struct proxy_ssh_packet *pkt = NULL; unsigned char *buf, *ptr; uint32_t bufsz, buflen, len = 0; module m; proxy_sess = user_data; m.name = "mod_proxy"; tmp_pool = make_sub_pool(session.pool); pr_pool_tag(tmp_pool, "Proxy SSH Auth completed pool"); /* Look up the hook for setting the callback for writing packets to the * frontend client; we'll need it later. */ hook_symbol = "sftp_get_packet_write"; sftp_cmdtab = pr_stash_get_symbol2(PR_SYM_HOOK, hook_symbol, NULL, NULL, NULL); if (sftp_cmdtab == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to find SFTP hook symbol '%s'", hook_symbol); destroy_pool(tmp_pool); pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION, NULL); } cmd = pr_cmd_alloc(tmp_pool, 1, NULL); result = pr_module_call(sftp_cmdtab->m, sftp_cmdtab->handler, cmd); if (result == NULL || MODRET_ISERROR(result)) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error getting SSH packet writer"); pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION, NULL); } res = proxy_ssh_auth_set_frontend_success_handle(tmp_pool, NULL); if (res < 0) { destroy_pool(tmp_pool); pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION, NULL); } user = pr_table_get(session.notes, "mod_auth.orig-user", NULL); res = proxy_session_setup_env(proxy_pool, user, PROXY_SESSION_FL_CHECK_LOGIN_ACL); if (res < 0) { destroy_pool(tmp_pool); pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_CONFIG_ACL, NULL); } /* The "connect data" we use here depends on the sticky ConnectPolicy in * effect. */ connect_data = user; if (proxy_reverse_get_connect_policy() == PROXY_REVERSE_CONNECT_POLICY_PER_GROUP) { connect_data = session.group; } res = proxy_reverse_connect(proxy_pool, proxy_sess, connect_data); if (res < 0) { destroy_pool(tmp_pool); pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION, NULL); } res = ssh_handle_kexinit(tmp_pool, proxy_sess); if (res < 0) { destroy_pool(tmp_pool); pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION, NULL); } proxy_ssh_auth_init(proxy_pool); /* Now we need to run the KEXINIT with the backend server to completion, * but not more than that. */ ssh_handle_kex(tmp_pool, proxy_sess); /* We now need to run the service, auth portions to completion; for these * we act as if we were the frontend client sending packets. We'll want * to reuse as much of our proxying machinery as possible, but we also need * to ensure that that machinery does not actually send packets to the * frontend client. Thus we temporarily use a null packet handler here. */ proxy_ssh_packet_set_frontend_packet_write(NULL); pkt = proxy_ssh_packet_create(tmp_pool); /* Note: It is important that this packet NOT come from mod_proxy. */ pkt->m = &m; bufsz = buflen = 1024; ptr = buf = palloc(pkt->pool, bufsz); len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_SERVICE_REQUEST); len += proxy_ssh_msg_write_string(&buf, &buflen, "ssh-userauth"); pkt->payload = ptr; pkt->payload_len = len; if (proxy_ssh_packet_handle(pkt) < 0) { /* Restore the callback for writing our DISCONNECT packet to the frontend * client. */ proxy_ssh_packet_set_frontend_packet_write(result->data); pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION, NULL); } pkt = proxy_ssh_packet_create(tmp_pool); /* Note: It is important that this packet NOT come from mod_proxy. */ pkt->m = &m; bufsz = buflen = 4096; ptr = buf = palloc(pkt->pool, bufsz); len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_USER_AUTH_REQUEST); len += proxy_ssh_msg_write_string(&buf, &buflen, user); len += proxy_ssh_msg_write_string(&buf, &buflen, "ssh-connection"); len += proxy_ssh_msg_write_string(&buf, &buflen, "hostbased"); pkt->payload = ptr; pkt->payload_len = len; if (proxy_ssh_packet_handle(pkt) < 0) { /* Restore the callback for writing our DISCONNECT packet to the frontend * client. */ proxy_ssh_packet_set_frontend_packet_write(result->data); pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION, NULL); } /* Now we should be successfully authenticated to the backend server. */ if (!(proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_AUTH)) { destroy_pool(tmp_pool); pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION, NULL); } res = proxy_ssh_packet_set_frontend_packet_handle(tmp_pool, proxy_ssh_packet_handle); if (res < 0) { destroy_pool(tmp_pool); pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION, NULL); } proxy_ssh_packet_set_frontend_packet_write(result->data); /* Now we register for mod_sftp's read-loop, to listen for frontend and * backend packets. */ pr_event_register(&proxy_module, "mod_sftp.ssh2.read-poll", ssh_ssh2_read_poll_ev, proxy_sess); /* To trigger mod_proxy to restrict this session, now that we have * authenticated to the backend server, we generate an event as if we * were handling an FTP session. */ pr_event_generate("mod_proxy.ctrl-read", NULL); destroy_pool(tmp_pool); } static void ssh_ssh2_kex_completed_ev(const void *event_data, void *user_data) { int res; struct proxy_session *proxy_sess; const char *hook_symbol; cmdtable *sftp_cmdtab; pool *tmp_pool; cmd_rec *cmd; modret_t *result; proxy_sess = user_data; tmp_pool = make_sub_pool(session.pool); pr_pool_tag(tmp_pool, "Proxy SSH KEX completed pool"); res = proxy_ssh_packet_set_frontend_packet_handle(tmp_pool, proxy_ssh_packet_handle); if (res < 0) { destroy_pool(tmp_pool); /* XXX Should we disconnect here? */ return; } /* If we have already authenticated to the backend, then this is a rekey, * and we do NOT want to interact with the backend anymore for this event. */ if (proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_AUTH) { pr_trace_msg(trace_channel, 19, "frontend-initiated rekeying COMPLETED"); /* Now we register for mod_sftp's read-loop, to listen for frontend and * backend packets. */ pr_event_register(&proxy_module, "mod_sftp.ssh2.read-poll", ssh_ssh2_read_poll_ev, proxy_sess); destroy_pool(tmp_pool); return; } hook_symbol = "sftp_get_packet_write"; sftp_cmdtab = pr_stash_get_symbol2(PR_SYM_HOOK, hook_symbol, NULL, NULL, NULL); if (sftp_cmdtab == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to find SFTP hook symbol '%s'", hook_symbol); destroy_pool(tmp_pool); /* XXX Should we disconnect here? */ return; } cmd = pr_cmd_alloc(tmp_pool, 1, NULL); result = pr_module_call(sftp_cmdtab->m, sftp_cmdtab->handler, cmd); if (result == NULL || MODRET_ISERROR(result)) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error getting SSH packet writer"); /* XXX Should we disconnect here? */ } /* Connecting to the selected backend server happened earlier (right?), * in proxy_reverse_sess_init(). So now we start our SSH session with * the selected backend host. * * Note, though, that if the UseReverseProxyAuth ProxyOption was configured, * we may not have connected to the backend server yet, as the frontend * client might not have authenticated; we have only completed the frontend * KEX at this point. So how do we knkow if we need to connect to the * backend server here? * * ProxyOptions UseReverseProxyAuth * ProxyReverseConnectPolicy NOT PerUser/PerGroup */ if (!(proxy_sess_state & PROXY_SESS_STATE_CONNECTED) && proxy_reverse_use_proxy_auth() == TRUE) { int connect_policy_id; connect_policy_id = proxy_reverse_get_connect_policy(); switch (connect_policy_id) { case PROXY_REVERSE_CONNECT_POLICY_PER_GROUP: case PROXY_REVERSE_CONNECT_POLICY_PER_USER: break; default: { res = proxy_reverse_connect(proxy_pool, proxy_sess, NULL); if (res < 0) { destroy_pool(tmp_pool); pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION, NULL); } } } } res = ssh_handle_kexinit(tmp_pool, proxy_sess); if (res < 0) { destroy_pool(tmp_pool); pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION, NULL); } proxy_ssh_auth_init(proxy_pool); /* Now we need to run the KEXINIT with the backend server to completion, * but not more than that. */ ssh_handle_kex(tmp_pool, proxy_sess); proxy_ssh_packet_set_frontend_packet_write(result->data); /* Now we register for mod_sftp's read-loop, to listen for frontend and * backend packets. */ pr_event_register(&proxy_module, "mod_sftp.ssh2.read-poll", ssh_ssh2_read_poll_ev, proxy_sess); /* To trigger mod_proxy to restrict this session, now that we have * authenticated to the backend server, we generate an event as if we * were handling an FTP session. */ pr_event_generate("mod_proxy.ctrl-read", NULL); destroy_pool(tmp_pool); } static void ssh_ssh2_read_poll_ev(const void *event_data, void *user_data) { const struct proxy_session *proxy_sess; int poll_timeout_secs, res; unsigned long poll_timeout_ms; unsigned int npackets = 0, poll_attempts; pool *tmp_pool; /* We only want to do polling for, and processing of, backend packets once * our SSH session has reached a necessary start (SSH_HAVE_AUTH in * particular). */ if (!(proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_AUTH)) { return; } proxy_sess = user_data; tmp_pool = make_sub_pool(session.pool); pr_pool_tag(tmp_pool, "Proxy SSH read-poll pool"); proxy_ssh_packet_get_poll_attempts(&poll_attempts); proxy_ssh_packet_get_poll_timeout(&poll_timeout_secs, &poll_timeout_ms); proxy_ssh_packet_set_poll_attempts(2); proxy_ssh_packet_set_poll_timeout(0, 100); /* We try to process multiple backend packets in a loop, if we can. */ res = proxy_ssh_packet_conn_mpoll(proxy_sess->frontend_ctrl_conn, proxy_sess->backend_ctrl_conn, PROXY_SSH_PACKET_IO_READ); pr_trace_msg(trace_channel, 10, "read-mpoll returned %d", res); while (res == 1 && npackets < MAX_POLL_PACKETS) { pr_signals_handle(); res = proxy_ssh_packet_process(tmp_pool, proxy_sess); if (res < 0) { pr_trace_msg(trace_channel, 2, "error processing backend packet during frontend read poll: %s", strerror(errno)); } npackets++; res = proxy_ssh_packet_conn_mpoll(proxy_sess->frontend_ctrl_conn, proxy_sess->backend_ctrl_conn, PROXY_SSH_PACKET_IO_READ); } proxy_ssh_packet_set_poll_attempts(poll_attempts); proxy_ssh_packet_set_poll_timeout(poll_timeout_secs, poll_timeout_ms); destroy_pool(tmp_pool); } #endif /* PR_USE_OPENSSL */ int proxy_ssh_init(pool *p, const char *tables_path, int flags) { #if defined(PR_USE_OPENSSL) int res; config_rec *c; memset(&ssh_ds, 0, sizeof(ssh_ds)); switch (proxy_datastore) { case PROXY_DATASTORE_REDIS: res = proxy_ssh_redis_as_datastore(&ssh_ds, proxy_datastore_data, proxy_datastore_datasz); break; case PROXY_DATASTORE_SQLITE: res = proxy_ssh_db_as_datastore(&ssh_ds, proxy_datastore_data, proxy_datastore_datasz); break; default: res = -1; errno = EINVAL; break; } if (res < 0) { return -1; } res = (ssh_ds.init)(p, tables_path, flags); if (res < 0) { return -1; } if (pr_module_exists("mod_sftp.c") == FALSE && pr_module_exists("mod_tls.c") == FALSE) { #if OPENSSL_VERSION_NUMBER < 0x10100000L OPENSSL_config(NULL); #endif /* prior to OpenSSL-1.1.x */ SSL_load_error_strings(); SSL_library_init(); ERR_load_crypto_strings(); OpenSSL_add_all_algorithms(); } ssh_tables_path = pstrdup(proxy_pool, tables_path); /* Initialize SSH API */ proxy_ssh_interop_init(); proxy_ssh_cipher_init(); proxy_ssh_mac_init(); proxy_ssh_utf8_init(); pr_event_register(&proxy_module, "core.postparse", ssh_restart_ev, NULL); /* Note that this function is called from mod_proxy's "core.postparse" event * listener. So we do, now, anything that we would have done in our own * postparse event listener. */ c = find_config(main_server->conf, CONF_PARAM, "ProxySFTPPassPhraseProvider", FALSE); if (c != NULL) { proxy_ssh_keys_set_passphrase_provider(c->argv[0]); } proxy_ssh_keys_get_passphrases(); #endif /* PR_USE_OPENSSL */ return 0; } int proxy_ssh_free(pool *p) { if (p == NULL) { errno = EINVAL; return -1; } #if defined(PR_USE_OPENSSL) if (ssh_ds.dsh != NULL) { int res; res = (ssh_ds.close)(p, ssh_ds.dsh); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error closing datastore: %s", strerror(errno)); } ssh_ds.dsh = NULL; } pr_event_unregister(&proxy_module, "core.restart", ssh_restart_ev); proxy_ssh_interop_free(); proxy_ssh_keys_free(); proxy_ssh_cipher_free(); proxy_ssh_mac_free(); proxy_ssh_utf8_free(); proxy_ssh_crypto_free(0); #endif /* PR_USE_OPENSSL */ return 0; } int proxy_ssh_sess_init(pool *p, struct proxy_session *proxy_sess, int flags) { #if defined(PR_USE_OPENSSL) int connect_policy_id = PROXY_REVERSE_CONNECT_POLICY_ROUND_ROBIN; int sftp_engine, proxy_role = 0, verify_server, xerrno = 0; config_rec *c; if (p == NULL) { errno = EINVAL; return -1; } c = find_config(main_server->conf, CONF_PARAM, "SFTPEngine", FALSE); if (c == NULL) { return 0; } sftp_engine = *((int *) c->argv[0]); if (sftp_engine != TRUE) { return 0; } /* We currently only support SSH reverse proxying, not forward proxying, * and therefore need to check the configured ProxyRole. */ c = find_config(main_server->conf, CONF_PARAM, "ProxyRole", FALSE); if (c != NULL) { proxy_role = *((int *) c->argv[0]); } /* Sadly, we cannot use the PROXY_ROLE constant here, since it is scoped * only to mod_proxy.c. */ if (proxy_role != 1) { pr_trace_msg(trace_channel, 1, "unable to support non-reverse ProxyRole for SFTP"); return 0; } proxy_sess->use_ftp = FALSE; proxy_sess->use_ssh = TRUE; pr_response_block(TRUE); c = find_config(main_server->conf, CONF_PARAM, "ServerIdent", FALSE); if (c != NULL) { if (*((unsigned char *) c->argv[0]) == FALSE) { /* The admin configured "ServerIdent off". Set the version string to * just "mod_proxy", and that's it, no version. */ ssh_client_version = pstrcat(proxy_pool, PROXY_SSH_ID_PREFIX, "mod_proxy", NULL); proxy_ssh_packet_set_version(ssh_client_version); } else { /* The admin configured "ServerIdent on", and possibly some custom * string. */ if (c->argc > 1) { ssh_client_version = pstrcat(proxy_pool, PROXY_SSH_ID_PREFIX, c->argv[1], NULL); proxy_ssh_packet_set_version(ssh_client_version); } } } c = find_config(main_server->conf, CONF_PARAM, "ProxySFTPOptions", FALSE); while (c != NULL) { unsigned long opts = 0; pr_signals_handle(); opts = *((unsigned long *) c->argv[0]); ssh_opts |= opts; c = find_config_next(c, c->next, CONF_PARAM, "ProxySFTPOptions", FALSE); } proxy_opts |= ssh_opts; c = find_config(main_server->conf, CONF_PARAM, "ProxySFTPHostKey", FALSE); while (c != NULL) { const char *path; pr_signals_handle(); path = c->argv[0]; if (proxy_ssh_keys_get_hostkey(p, path) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error loading hostkey '%s', skipping key", path); } c = find_config_next(c, c->next, CONF_PARAM, "ProxySFTPHostKey", FALSE); } c = find_config(main_server->conf, CONF_PARAM, "ProxySFTPVerifyServer", FALSE); if (c != NULL) { verify_server = *((int *) c->argv[0]); } else { verify_server = FALSE; } PRIVS_ROOT ssh_ds.dsh = (ssh_ds.open)(proxy_pool, ssh_tables_path, ssh_opts); xerrno = errno; PRIVS_RELINQUISH if (ssh_ds.dsh == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error opening SSH datastore: %s", strerror(xerrno)); errno = xerrno; return -1; } proxy_ssh_kex_sess_init(p, &ssh_ds, verify_server); /* For PerUser/PerGroup/PerHost connection policies, we pay attention to * the mod_sftp events generated for successful authentication, otherwise * we use the successful KEX event. */ c = find_config(main_server->conf, CONF_PARAM, "ProxyReverseConnectPolicy", FALSE); if (c != NULL) { connect_policy_id = *((int *) c->argv[0]); } if (proxy_reverse_policy_is_sticky(connect_policy_id) == TRUE && connect_policy_id != PROXY_REVERSE_CONNECT_POLICY_PER_HOST) { /* PerUser/PerGroup connect policies REQUIRE that we use "hostbased" * authentication to the backend server; make sure that ProxySFTPHostKeys * have been configured for this. */ if (proxy_ssh_keys_have_hostkey(PROXY_SSH_KEY_UNKNOWN) != 0) { /* We have no configured hostkeys, thus cannot use "hostbased" * authentication; return FAILURE to the frontend client. */ (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to handle '%s' ProxyReverseConnectPolicy: " "no ProxySFTPHostKeys configured", proxy_reverse_policy_name(connect_policy_id)); errno = EPERM; return -1; } pr_event_register(&proxy_module, "mod_sftp.ssh2.auth-hostbased", ssh_ssh2_auth_completed_ev, proxy_sess); pr_event_register(&proxy_module, "mod_sftp.ssh2.auth-kbdint", ssh_ssh2_auth_completed_ev, proxy_sess); pr_event_register(&proxy_module, "mod_sftp.ssh2.auth-password", ssh_ssh2_auth_completed_ev, proxy_sess); pr_event_register(&proxy_module, "mod_sftp.ssh2.auth-publickey", ssh_ssh2_auth_completed_ev, proxy_sess); } else { pr_event_register(&proxy_module, "mod_sftp.ssh2.kex.completed", ssh_ssh2_kex_completed_ev, proxy_sess); } if (proxy_ssh_auth_sess_init(p, proxy_sess) < 0) { return -1; } #endif /* PR_USE_OPENSSL */ return 0; } int proxy_ssh_sess_free(pool *p) { if (p == NULL) { errno = EINVAL; return -1; } #if defined(PR_USE_OPENSSL) ssh_opts = 0UL; if (ssh_ds.dsh != NULL) { (void) (ssh_ds.close)(p, ssh_ds.dsh); ssh_ds.dsh = NULL; } proxy_ssh_kex_sess_free(); pr_event_unregister(&proxy_module, "mod_sftp.ssh2.auth-hostbased", ssh_ssh2_auth_completed_ev); pr_event_unregister(&proxy_module, "mod_sftp.ssh2.auth-kbdint", ssh_ssh2_auth_completed_ev); pr_event_unregister(&proxy_module, "mod_sftp.ssh2.auth-password", ssh_ssh2_auth_completed_ev); pr_event_unregister(&proxy_module, "mod_sftp.ssh2.auth-publickey", ssh_ssh2_auth_completed_ev); pr_event_unregister(&proxy_module, "mod_sftp.ssh2.kex.completed", ssh_ssh2_kex_completed_ev); pr_event_unregister(&proxy_module, "mod_sftp.ssh2.read-poll", ssh_ssh2_read_poll_ev); #endif /* PR_USE_OPENSSL */ return 0; } proftpd-mod_proxy-0.9.5/lib/proxy/ssh/000077500000000000000000000000001475737016700177665ustar00rootroot00000000000000proftpd-mod_proxy-0.9.5/lib/proxy/ssh/agent.c000066400000000000000000000260171475737016700212360ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH agent support * Copyright (c) 2021-2023 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "proxy/ssh/agent.h" #include "proxy/ssh/msg.h" #if defined(PR_USE_OPENSSL) static const char *trace_channel = "proxy.ssh.agent"; /* These values from https://tools.ietf.org/html/draft-miller-ssh-agent-04 */ #define PROXY_SSH_AGENT_FAILURE 5 #define PROXY_SSH_AGENT_SUCCESS 6 #define PROXY_SSH_AGENT_REQ_IDS 11 #define PROXY_SSH_AGENT_RESP_IDS 12 #define PROXY_SSH_AGENT_REQ_SIGN_DATA 13 #define PROXY_SSH_AGENT_RESP_SIGN_DATA 14 #define PROXY_SSH_AGENT_EXTENDED_FAILURE 30 /* Error code for ssh.com's ssh-agent2 process. */ #define PROXY_SSHCOM_AGENT_FAILURE 102 /* Size of the buffer we use to talk to the agent. */ #define AGENT_REQUEST_MSGSZ 1024 /* Max size of the agent reply that we will handle. */ #define AGENT_REPLY_MAXSZ (256 * 1024) /* Max number of identities/keys we're willing to handle at one time. */ #define AGENT_MAX_KEYS 1024 /* In proxy_ssh_keys_get_clientkey(), when dealing with the key data returned * from the agent, use get_pkey_from_data() to create the EVP_PKEY. Keep * the key_data around, for signing requests to send to the agent. */ static int agent_failure(char resp_status) { int failed = FALSE; switch (resp_status) { case PROXY_SSH_AGENT_FAILURE: case PROXY_SSH_AGENT_EXTENDED_FAILURE: case PROXY_SSHCOM_AGENT_FAILURE: failed = TRUE; break; } return failed; } static unsigned char *agent_request(pool *p, int fd, const char *path, unsigned char *req, uint32_t reqlen, uint32_t *resplen) { unsigned char msg[AGENT_REQUEST_MSGSZ], *buf, *ptr; uint32_t bufsz, buflen, len = 0; size_t write_len; int res; bufsz = buflen = sizeof(msg); buf = ptr = msg; len += proxy_ssh_msg_write_int(&buf, &buflen, reqlen); /* Send the message length to the agent. */ write_len = len; res = write(fd, ptr, write_len); if (res < 0) { int xerrno = errno; pr_trace_msg(trace_channel, 3, "error sending request length to SSH agent at '%s': %s", path, strerror(xerrno)); errno = xerrno; return NULL; } /* Handle short writes. */ if ((size_t) res != write_len) { pr_trace_msg(trace_channel, 3, "short write (%d of %lu bytes sent) when talking to SSH agent at '%s'", res, (unsigned long) (write_len), path); errno = EIO; return NULL; } /* Send the message payload to the agent. */ res = write(fd, req, reqlen); if (res < 0) { int xerrno = errno; pr_trace_msg(trace_channel, 3, "error sending request payload to SSH agent at '%s': %s", path, strerror(xerrno)); errno = xerrno; return NULL; } /* Handle short writes. */ if ((uint32_t) res != reqlen) { pr_trace_msg(trace_channel, 3, "short write (%d of %lu bytes sent) when talking to SSH agent at '%s'", res, (unsigned long) reqlen, path); errno = EIO; return NULL; } /* Wait for a response from the server. */ /* XXX This needs a timeout, prevent a blocked/bad agent from stalling * the client. Maybe just set an internal timer? */ res = read(fd, msg, sizeof(uint32_t)); if (res < 0) { int xerrno = errno; pr_trace_msg(trace_channel, 3, "error reading response length from SSH agent at '%s': %s", path, strerror(xerrno)); errno = xerrno; return NULL; } /* Sanity check the returned length; we could be dealing with a buggy * client (or something else is injecting data into the Unix domain socket). * Best be conservative: if we get a response length of more than 256KB, * it's too big. (What about very long lists of keys, and/or large keys?) */ if (res > AGENT_REPLY_MAXSZ) { pr_trace_msg(trace_channel, 1, "response length (%d) from SSH agent at '%s' exceeds maximum (%lu), " "ignoring", res, path, (unsigned long) AGENT_REPLY_MAXSZ); errno = EIO; return NULL; } buf = msg; buflen = res; len = proxy_ssh_msg_read_int(p, &buf, &buflen, resplen); bufsz = buflen = *resplen; if (bufsz == 0 || bufsz > AGENT_REPLY_MAXSZ) { pr_trace_msg(trace_channel, 1, "response length (%lu) from SSH agent at '%s' exceeds maximum (%lu), " "ignoring", (unsigned long) bufsz, path, (unsigned long) AGENT_REPLY_MAXSZ); errno = EIO; return NULL; } buf = ptr = palloc(p, bufsz); buflen = 0; while (buflen != *resplen) { pr_signals_handle(); res = read(fd, buf + buflen, bufsz - buflen); if (res < 0) { int xerrno = errno; pr_trace_msg(trace_channel, 3, "error reading %d bytes of response payload from SSH agent at '%s': %s", (bufsz - buflen), path, strerror(xerrno)); errno = xerrno; return NULL; } /* XXX Handle short reads? */ buflen += res; } return ptr; } static int agent_connect(const char *path) { int fd, len, res, xerrno; struct sockaddr_un sock; memset(&sock, 0, sizeof(sock)); sock.sun_family = AF_UNIX; sstrncpy(sock.sun_path, path, sizeof(sock.sun_path)); len = sizeof(sock); fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) { xerrno = errno; pr_trace_msg(trace_channel, 3, "error opening Unix domain socket: %s", strerror(xerrno)); errno = xerrno; return -1; } if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) { pr_trace_msg(trace_channel, 3, "error setting CLOEXEC on fd %d for talking to SSH agent: %s", fd, strerror(errno)); } PRIVS_ROOT res = connect(fd, (struct sockaddr *) &sock, len); xerrno = errno; PRIVS_RELINQUISH if (res < 0) { pr_trace_msg(trace_channel, 2, "error connecting to SSH agent at '%s': %s", path, strerror(xerrno)); (void) close(fd); errno = xerrno; return -1; } return fd; } int proxy_ssh_agent_get_keys(pool *p, const char *agent_path, array_header *key_list) { register unsigned int i; int fd; unsigned char *buf, *req, *resp; uint32_t buflen, key_count, reqlen, reqsz, resplen, len = 0; unsigned char resp_status; fd = agent_connect(agent_path); if (fd < 0) { return -1; } /* Write out the request for the identities (i.e. the public keys). */ reqsz = buflen = 64; req = buf = palloc(p, reqsz); len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_AGENT_REQ_IDS); reqlen = len; resp = agent_request(p, fd, agent_path, req, reqlen, &resplen); if (resp == NULL) { int xerrno = errno; (void) close(fd); errno = xerrno; return -1; } (void) close(fd); /* Read the response from the agent. */ len = proxy_ssh_msg_read_byte(p, &resp, &resplen, &resp_status); if (agent_failure(resp_status) == TRUE) { pr_trace_msg(trace_channel, 5, "SSH agent at '%s' indicated failure (%d) for identities request", agent_path, resp_status); errno = EPERM; return -1; } if (resp_status != PROXY_SSH_AGENT_RESP_IDS) { pr_trace_msg(trace_channel, 5, "unknown response type %d from SSH agent at '%s'", resp_status, agent_path); errno = EACCES; return -1; } len = proxy_ssh_msg_read_int(p, &resp, &resplen, &key_count); if (key_count > AGENT_MAX_KEYS) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "SSH agent at '%s' returned too many keys (%lu, max %lu)", agent_path, (unsigned long) key_count, (unsigned long) AGENT_MAX_KEYS); errno = EPERM; return -1; } for (i = 0; i < key_count; i++) { unsigned char *key_data; uint32_t key_datalen; char *key_comment; struct agent_key *key; len = proxy_ssh_msg_read_int(p, &resp, &resplen, &key_datalen); len = proxy_ssh_msg_read_data(p, &resp, &resplen, key_datalen, &key_data); len = proxy_ssh_msg_read_string(p, &resp, &resplen, &key_comment); if (key_comment != NULL) { pr_trace_msg(trace_channel, 9, "SSH agent at '%s' provided comment '%s' for key #%u", agent_path, key_comment, (i + 1)); } key = pcalloc(p, sizeof(struct agent_key)); key->key_data = key_data; key->key_datalen = key_datalen; key->agent_path = pstrdup(p, agent_path); *((struct agent_key **) push_array(key_list)) = key; } pr_trace_msg(trace_channel, 9, "SSH agent at '%s' provided %lu %s", agent_path, (unsigned long) key_count, key_count != 1 ? "keys" : "key"); return 0; } const unsigned char *proxy_ssh_agent_sign_data(pool *p, const char *agent_path, const unsigned char *key_data, uint32_t key_datalen, const unsigned char *data, uint32_t datalen, uint32_t *sig_datalen, int flags) { int fd; unsigned char *buf, *req, *resp, *sig_data; uint32_t buflen, sig_flags, reqlen, reqsz, resplen, len = 0; unsigned char resp_status; fd = agent_connect(agent_path); if (fd < 0) { return NULL; } /* XXX When to set flags to OLD_SIGNATURE? */ sig_flags = 0; /* Write out the request for signing the given data. */ reqsz = buflen = 1 + key_datalen + 4 + datalen + 4 + 4; req = buf = palloc(p, reqsz); len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_AGENT_REQ_SIGN_DATA); len += proxy_ssh_msg_write_data(&buf, &buflen, key_data, key_datalen, TRUE); len += proxy_ssh_msg_write_data(&buf, &buflen, data, datalen, TRUE); len += proxy_ssh_msg_write_int(&buf, &buflen, sig_flags); reqlen = len; resp = agent_request(p, fd, agent_path, req, reqlen, &resplen); if (resp == NULL) { int xerrno = errno; (void) close(fd); errno = xerrno; return NULL; } (void) close(fd); /* Read the response from the agent. */ len = proxy_ssh_msg_read_byte(p, &resp, &resplen, &resp_status); if (agent_failure(resp_status) == TRUE) { pr_trace_msg(trace_channel, 5, "SSH agent at '%s' indicated failure (%d) for data signing request", agent_path, resp_status); errno = EPERM; return NULL; } if (resp_status != PROXY_SSH_AGENT_RESP_SIGN_DATA) { pr_trace_msg(trace_channel, 5, "unknown response type %d from SSH agent at '%s'", resp_status, agent_path); errno = EACCES; return NULL; } len = proxy_ssh_msg_read_int(p, &resp, &resplen, sig_datalen); len = proxy_ssh_msg_read_data(p, &resp, &resplen, *sig_datalen, &sig_data); return sig_data; } #endif /* PR_USE_OPENSSL */ proftpd-mod_proxy-0.9.5/lib/proxy/ssh/auth.c000066400000000000000000001074701475737016700211040ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH user authentication * Copyright (c) 2021-2022 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "proxy/ssh/ssh2.h" #include "proxy/ssh/packet.h" #include "proxy/ssh/msg.h" #include "proxy/ssh/disconnect.h" #include "proxy/ssh/interop.h" #include "proxy/ssh/auth.h" #include "proxy/ssh/crypto.h" #include "proxy/ssh/cipher.h" #include "proxy/ssh/mac.h" #include "proxy/ssh/compress.h" #include "proxy/ssh/session.h" #include "proxy/ssh/keys.h" #include "proxy/ssh/utf8.h" #if defined(PR_USE_OPENSSL) /* From response.c */ extern pr_response_t *resp_list, *resp_err_list; static pool *auth_pool = NULL; static const char *trace_channel = "proxy.ssh.auth"; static void dispatch_cmd_err(cmd_rec *cmd) { pr_response_add_err(R_530, "Login incorrect."); pr_cmd_dispatch_phase(cmd, POST_CMD_ERR, 0); pr_cmd_dispatch_phase(cmd, LOG_CMD_ERR, 0); pr_response_clear(&resp_err_list); } static int dispatch_user_cmd(pool *p, const char *orig_user, char **new_user) { cmd_rec *user_cmd; user_cmd = pr_cmd_alloc(p, 2, pstrdup(p, C_USER), orig_user); user_cmd->cmd_class = CL_AUTH|CL_SSH; user_cmd->arg = (char *) orig_user; pr_response_set_pool(user_cmd->pool); /* Dispatch these as PRE_CMDs, so that mod_delay's tactics can be used * to ameliorate any timing-based attacks. */ if (pr_cmd_dispatch_phase(user_cmd, PRE_CMD, 0) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "authentication request for user '%s' blocked by '%s' handler", orig_user, (char *) user_cmd->argv[0]); dispatch_cmd_err(user_cmd); destroy_pool(user_cmd->pool); pr_response_set_pool(NULL); return -1; } if (strcmp(orig_user, user_cmd->arg) != 0) { *new_user = pstrdup(p, user_cmd->arg); } pr_response_add(R_331, "Password required for %s", orig_user); pr_cmd_dispatch_phase(user_cmd, POST_CMD, 0); pr_cmd_dispatch_phase(user_cmd, LOG_CMD, 0); pr_response_clear(&resp_list); destroy_pool(user_cmd->pool); pr_response_set_pool(NULL); return 0; } static int dispatch_pass_cmd(pool *p, int success) { cmd_rec *pass_cmd; pass_cmd = pr_cmd_alloc(p, 1, pstrdup(p, C_PASS)); pass_cmd->cmd_class = CL_AUTH|CL_SSH; pass_cmd->arg = pstrdup(pass_cmd->pool, "(hidden)"); pr_response_set_pool(pass_cmd->pool); if (success == TRUE) { pr_cmd_dispatch_phase(pass_cmd, POST_CMD, 0); pr_cmd_dispatch_phase(pass_cmd, LOG_CMD, 0); } else { pr_cmd_dispatch_phase(pass_cmd, POST_CMD_ERR, 0); pr_cmd_dispatch_phase(pass_cmd, LOG_CMD_ERR, 0); } pr_response_clear(&resp_list); destroy_pool(pass_cmd->pool); pr_response_set_pool(NULL); return 0; } static struct proxy_ssh_packet *read_auth_packet(pool *p, const struct proxy_session *proxy_sess) { struct proxy_ssh_packet *pkt = NULL; unsigned int poll_attempts; unsigned long poll_timeout_ms; int poll_timeout_secs, res, xerrno = 0; char msg_type; proxy_ssh_packet_get_poll_attempts(&poll_attempts); proxy_ssh_packet_get_poll_timeout(&poll_timeout_secs, &poll_timeout_ms); proxy_ssh_packet_set_poll_attempts(3); proxy_ssh_packet_set_poll_timeout(0, 250); pkt = proxy_ssh_packet_create(p); res = proxy_ssh_packet_read(proxy_sess->backend_ctrl_conn, pkt); if (res < 0) { xerrno = errno; proxy_ssh_packet_set_poll_attempts(poll_attempts); proxy_ssh_packet_set_poll_timeout(poll_timeout_secs, poll_timeout_ms); destroy_pool(pkt->pool); errno = xerrno; return NULL; } proxy_ssh_packet_set_poll_attempts(poll_attempts); proxy_ssh_packet_set_poll_timeout(poll_timeout_secs, poll_timeout_ms); msg_type = proxy_ssh_packet_peek_msg_type(pkt); pr_trace_msg(trace_channel, 3, "received %s (%d) packet (from mod_%s.c)", proxy_ssh_packet_get_msg_type_desc(msg_type), msg_type, pkt->m->name); return pkt; } /* Returns 0 if the packet was completely processed, 1 if the caller should * process the packet, and -1 if the packet is invalid. */ static int process_auth_packet(struct proxy_ssh_packet *pkt, const struct proxy_session *proxy_sess) { char msg_type; int res = 0; msg_type = proxy_ssh_packet_peek_msg_type(pkt); switch (msg_type) { case PROXY_SSH_MSG_USER_AUTH_SUCCESS: case PROXY_SSH_MSG_USER_AUTH_FAILURE: res = 1; break; case PROXY_SSH_MSG_USER_AUTH_BANNER: proxy_ssh_packet_log_cmd(pkt, FALSE); proxy_ssh_packet_proxied(proxy_sess, pkt, FALSE); destroy_pool(pkt->pool); res = 0; break; case PROXY_SSH_MSG_DEBUG: case PROXY_SSH_MSG_DISCONNECT: case PROXY_SSH_MSG_IGNORE: case PROXY_SSH_MSG_UNIMPLEMENTED: proxy_ssh_packet_handle(pkt); res = 0; break; default: errno = EINVAL; res = -1; break; } return res; } static int handle_userauth_none(struct proxy_ssh_packet *pkt, const struct proxy_session *proxy_sess) { int res; const char *methods; unsigned char *buf, *ptr; uint32_t bufsz, buflen, len = 0; /* Ideally, we would query the backend server ourselves, and synthesize the * full list of available methods for the frontend client. For now, however, * return a response listing all implemented methods. */ destroy_pool(pkt->pool); pkt = proxy_ssh_packet_create(auth_pool); bufsz = buflen = 1024; ptr = buf = palloc(pkt->pool, bufsz); len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_USER_AUTH_FAILURE); if (proxy_ssh_keys_have_hostkey(PROXY_SSH_KEY_UNKNOWN) == 0) { methods = "password,keyboard-interactive,publickey,hostbased"; } else { /* If we have no configured ProxySFTPHostKeys, do not include the * "hostbased" method. */ methods = "password,keyboard-interactive,publickey"; } len += proxy_ssh_msg_write_string(&buf, &buflen, methods); len += proxy_ssh_msg_write_bool(&buf, &buflen, FALSE); pkt->payload = ptr; pkt->payload_len = len; res = proxy_ssh_packet_write_frontend(proxy_sess->frontend_ctrl_conn, pkt); if (res < 0 && errno != ENOSYS) { destroy_pool(pkt->pool); return -1; } destroy_pool(pkt->pool); return 0; } static const unsigned char *write_userauth_signed_data(pool *p, unsigned char *data, uint32_t datalen, size_t *sig_datalen) { unsigned char *buf, *ptr; const unsigned char *session_id; uint32_t bufsz, buflen, session_idlen, len = 0; /* XXX Is this buffer large enough? Too large? */ bufsz = buflen = 2048; ptr = buf = palloc(p, bufsz); /* Write the session ID. */ session_idlen = proxy_ssh_session_get_id(&session_id); len += proxy_ssh_msg_write_data(&buf, &buflen, session_id, session_idlen, TRUE); /* Write the given data. */ len += proxy_ssh_msg_write_data(&buf, &buflen, data, datalen, FALSE); *sig_datalen = len; return ptr; } static int write_userauth_hostbased(struct proxy_ssh_packet *pkt, const char *user, const char *service) { unsigned char *buf, *ptr; const unsigned char *hostkey_data, *sig_data, *signature; uint32_t bufsz, buflen, hostkey_datalen, len = 0; size_t signature_len, sig_datalen; const char *hostkey_algo = NULL, *hostname; enum proxy_ssh_key_type_e use_hostkey_type = PROXY_SSH_KEY_UNKNOWN; /* XXX Is this buffer large enough? Too large? */ bufsz = buflen = 8192; ptr = buf = palloc(pkt->pool, bufsz); /* Retrieve our hostkey. We probe for our available hostkeys in preference * order: * * Ed25519 * ECDSA521 * ECDSA384 * ECDSA256 * RSA * DSA * * Note that RFC 8308 and the "server-sig-algs" EXT_INFO extension, for * SHA256/512 signatures using RSA keys, only applies to "publickey" * authentication requests, not "hostbased" -- hence why we do not probe * for those combinations. */ len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_USER_AUTH_REQUEST); len += proxy_ssh_msg_write_string(&buf, &buflen, pstrdup(pkt->pool, user)); len += proxy_ssh_msg_write_string(&buf, &buflen, pstrdup(pkt->pool, service)); len += proxy_ssh_msg_write_string(&buf, &buflen, pstrdup(pkt->pool, "hostbased")); if (proxy_ssh_keys_have_hostkey(PROXY_SSH_KEY_ED448) == 0) { use_hostkey_type = PROXY_SSH_KEY_ED448; hostkey_algo = "ssh-ed448"; } else if (proxy_ssh_keys_have_hostkey(PROXY_SSH_KEY_ED25519) == 0) { use_hostkey_type = PROXY_SSH_KEY_ED25519; hostkey_algo = "ssh-ed25519"; } else if (proxy_ssh_keys_have_hostkey(PROXY_SSH_KEY_ECDSA_521) == 0) { use_hostkey_type = PROXY_SSH_KEY_ECDSA_521; hostkey_algo = "ecdsa-sha2-nistp521"; } else if (proxy_ssh_keys_have_hostkey(PROXY_SSH_KEY_ECDSA_384) == 0) { use_hostkey_type = PROXY_SSH_KEY_ECDSA_384; hostkey_algo = "ecdsa-sha2-nistp384"; } else if (proxy_ssh_keys_have_hostkey(PROXY_SSH_KEY_ECDSA_256) == 0) { use_hostkey_type = PROXY_SSH_KEY_ECDSA_256; hostkey_algo = "ecdsa-sha2-nistp256"; } else if (proxy_ssh_keys_have_hostkey(PROXY_SSH_KEY_RSA) == 0) { use_hostkey_type = PROXY_SSH_KEY_RSA; hostkey_algo = "ssh-rsa"; } else if (proxy_ssh_keys_have_hostkey(PROXY_SSH_KEY_DSA) == 0) { use_hostkey_type = PROXY_SSH_KEY_DSA; hostkey_algo = "ssh-dss"; } hostkey_data = proxy_ssh_keys_get_hostkey_data(pkt->pool, use_hostkey_type, &hostkey_datalen); if (hostkey_data == NULL) { return -1; } len += proxy_ssh_msg_write_string(&buf, &buflen, hostkey_algo); len += proxy_ssh_msg_write_data(&buf, &buflen, hostkey_data, hostkey_datalen, TRUE); hostname = pr_netaddr_get_localaddr_str(pkt->pool); len += proxy_ssh_msg_write_string(&buf, &buflen, hostname); len += proxy_ssh_msg_write_string(&buf, &buflen, pstrdup(pkt->pool, user)); sig_data = write_userauth_signed_data(pkt->pool, ptr, len, &sig_datalen); if (sig_data == NULL) { return -1; } signature = proxy_ssh_keys_sign_data(pkt->pool, use_hostkey_type, sig_data, sig_datalen, &signature_len); len += proxy_ssh_msg_write_data(&buf, &buflen, signature, signature_len, TRUE); pkt->payload = ptr; pkt->payload_len = len; return 0; } static int handle_userauth_hostbased(struct proxy_ssh_packet *pkt, const struct proxy_session *proxy_sess) { int res, xerrno, success = FALSE; unsigned char *buf; uint32_t buflen; char *orig_user, *new_user = NULL, *user, *service; pool *tmp_pool; /* We cannot send this "hostbased" USER_AUTH_REQUEST packet to the backend * server as-is, since the signed data involves the *frontend* session ID -- * which the backend server will not have. * * Thus to support this, we need our own ProxySFTPHostKey. */ buf = pkt->payload; buflen = pkt->payload_len; /* Skip past the message type. */ buf += sizeof(char); buflen -= sizeof(char); /* We only need to copy the user name, service name from the frontend packet; * we can ignore the rest. */ proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &orig_user); /* If mod_sftp has already authenticated the client (as for PerUser/PerGroup * connect policies), then we need not dispatch USER/PASS commands again. */ if (session.auth_mech == NULL) { res = dispatch_user_cmd(pkt->pool, orig_user, &new_user); if (res < 0) { destroy_pool(pkt->pool); return -1; } } proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &service); tmp_pool = make_sub_pool(auth_pool); user = pstrdup(tmp_pool, new_user != NULL ? new_user : orig_user); service = pstrdup(tmp_pool, service); destroy_pool(pkt->pool); if (session.auth_mech == NULL) { (void) pr_table_remove(session.notes, "mod_auth.orig-user", NULL); if (pr_table_add_dup(session.notes, "mod_auth.orig-user", user, 0) < 0 && errno != EEXIST) { pr_log_debug(DEBUG3, "error stashing 'mod_auth.orig-user' in " "session.notes: %s", strerror(errno)); } } if (proxy_ssh_keys_have_hostkey(PROXY_SSH_KEY_UNKNOWN) != 0) { unsigned char *ptr; uint32_t bufsz, len = 0; const char *methods; /* We have no configured hostkeys, thus cannot use "hostbased" * authentication; return FAILURE to the frontend client. */ (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to handle client-requested hostbased authentication: " "no ProxySFTPHostKeys configured"); pr_trace_msg(trace_channel, 9, "writing USER_AUTH_FAILURE message to client"); pkt = proxy_ssh_packet_create(auth_pool); bufsz = buflen = 1024; ptr = buf = palloc(pkt->pool, bufsz); len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_USER_AUTH_FAILURE); methods = "password,keyboard-interactive,publickey"; len += proxy_ssh_msg_write_string(&buf, &buflen, methods); len += proxy_ssh_msg_write_bool(&buf, &buflen, FALSE); pkt->payload = ptr; pkt->payload_len = len; res = proxy_ssh_packet_write_frontend(proxy_sess->frontend_ctrl_conn, pkt); if (res < 0 && errno != ENOSYS) { destroy_pool(pkt->pool); return -1; } return 0; } pr_trace_msg(trace_channel, 9, "writing USER_AUTH_REQUEST hostbased message to server"); pkt = proxy_ssh_packet_create(auth_pool); res = write_userauth_hostbased(pkt, user, service); if (res < 0) { xerrno = errno; destroy_pool(pkt->pool); destroy_pool(tmp_pool); errno = xerrno; return -1; } res = proxy_ssh_packet_write(proxy_sess->backend_ctrl_conn, pkt); if (res < 0) { xerrno = errno; destroy_pool(pkt->pool); destroy_pool(tmp_pool); errno = xerrno; return -1; } destroy_pool(pkt->pool); destroy_pool(tmp_pool); while (TRUE) { char msg_type; pr_signals_handle(); pkt = read_auth_packet(auth_pool, proxy_sess); if (pkt == NULL) { return -1; } msg_type = proxy_ssh_packet_peek_msg_type(pkt); /* Handle the hostbased-specific message types, if any, here. */ res = process_auth_packet(pkt, proxy_sess); if (res < 0) { destroy_pool(pkt->pool); /* Invalid protocol sequence */ (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "received unexpected %s packet during SSH authentication, failing", proxy_ssh_packet_get_msg_type_desc(msg_type)); errno = ENOSYS; return -1; } if (res == 0) { continue; } /* If we reach here, it should be for USER_AUTH_SUCCESS/FAILURE packets, to * send to the frontend client. */ proxy_ssh_packet_log_cmd(pkt, FALSE); if (msg_type == PROXY_SSH_MSG_USER_AUTH_SUCCESS) { success = TRUE; } break; } res = proxy_ssh_packet_proxied(proxy_sess, pkt, FALSE); if (res < 0) { xerrno = errno; destroy_pool(pkt->pool); errno = xerrno; return -1; } destroy_pool(pkt->pool); return success; } static int write_userauth_kbdint(struct proxy_ssh_packet *pkt, const char *user, const char *service, const char *language, const char *submethods) { unsigned char *buf, *ptr; uint32_t bufsz, buflen, len = 0; /* XXX Is this buffer large enough? Too large? */ bufsz = buflen = 8192; ptr = buf = palloc(pkt->pool, bufsz); len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_USER_AUTH_REQUEST); len += proxy_ssh_msg_write_string(&buf, &buflen, pstrdup(pkt->pool, user)); len += proxy_ssh_msg_write_string(&buf, &buflen, pstrdup(pkt->pool, service)); len += proxy_ssh_msg_write_string(&buf, &buflen, pstrdup(pkt->pool, "keyboard-interactive")); len += proxy_ssh_msg_write_string(&buf, &buflen, pstrdup(pkt->pool, language)); len += proxy_ssh_msg_write_string(&buf, &buflen, pstrdup(pkt->pool, submethods)); pkt->payload = ptr; pkt->payload_len = len; return 0; } static int handle_userauth_kbdint(struct proxy_ssh_packet *pkt, const struct proxy_session *proxy_sess) { int res, xerrno, success = FALSE; unsigned char *buf; uint32_t buflen; char *orig_user, *new_user = NULL; buf = pkt->payload; buflen = pkt->payload_len; /* Skip past the message type. */ buf += sizeof(char); buflen -= sizeof(char); proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &orig_user); res = dispatch_user_cmd(pkt->pool, orig_user, &new_user); if (res < 0) { destroy_pool(pkt->pool); return -1; } if (new_user == NULL) { /* No changes to the user; we can proxy the packet as is. */ (void) pr_table_remove(session.notes, "mod_auth.orig-user", NULL); res = pr_table_add_dup(session.notes, "mod_auth.orig-user", orig_user, 0); if (res < 0 && errno != EEXIST) { pr_log_debug(DEBUG3, "error stashing 'mod_auth.orig-user' in " "session.notes: %s", strerror(errno)); } res = proxy_ssh_packet_proxied(proxy_sess, pkt, TRUE); if (res < 0) { destroy_pool(pkt->pool); return -1; } } else { char *user, *service, *method, *language, *submethods; pool *tmp_pool; /* The username changed; we need to write a new packet. */ proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &service); proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &method); proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &language); proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &submethods); tmp_pool = make_sub_pool(auth_pool); user = pstrdup(tmp_pool, new_user); service = pstrdup(tmp_pool, service); language = pstrdup(tmp_pool, language); submethods = pstrdup(tmp_pool, submethods); destroy_pool(pkt->pool); (void) pr_table_remove(session.notes, "mod_auth.orig-user", NULL); if (pr_table_add_dup(session.notes, "mod_auth.orig-user", user, 0) < 0 && errno != EEXIST) { pr_log_debug(DEBUG3, "error stashing 'mod_auth.orig-user' in " "session.notes: %s", strerror(errno)); } pkt = proxy_ssh_packet_create(auth_pool); res = write_userauth_kbdint(pkt, user, service, language, submethods); if (res < 0) { xerrno = errno; destroy_pool(pkt->pool); destroy_pool(tmp_pool); errno = xerrno; return -1; } res = proxy_ssh_packet_write(proxy_sess->backend_ctrl_conn, pkt); if (res < 0) { xerrno = errno; destroy_pool(pkt->pool); destroy_pool(tmp_pool); errno = xerrno; return -1; } destroy_pool(tmp_pool); } destroy_pool(pkt->pool); while (TRUE) { char msg_type; pr_signals_handle(); pkt = read_auth_packet(auth_pool, proxy_sess); if (pkt == NULL) { return -1; } msg_type = proxy_ssh_packet_peek_msg_type(pkt); /* Handle the kbdint-specific message types, if any, here. */ if (msg_type == PROXY_SSH_MSG_USER_AUTH_INFO_REQ) { proxy_ssh_packet_log_cmd(pkt, FALSE); res = proxy_ssh_packet_proxied(proxy_sess, pkt, FALSE); destroy_pool(pkt->pool); if (res < 0) { return -1; } return 0; } res = process_auth_packet(pkt, proxy_sess); if (res < 0) { destroy_pool(pkt->pool); /* Invalid protocol sequence */ (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "received unexpected %s packet during SSH authentication, failing", proxy_ssh_packet_get_msg_type_desc(msg_type)); errno = ENOSYS; return -1; } if (res == 0) { continue; } /* If we reach here, it should be for USER_AUTH_SUCCESS/FAILURE packets, to * send to the frontend client. */ proxy_ssh_packet_log_cmd(pkt, FALSE); if (msg_type == PROXY_SSH_MSG_USER_AUTH_SUCCESS) { success = TRUE; } break; } res = proxy_ssh_packet_proxied(proxy_sess, pkt, FALSE); if (res < 0) { xerrno = errno; destroy_pool(pkt->pool); errno = xerrno; return -1; } destroy_pool(pkt->pool); return success; } static int write_userauth_password(struct proxy_ssh_packet *pkt, const char *user, const char *service, const char *password) { unsigned char *buf, *ptr; uint32_t bufsz, buflen, len = 0; /* XXX Is this buffer large enough? Too large? */ bufsz = buflen = 8192; ptr = buf = palloc(pkt->pool, bufsz); len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_USER_AUTH_REQUEST); len += proxy_ssh_msg_write_string(&buf, &buflen, pstrdup(pkt->pool, user)); len += proxy_ssh_msg_write_string(&buf, &buflen, pstrdup(pkt->pool, service)); len += proxy_ssh_msg_write_string(&buf, &buflen, pstrdup(pkt->pool, "password")); len += proxy_ssh_msg_write_bool(&buf, &buflen, FALSE); len += proxy_ssh_msg_write_string(&buf, &buflen, pstrdup(pkt->pool, password)); pkt->payload = ptr; pkt->payload_len = len; return 0; } static int handle_userauth_password(struct proxy_ssh_packet *pkt, const struct proxy_session *proxy_sess) { int res, xerrno, with_password, success = FALSE; unsigned char *buf; uint32_t buflen; char *orig_user, *new_user = NULL; buf = pkt->payload; buflen = pkt->payload_len; /* Skip past the message type. */ buf += sizeof(char); buflen -= sizeof(char); proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &orig_user); res = dispatch_user_cmd(pkt->pool, orig_user, &new_user); if (res < 0) { destroy_pool(pkt->pool); return -1; } if (new_user == NULL) { /* No changes to the user; we can proxy the packet as is. */ (void) pr_table_remove(session.notes, "mod_auth.orig-user", NULL); res = pr_table_add_dup(session.notes, "mod_auth.orig-user", orig_user, 0); if (res < 0 && errno != EEXIST) { pr_log_debug(DEBUG3, "error stashing 'mod_auth.orig-user' in " "session.notes: %s", strerror(errno)); } res = proxy_ssh_packet_proxied(proxy_sess, pkt, TRUE); if (res < 0) { destroy_pool(pkt->pool); return -1; } } else { char *user, *service, *method, *password; pool *tmp_pool; /* The username changed; we need to write a new packet. */ proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &service); proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &method); proxy_ssh_msg_read_bool(pkt->pool, &buf, &buflen, &with_password); proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &password); tmp_pool = make_sub_pool(auth_pool); user = pstrdup(tmp_pool, new_user); service = pstrdup(tmp_pool, service); password = pstrdup(tmp_pool, password); destroy_pool(pkt->pool); (void) pr_table_remove(session.notes, "mod_auth.orig-user", NULL); if (pr_table_add_dup(session.notes, "mod_auth.orig-user", user, 0) < 0 && errno != EEXIST) { pr_log_debug(DEBUG3, "error stashing 'mod_auth.orig-user' in " "session.notes: %s", strerror(errno)); } pkt = proxy_ssh_packet_create(auth_pool); res = write_userauth_password(pkt, user, service, password); if (res < 0) { xerrno = errno; pr_memscrub(password, strlen(password)); destroy_pool(pkt->pool); destroy_pool(tmp_pool); errno = xerrno; return -1; } res = proxy_ssh_packet_write(proxy_sess->backend_ctrl_conn, pkt); if (res < 0) { xerrno = errno; pr_memscrub(password, strlen(password)); destroy_pool(pkt->pool); destroy_pool(tmp_pool); errno = xerrno; return -1; } pr_memscrub(password, strlen(password)); destroy_pool(tmp_pool); } destroy_pool(pkt->pool); while (TRUE) { char msg_type; pr_signals_handle(); pkt = read_auth_packet(auth_pool, proxy_sess); if (pkt == NULL) { return -1; } msg_type = proxy_ssh_packet_peek_msg_type(pkt); /* Handle the password-specific message types here. */ if (msg_type == PROXY_SSH_MSG_USER_AUTH_PASSWD) { proxy_ssh_packet_log_cmd(pkt, FALSE); res = proxy_ssh_packet_proxied(proxy_sess, pkt, FALSE); destroy_pool(pkt->pool); if (res < 0) { return -1; } continue; } res = process_auth_packet(pkt, proxy_sess); if (res < 0) { destroy_pool(pkt->pool); /* Invalid protocol sequence */ (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "received unexpected %s packet during SSH authentication, failing", proxy_ssh_packet_get_msg_type_desc(msg_type)); errno = ENOSYS; return -1; } if (res == 0) { continue; } /* If we reach here, it should be for USER_AUTH_SUCCESS/FAILURE packets, to * send to the frontend client. */ proxy_ssh_packet_log_cmd(pkt, FALSE); if (msg_type == PROXY_SSH_MSG_USER_AUTH_SUCCESS) { success = TRUE; } break; } res = proxy_ssh_packet_proxied(proxy_sess, pkt, FALSE); if (res < 0) { xerrno = errno; destroy_pool(pkt->pool); errno = xerrno; return -1; } destroy_pool(pkt->pool); return success; } static int write_pk_ok(struct proxy_ssh_packet *pkt, const char *publickey_algo, unsigned char *publickey_blob, uint32_t publickey_bloblen) { unsigned char *buf, *ptr; uint32_t bufsz, buflen, len = 0; /* XXX Is this buffer large enough? Too large? */ bufsz = buflen = 8192; ptr = buf = palloc(pkt->pool, bufsz); len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_USER_AUTH_PK_OK); len += proxy_ssh_msg_write_string(&buf, &buflen, pstrdup(pkt->pool, publickey_algo)); len += proxy_ssh_msg_write_data(&buf, &buflen, publickey_blob, publickey_bloblen, TRUE); pkt->payload = ptr; pkt->payload_len = len; return 0; } /* We will use "hostbased" authentication to the backend, but we still need to * fulfill the "publickey" authentication protocol to the frontend client. */ static int handle_userauth_publickey(struct proxy_ssh_packet *pkt, const struct proxy_session *proxy_sess) { int res, xerrno, success = FALSE, with_signature = FALSE; unsigned char *buf, *buf2, *publickey_blob; uint32_t buflen, publickey_bloblen; char *orig_user, *new_user = NULL, *user, *service, *method, *publickey_algo; pool *tmp_pool; /* We cannot send this "publickey" USER_AUTH_REQUEST packet to the backend * server as-is, since the signed data involves the *frontend* session ID -- * which the backend server will not have. * * If this publickey request contains the signature, we will send our * hostbased request to the backend server, and return SUCCESS. Otherwise, * we will respond to the frontend client, asking them to send the * signature. */ buf = pkt->payload; buflen = pkt->payload_len; /* Skip past the message type. */ buf += sizeof(char); buflen -= sizeof(char); proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &orig_user); res = dispatch_user_cmd(pkt->pool, orig_user, &new_user); if (res < 0) { destroy_pool(pkt->pool); return -1; } proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &service); proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &method); proxy_ssh_msg_read_bool(pkt->pool, &buf, &buflen, &with_signature); proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &publickey_algo); proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &publickey_bloblen); proxy_ssh_msg_read_data(pkt->pool, &buf, &buflen, publickey_bloblen, &publickey_blob); tmp_pool = make_sub_pool(auth_pool); user = pstrdup(tmp_pool, new_user != NULL ? new_user : orig_user); service = pstrdup(tmp_pool, service); publickey_algo = pstrdup(tmp_pool, publickey_algo); buf2 = palloc(tmp_pool, publickey_bloblen); memcpy(buf2, publickey_blob, publickey_bloblen); publickey_blob = buf2; destroy_pool(pkt->pool); (void) pr_table_remove(session.notes, "mod_auth.orig-user", NULL); if (pr_table_add_dup(session.notes, "mod_auth.orig-user", user, 0) < 0 && errno != EEXIST) { pr_log_debug(DEBUG3, "error stashing 'mod_auth.orig-user' in " "session.notes: %s", strerror(errno)); } if (proxy_ssh_keys_have_hostkey(PROXY_SSH_KEY_UNKNOWN) != 0) { unsigned char *ptr; uint32_t bufsz, len = 0; const char *methods; /* We have no configured hostkeys, thus cannot use "hostbased" * authentication; return FAILURE to the frontend client. */ (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to handle client-requested publickey authentication: " "no ProxySFTPHostKeys configured"); pr_trace_msg(trace_channel, 9, "writing USER_AUTH_FAILURE message to client"); pkt = proxy_ssh_packet_create(auth_pool); bufsz = buflen = 1024; ptr = buf = palloc(pkt->pool, bufsz); len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_USER_AUTH_FAILURE); methods = "password,keyboard-interactive,hostbased"; len += proxy_ssh_msg_write_string(&buf, &buflen, methods); len += proxy_ssh_msg_write_bool(&buf, &buflen, FALSE); pkt->payload = ptr; pkt->payload_len = len; res = proxy_ssh_packet_write_frontend(proxy_sess->frontend_ctrl_conn, pkt); if (res < 0 && errno != ENOSYS) { destroy_pool(pkt->pool); return -1; } return 0; } if (with_signature == TRUE) { pr_trace_msg(trace_channel, 9, "publickey request includes signature, writing USER_AUTH_REQUEST " "hostbased message to server"); pkt = proxy_ssh_packet_create(auth_pool); res = write_userauth_hostbased(pkt, user, service); if (res < 0) { xerrno = errno; destroy_pool(pkt->pool); destroy_pool(tmp_pool); errno = xerrno; return -1; } res = proxy_ssh_packet_write(proxy_sess->backend_ctrl_conn, pkt); if (res < 0) { xerrno = errno; destroy_pool(pkt->pool); destroy_pool(tmp_pool); errno = xerrno; return -1; } } else { pr_trace_msg(trace_channel, 9, "publickey request does not include signature, writing USER_AUTH_PK_OK " "message to client"); pkt = proxy_ssh_packet_create(auth_pool); res = write_pk_ok(pkt, publickey_algo, publickey_blob, publickey_bloblen); if (res < 0) { xerrno = errno; destroy_pool(pkt->pool); destroy_pool(tmp_pool); errno = xerrno; return -1; } res = proxy_ssh_packet_write_frontend(proxy_sess->frontend_ctrl_conn, pkt); if (res < 0 && errno != ENOSYS) { xerrno = errno; destroy_pool(pkt->pool); destroy_pool(tmp_pool); errno = xerrno; return -1; } destroy_pool(pkt->pool); destroy_pool(tmp_pool); return 0; } destroy_pool(pkt->pool); destroy_pool(tmp_pool); while (TRUE) { char msg_type; pr_signals_handle(); pkt = read_auth_packet(auth_pool, proxy_sess); if (pkt == NULL) { return -1; } msg_type = proxy_ssh_packet_peek_msg_type(pkt); /* Handle the publickey-specific message types, if any, here. */ res = process_auth_packet(pkt, proxy_sess); if (res < 0) { destroy_pool(pkt->pool); /* Invalid protocol sequence */ (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "received unexpected %s packet during SSH authentication, failing", proxy_ssh_packet_get_msg_type_desc(msg_type)); errno = ENOSYS; return -1; } if (res == 0) { continue; } /* If we reach here, it should be for USER_AUTH_SUCCESS/FAILURE packets, to * send to the frontend client. */ proxy_ssh_packet_log_cmd(pkt, FALSE); if (msg_type == PROXY_SSH_MSG_USER_AUTH_SUCCESS) { success = TRUE; } break; } res = proxy_ssh_packet_proxied(proxy_sess, pkt, FALSE); if (res < 0) { xerrno = errno; destroy_pool(pkt->pool); errno = xerrno; return -1; } destroy_pool(pkt->pool); return success; } int proxy_ssh_auth_handle(struct proxy_ssh_packet *pkt, const struct proxy_session *proxy_sess) { char msg_type, *user = NULL, *service = NULL, *method = NULL; unsigned char *buf = NULL; uint32_t buflen = 0; int success = FALSE; msg_type = proxy_ssh_packet_peek_msg_type(pkt); buf = pkt->payload; buflen = pkt->payload_len; /* Skip past the message type. */ buf += sizeof(char); buflen -= sizeof(char); if (msg_type == PROXY_SSH_MSG_USER_AUTH_REQUEST) { uint32_t len; len = proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &user); len = proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &service); len = proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &method); pr_trace_msg(trace_channel, 10, "auth requested for user '%s', service '%s', using method '%s'", user, service, method); if (strcmp(method, "none") == 0) { success = handle_userauth_none(pkt, proxy_sess); } else if (strcmp(method, "hostbased") == 0) { success = handle_userauth_hostbased(pkt, proxy_sess); } else if (strcmp(method, "keyboard-interactive") == 0) { success = handle_userauth_kbdint(pkt, proxy_sess); } else if (strcmp(method, "password") == 0) { success = handle_userauth_password(pkt, proxy_sess); } else if (strcmp(method, "publickey") == 0) { success = handle_userauth_publickey(pkt, proxy_sess); } else { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to handle SSH_MSG_USER_AUTH_REQUEST message: " "unknown/unsupported method '%s' requested", method); errno = EINVAL; return -1; } } else if (msg_type == PROXY_SSH_MSG_USER_AUTH_INFO_RESP) { pr_trace_msg(trace_channel, 17, "handling USER_AUTH_INFO_RESPONSE"); success = handle_userauth_kbdint(pkt, proxy_sess); } if (success == TRUE) { int res; const char *orig_user; (void) pr_timer_remove(PR_TIMER_LOGIN, ANY_MODULE); if (session.auth_mech == NULL) { orig_user = pr_table_get(session.notes, "mod_auth.orig-user", NULL); res = proxy_session_setup_env(proxy_pool, orig_user, 0); if (res < 0) { errno = EINVAL; return -1; } } /* We call the compression init routines here as well, in case the * server selected "delayed" compression. */ proxy_ssh_compress_init_read(PROXY_SSH_COMPRESS_FL_AUTHENTICATED); proxy_ssh_compress_init_write(PROXY_SSH_COMPRESS_FL_AUTHENTICATED); } /* If mod_sftp has already authenticated the client (as for PerUser/PerGroup * connect policies), then we need not dispatch USER/PASS commands again. */ if (session.auth_mech == NULL) { dispatch_pass_cmd(proxy_pool, success); } return success; } int proxy_ssh_auth_init(pool *p) { if (auth_pool == NULL) { auth_pool = make_sub_pool(p); pr_pool_tag(auth_pool, "Proxy SSH Auth Pool"); } return 0; } int proxy_ssh_auth_sess_init(pool *p, const struct proxy_session *proxy_sess) { /* Currently unused. */ (void) p; (void) proxy_sess; return 0; } int proxy_ssh_auth_set_frontend_success_handle(pool *p, int (*success_handle)(pool *p, const char *user)) { const char *hook_symbol; cmdtable *sftp_cmdtab; cmd_rec *cmd; modret_t *result; hook_symbol = "sftp_set_auth_success_handler"; sftp_cmdtab = pr_stash_get_symbol2(PR_SYM_HOOK, hook_symbol, NULL, NULL, NULL); if (sftp_cmdtab == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to find SFTP hook symbol '%s'", hook_symbol); errno = ENOENT; return -1; } cmd = pr_cmd_alloc(p, 1, NULL); cmd->argv[0] = (void *) success_handle; result = pr_module_call(sftp_cmdtab->m, sftp_cmdtab->handler, cmd); if (result == NULL || MODRET_ISERROR(result)) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error setting Proxy SSH Auth success handler"); errno = EPERM; return -1; } return 0; } #endif /* PR_USE_OPENSSL */ proftpd-mod_proxy-0.9.5/lib/proxy/ssh/bcrypt.c000066400000000000000000000150071475737016700214400ustar00rootroot00000000000000/* * Copyright (c) 2013 Ted Unangst * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "openbsd-blowfish.h" #include "proxy/ssh/bcrypt.h" #include "proxy/ssh/crypto.h" #if defined(PR_USE_OPENSSL) #include #define MINIMUM(a,b) (((a) < (b)) ? (a) : (b)) /* * pkcs #5 pbkdf2 implementation using the "bcrypt" hash * * The bcrypt hash function is derived from the bcrypt password hashing * function with the following modifications: * 1. The input password and salt are preprocessed with SHA512. * 2. The output length is expanded to 256 bits. * 3. Subsequently the magic string to be encrypted is lengthened and modifed * to "OxychromaticBlowfishSwatDynamite" * 4. The hash function is defined to perform 64 rounds of initial state * expansion. (More rounds are performed by iterating the hash.) * * Note that this implementation pulls the SHA512 operations into the caller * as a performance optimization. * * One modification from official pbkdf2. Instead of outputting key material * linearly, we mix it. pbkdf2 has a known weakness where if one uses it to * generate (e.g.) 512 bits of key material for use as two 256 bit keys, an * attacker can merely run once through the outer loop, but the user * always runs it twice. Shuffling output bytes requires computing the * entirety of the key material to assemble any subkey. This is something a * wise caller could do; we just do it for you. */ #define BCRYPT_WORDS 8 #define BCRYPT_HASHSIZE (BCRYPT_WORDS * 4) static void bcrypt_hash(uint8_t *sha2pass, uint8_t *sha2salt, uint8_t *out) { blf_ctx state; uint8_t ciphertext[BCRYPT_HASHSIZE] = "OxychromaticBlowfishSwatDynamite"; uint32_t cdata[BCRYPT_WORDS]; int i; uint16_t j; size_t shalen = SHA512_DIGEST_LENGTH; /* key expansion */ Blowfish_initstate(&state); Blowfish_expandstate(&state, sha2salt, shalen, sha2pass, shalen); for (i = 0; i < 64; i++) { Blowfish_expand0state(&state, sha2salt, shalen); Blowfish_expand0state(&state, sha2pass, shalen); } /* encryption */ j = 0; for (i = 0; i < BCRYPT_WORDS; i++) cdata[i] = Blowfish_stream2word(ciphertext, sizeof(ciphertext), &j); for (i = 0; i < 64; i++) blf_enc(&state, cdata, sizeof(cdata) / sizeof(uint64_t)); /* copy out */ for (i = 0; i < BCRYPT_WORDS; i++) { out[4 * i + 3] = (cdata[i] >> 24) & 0xff; out[4 * i + 2] = (cdata[i] >> 16) & 0xff; out[4 * i + 1] = (cdata[i] >> 8) & 0xff; out[4 * i + 0] = cdata[i] & 0xff; } /* zap */ pr_memscrub(ciphertext, sizeof(ciphertext)); pr_memscrub(cdata, sizeof(cdata)); pr_memscrub(&state, sizeof(state)); } static int bcrypt_pbkdf(const char *pass, size_t passlen, const uint8_t *salt, size_t saltlen, uint8_t *key, size_t keylen, unsigned int rounds) { SHA512_CTX ctx; uint8_t sha2pass[SHA512_DIGEST_LENGTH]; uint8_t sha2salt[SHA512_DIGEST_LENGTH]; uint8_t out[BCRYPT_HASHSIZE]; uint8_t tmpout[BCRYPT_HASHSIZE]; uint8_t countsalt[4]; size_t i, j, amt, stride; uint32_t count; size_t origkeylen = keylen; /* nothing crazy */ if (rounds < 1) return -1; if (passlen == 0 || saltlen == 0 || keylen == 0 || keylen > sizeof(out) * sizeof(out)) return -1; stride = (keylen + sizeof(out) - 1) / sizeof(out); amt = (keylen + stride - 1) / stride; /* collapse password */ SHA512_Init(&ctx); SHA512_Update(&ctx, pass, passlen); SHA512_Final(sha2pass, &ctx); /* generate key, sizeof(out) at a time */ for (count = 1; keylen > 0; count++) { countsalt[0] = (count >> 24) & 0xff; countsalt[1] = (count >> 16) & 0xff; countsalt[2] = (count >> 8) & 0xff; countsalt[3] = count & 0xff; /* first round, salt is salt */ SHA512_Init(&ctx); SHA512_Update(&ctx, salt, saltlen); SHA512_Update(&ctx, countsalt, sizeof(countsalt)); SHA512_Final(sha2salt, &ctx); bcrypt_hash(sha2pass, sha2salt, tmpout); memcpy(out, tmpout, sizeof(out)); for (i = 1; i < rounds; i++) { /* subsequent rounds, salt is previous output */ SHA512_Init(&ctx); SHA512_Update(&ctx, tmpout, sizeof(tmpout)); SHA512_Final(sha2salt, &ctx); bcrypt_hash(sha2pass, sha2salt, tmpout); for (j = 0; j < sizeof(out); j++) out[j] ^= tmpout[j]; } /* * pbkdf2 deviation: output the key material non-linearly. */ amt = MINIMUM(amt, keylen); for (i = 0; i < amt; i++) { size_t dest = i * stride + (count - 1); if (dest >= origkeylen) break; key[dest] = out[i]; } keylen -= i; } /* zap */ pr_memscrub(&ctx, sizeof(ctx)); pr_memscrub(out, sizeof(out)); return 0; } static const char *trace_channel = "proxy.ssh.bcrypt"; int proxy_ssh_bcrypt_pbkdf2(pool *p, const char *passphrase, size_t passphrase_len, unsigned char *salt, uint32_t salt_len, uint32_t rounds, unsigned char *key, uint32_t key_len) { int res = 0; if (p == NULL || passphrase == NULL || salt == NULL) { errno = EINVAL; return -1; } if (rounds < 1) { pr_trace_msg(trace_channel, 4, "invalid rounds (%lu) for bcrypt KDF", (unsigned long) rounds); errno = EINVAL; return -1; } if (passphrase_len == 0 || salt_len == 0 || key_len == 0) { pr_trace_msg(trace_channel, 4, "invalid bcrypt KDF data: passphrase (%lu bytes), salt (%lu bytes), " "or key (%lu bytes)", (unsigned long) passphrase_len, (unsigned long) salt_len, (unsigned long) key_len); errno = EINVAL; return -1; } if (key_len < PROXY_SSH_BCRYPT_DIGEST_LEN) { pr_trace_msg(trace_channel, 4, "invalid bcrypt KDF data: key (%lu bytes) too short; need at " "least %lu bytes", (unsigned long) key_len, (unsigned long) PROXY_SSH_BCRYPT_DIGEST_LEN); errno = EINVAL; return -1; } res = bcrypt_pbkdf(passphrase, passphrase_len, salt, salt_len, key, key_len, rounds); if (res < 0) { errno = EINVAL; return -1; } return 0; } #endif /* PR_USE_OPENSSL */ proftpd-mod_proxy-0.9.5/lib/proxy/ssh/cipher.c000066400000000000000000001032671475737016700214150ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH ciphers * Copyright (c) 2021-2022 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "proxy/ssh/packet.h" #include "proxy/ssh/msg.h" #include "proxy/ssh/crypto.h" #include "proxy/ssh/cipher.h" #include "proxy/ssh/session.h" #include "proxy/ssh/interop.h" #if defined(PR_USE_OPENSSL) #include struct proxy_ssh_cipher { pool *pool; const char *algo; const EVP_CIPHER *cipher; unsigned char *iv; uint32_t iv_len; unsigned char *key; uint32_t key_len; uint32_t auth_len; size_t discard_len; }; /* We need to keep the old ciphers around, so that we can handle N * arbitrary packets to/from the client using the old keys, as during rekeying. * Thus we have two read cipher contexts, two write cipher contexts. * The cipher idx variable indicates which of the ciphers is currently in use. */ static struct proxy_ssh_cipher read_ciphers[2] = { { NULL, NULL, NULL, NULL, 0, NULL, 0, 0, 0 }, { NULL, NULL, NULL, NULL, 0, NULL, 0, 0, 0 } }; static EVP_CIPHER_CTX *read_ctxs[2]; static struct proxy_ssh_cipher write_ciphers[2] = { { NULL, NULL, NULL, NULL, 0, NULL, 0, 0, 0 }, { NULL, NULL, NULL, NULL, 0, NULL, 0, 0, 0 } }; static EVP_CIPHER_CTX *write_ctxs[2]; #define PROXY_SSH_CIPHER_DEFAULT_BLOCK_SZ 8 static size_t read_blockszs[2] = { PROXY_SSH_CIPHER_DEFAULT_BLOCK_SZ, PROXY_SSH_CIPHER_DEFAULT_BLOCK_SZ, }; static size_t write_blockszs[2] = { PROXY_SSH_CIPHER_DEFAULT_BLOCK_SZ, PROXY_SSH_CIPHER_DEFAULT_BLOCK_SZ, }; static unsigned int read_cipher_idx = 0; static unsigned int write_cipher_idx = 0; static const char *trace_channel = "proxy.ssh.cipher"; static void clear_cipher(struct proxy_ssh_cipher *); static unsigned int get_next_read_index(void) { if (read_cipher_idx == 1) { return 0; } return 1; } static unsigned int get_next_write_index(void) { if (write_cipher_idx == 1) { return 0; } return 1; } static void switch_read_cipher(void) { /* First, clear the context of the existing read cipher, if any. */ if (read_ciphers[read_cipher_idx].key != NULL) { clear_cipher(&(read_ciphers[read_cipher_idx])); #if OPENSSL_VERSION_NUMBER < 0x10100000L || \ defined(HAVE_LIBRESSL) if (EVP_CIPHER_CTX_cleanup(read_ctxs[read_cipher_idx]) != 1) { #else if (EVP_CIPHER_CTX_reset(read_ctxs[read_cipher_idx]) != 1) { #endif /* OpenSSL-1.1.x and later */ (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error clearing cipher context: %s", proxy_ssh_crypto_get_errors()); } read_blockszs[read_cipher_idx] = PROXY_SSH_CIPHER_DEFAULT_BLOCK_SZ; /* Now we can switch the index. */ if (read_cipher_idx == 1) { read_cipher_idx = 0; return; } read_cipher_idx = 1; } } static void switch_write_cipher(void) { /* First, clear the context of the existing read cipher, if any. */ if (write_ciphers[write_cipher_idx].key != NULL) { clear_cipher(&(write_ciphers[write_cipher_idx])); #if OPENSSL_VERSION_NUMBER < 0x10100000L || \ defined(HAVE_LIBRESSL) if (EVP_CIPHER_CTX_cleanup(write_ctxs[write_cipher_idx]) != 1) { #else if (EVP_CIPHER_CTX_reset(write_ctxs[write_cipher_idx]) != 1) { #endif /* OpenSSL-1.1.x and later */ (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error clearing cipher context: %s", proxy_ssh_crypto_get_errors()); } write_blockszs[write_cipher_idx] = PROXY_SSH_CIPHER_DEFAULT_BLOCK_SZ; /* Now we can switch the index. */ if (write_cipher_idx == 1) { write_cipher_idx = 0; return; } write_cipher_idx = 1; } } static void clear_cipher(struct proxy_ssh_cipher *cipher) { if (cipher->iv != NULL) { pr_memscrub(cipher->iv, cipher->iv_len); free(cipher->iv); cipher->iv = NULL; cipher->iv_len = 0; } if (cipher->key != NULL) { pr_memscrub(cipher->key, cipher->key_len); free(cipher->key); cipher->key = NULL; cipher->key_len = 0; } cipher->cipher = NULL; cipher->algo = NULL; } static int set_cipher_iv(struct proxy_ssh_cipher *cipher, const EVP_MD *md, const unsigned char *k, uint32_t klen, const char *h, uint32_t hlen, char letter, const unsigned char *id, uint32_t id_len) { EVP_MD_CTX *ctx; unsigned char *iv = NULL; size_t cipher_iv_len = 0, iv_sz = 0; uint32_t iv_len = 0; if (strcmp(cipher->algo, "none") == 0) { cipher->iv = iv; cipher->iv_len = iv_len; return 0; } /* Some ciphers do not use IVs; handle this case. */ cipher_iv_len = EVP_CIPHER_iv_length(cipher->cipher); if (cipher_iv_len != 0) { iv_sz = proxy_ssh_crypto_get_size(cipher_iv_len, EVP_MD_size(md)); } else { iv_sz = EVP_MD_size(md); } if (iv_sz == 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to determine IV length for cipher '%s'", cipher->algo); errno = EINVAL; return -1; } iv = malloc(iv_sz); if (iv == NULL) { pr_log_pri(PR_LOG_ALERT, MOD_PROXY_VERSION ": Out of memory!"); _exit(1); } ctx = EVP_MD_CTX_create(); if (EVP_DigestInit(ctx, md) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to initialize SSH MD context for '%s': %s", EVP_MD_name(md), proxy_ssh_crypto_get_errors()); free(iv); errno = EINVAL; return -1; } if (proxy_ssh_interop_supports_feature(PROXY_SSH_FEAT_CIPHER_USE_K)) { EVP_DigestUpdate(ctx, k, klen); } if (EVP_DigestUpdate(ctx, h, hlen) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to update SSH MD context for '%s': %s", EVP_MD_name(md), proxy_ssh_crypto_get_errors()); free(iv); errno = EINVAL; return -1; } EVP_DigestUpdate(ctx, &letter, sizeof(letter)); EVP_DigestUpdate(ctx, (char *) id, id_len); if (EVP_DigestFinal(ctx, iv, &iv_len) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to finish SSH MD context for '%s': %s", EVP_MD_name(md), proxy_ssh_crypto_get_errors()); free(iv); errno = EINVAL; return -1; } EVP_MD_CTX_destroy(ctx); /* If we need more, keep hashing, as per RFC, until we have enough * material. */ while (iv_sz > iv_len) { uint32_t len = iv_len; pr_signals_handle(); ctx = EVP_MD_CTX_create(); EVP_DigestInit(ctx, md); if (proxy_ssh_interop_supports_feature(PROXY_SSH_FEAT_CIPHER_USE_K)) { EVP_DigestUpdate(ctx, k, klen); } EVP_DigestUpdate(ctx, h, hlen); EVP_DigestUpdate(ctx, iv, len); EVP_DigestFinal(ctx, iv + len, &len); EVP_MD_CTX_destroy(ctx); iv_len += len; } cipher->iv = iv; cipher->iv_len = iv_len; return 0; } static int set_cipher_key(struct proxy_ssh_cipher *cipher, const EVP_MD *md, const unsigned char *k, uint32_t klen, const char *h, uint32_t hlen, char letter, const unsigned char *id, uint32_t id_len) { EVP_MD_CTX *ctx; unsigned char *key = NULL; size_t key_sz = 0; uint32_t key_len = 0; if (strcmp(cipher->algo, "none") == 0) { cipher->key = key; cipher->key_len = key_len; return 0; } key_sz = proxy_ssh_crypto_get_size(cipher->key_len > 0 ? cipher->key_len : (size_t) EVP_CIPHER_key_length(cipher->cipher), EVP_MD_size(md)); if (key_sz == 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to determine key length for cipher '%s'", cipher->algo); errno = EINVAL; return -1; } pr_trace_msg(trace_channel, 19, "setting key (%lu bytes) for cipher %s", (unsigned long) key_sz, cipher->algo); key = malloc(key_sz); if (key == NULL) { pr_log_pri(PR_LOG_ALERT, MOD_PROXY_VERSION ": Out of memory!"); _exit(1); } ctx = EVP_MD_CTX_create(); if (EVP_DigestInit(ctx, md) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to initialize SSH MD context for '%s': %s", EVP_MD_name(md), proxy_ssh_crypto_get_errors()); free(key); errno = EINVAL; return -1; } if (EVP_DigestUpdate(ctx, k, klen) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to update SSH MD context for '%s': %s", EVP_MD_name(md), proxy_ssh_crypto_get_errors()); free(key); errno = EINVAL; return -1; } EVP_DigestUpdate(ctx, h, hlen); EVP_DigestUpdate(ctx, &letter, sizeof(letter)); EVP_DigestUpdate(ctx, (char *) id, id_len); if (EVP_DigestFinal(ctx, key, &key_len) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to finish SSH MD context for '%s': %s", EVP_MD_name(md), proxy_ssh_crypto_get_errors()); free(key); errno = EINVAL; return -1; } EVP_MD_CTX_destroy(ctx); pr_trace_msg(trace_channel, 19, "hashed data to produce key (%lu bytes)", (unsigned long) key_len); /* If we need more, keep hashing, as per RFC, until we have enough * material. */ while (key_sz > key_len) { uint32_t len = key_len; pr_signals_handle(); ctx = EVP_MD_CTX_create(); EVP_DigestInit(ctx, md); EVP_DigestUpdate(ctx, k, klen); EVP_DigestUpdate(ctx, h, hlen); EVP_DigestUpdate(ctx, key, len); EVP_DigestFinal(ctx, key + len, &len); EVP_MD_CTX_destroy(ctx); key_len += len; } cipher->key = key; return 0; } /* If the chosen cipher requires that we discard some of the initial bytes of * the cipher stream, then do so. (This is mostly for any RC4 ciphers.) */ static int set_cipher_discarded(struct proxy_ssh_cipher *cipher, EVP_CIPHER_CTX *pctx) { unsigned char *garbage_in, *garbage_out; if (cipher->discard_len == 0) { return 0; } garbage_in = malloc(cipher->discard_len); if (garbage_in == NULL) { pr_log_pri(PR_LOG_ALERT, MOD_PROXY_VERSION ": Out of memory!"); _exit(1); } garbage_out = malloc(cipher->discard_len); if (garbage_out == NULL) { free(garbage_in); pr_log_pri(PR_LOG_ALERT, MOD_PROXY_VERSION ": Out of memory!"); _exit(1); } if (EVP_Cipher(pctx, garbage_out, garbage_in, cipher->discard_len) != 1) { free(garbage_in); pr_memscrub(garbage_out, cipher->discard_len); free(garbage_out); (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error ciphering discard data: %s", proxy_ssh_crypto_get_errors()); return -1; } pr_trace_msg(trace_channel, 19, "discarded %lu bytes of cipher data", (unsigned long) cipher->discard_len); free(garbage_in); pr_memscrub(garbage_out, cipher->discard_len); free(garbage_out); return 0; } /* These accessors to get the authenticated data length for the read, write * ciphers are used during packet IO, and thus do not return the AAD lengths * until those ciphers are keyed. * * However, during KEX, there are times when we want to know the ADD lengths * after the algorithms are selected, but before they are keyed. Thus for * those cases, we have the accessor variants. */ size_t proxy_ssh_cipher_get_read_auth_size2(void) { return read_ciphers[read_cipher_idx].auth_len; } size_t proxy_ssh_cipher_get_read_auth_size(void) { /* Do not indicate the read cipher authentication tag size until the * cipher has been keyed. */ if (read_ciphers[read_cipher_idx].key != NULL) { return proxy_ssh_cipher_get_read_auth_size2(); } return 0; } size_t proxy_ssh_cipher_get_write_auth_size2(void) { return write_ciphers[write_cipher_idx].auth_len; } size_t proxy_ssh_cipher_get_write_auth_size(void) { /* Do not indicate the write cipher authentication tag size until the * cipher has been keyed. */ if (write_ciphers[write_cipher_idx].key != NULL) { return proxy_ssh_cipher_get_write_auth_size2(); } return 0; } size_t proxy_ssh_cipher_get_read_block_size(void) { return read_blockszs[read_cipher_idx]; } size_t proxy_ssh_cipher_get_write_block_size(void) { return write_blockszs[write_cipher_idx]; } void proxy_ssh_cipher_set_read_block_size(size_t blocksz) { if (blocksz > read_blockszs[read_cipher_idx]) { read_blockszs[read_cipher_idx] = blocksz; } } void proxy_ssh_cipher_set_write_block_size(size_t blocksz) { if (blocksz > write_blockszs[write_cipher_idx]) { write_blockszs[write_cipher_idx] = blocksz; } } const char *proxy_ssh_cipher_get_read_algo(void) { if (read_ciphers[read_cipher_idx].key != NULL || strcmp(read_ciphers[read_cipher_idx].algo, "none") == 0) { return read_ciphers[read_cipher_idx].algo; } return NULL; } int proxy_ssh_cipher_set_read_algo(pool *p, const char *algo) { unsigned int idx = read_cipher_idx; size_t key_len = 0, auth_len = 0, discard_len = 0; if (read_ciphers[idx].key != NULL) { /* If we have an existing key, it means that we are currently rekeying. */ idx = get_next_read_index(); } read_ciphers[idx].cipher = proxy_ssh_crypto_get_cipher(algo, &key_len, &auth_len, &discard_len); if (read_ciphers[idx].cipher == NULL) { return -1; } if (key_len > 0) { pr_trace_msg(trace_channel, 19, "setting read key for cipher %s: key len = %lu", algo, (unsigned long) key_len); } if (auth_len > 0) { pr_trace_msg(trace_channel, 19, "setting read key for cipher %s: auth len = %lu", algo, (unsigned long) auth_len); } if (discard_len > 0) { pr_trace_msg(trace_channel, 19, "setting read key for cipher %s: discard len = %lu", algo, (unsigned long) discard_len); } /* Note that we use a new pool, each time the algorithm is set (which * happens during key exchange) to prevent undue memory growth for * long-lived sessions with many rekeys. */ if (read_ciphers[idx].pool != NULL) { destroy_pool(read_ciphers[idx].pool); } read_ciphers[idx].pool = make_sub_pool(p); pr_pool_tag(read_ciphers[idx].pool, "Proxy SFTP cipher read pool"); read_ciphers[idx].algo = pstrdup(read_ciphers[idx].pool, algo); read_ciphers[idx].key_len = (uint32_t) key_len; read_ciphers[idx].auth_len = (uint32_t) auth_len; read_ciphers[idx].discard_len = discard_len; return 0; } int proxy_ssh_cipher_set_read_key(pool *p, const EVP_MD *md, const unsigned char *k, uint32_t klen, const char *h, uint32_t hlen, int role) { const unsigned char *id = NULL; char letter; uint32_t id_len; int key_len, auth_len; struct proxy_ssh_cipher *cipher; EVP_CIPHER_CTX *pctx; /* Currently unused. */ (void) p; switch_read_cipher(); cipher = &(read_ciphers[read_cipher_idx]); pctx = read_ctxs[read_cipher_idx]; id_len = proxy_ssh_session_get_id(&id); /* The letters used depend on the role; see: * https://tools.ietf.org/html/rfc4253#section-7.2 * * If we are the CLIENT, then we use the letters for the "server to client" * flows, since we are READING from the server. */ /* client-to-server IV: HASH(K || H || "A" || session_id) * server-to-client IV: HASH(K || H || "B" || session_id) */ letter = (role == PROXY_SSH_ROLE_CLIENT ? 'B' : 'A'); if (set_cipher_iv(cipher, md, k, klen, h, hlen, letter, id, id_len) < 0) { return -1; } /* client-to-server key: HASH(K || H || "C" || session_id) * server-to-client key: HASH(K || H || "D" || session_id) */ letter = (role == PROXY_SSH_ROLE_CLIENT ? 'D' : 'C'); if (set_cipher_key(cipher, md, k, klen, h, hlen, letter, id, id_len) < 0) { return -1; } #if OPENSSL_VERSION_NUMBER < 0x10100000L || \ defined(HAVE_LIBRESSL) EVP_CIPHER_CTX_init(pctx); #else EVP_CIPHER_CTX_reset(pctx); #endif /* prior to OpenSSL-1.1.0 */ #if defined(PR_USE_OPENSSL_EVP_CIPHERINIT_EX) if (EVP_CipherInit_ex(pctx, cipher->cipher, NULL, NULL, cipher->iv, 0) != 1) { #else if (EVP_CipherInit(pctx, cipher->cipher, NULL, cipher->iv, 0) != 1) { #endif /* PR_USE_OPENSSL_EVP_CIPHERINIT_EX */ (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error initializing %s cipher for decryption: %s", cipher->algo, proxy_ssh_crypto_get_errors()); return -1; } auth_len = (int) cipher->auth_len; if (auth_len > 0) { #if defined(EVP_CTRL_GCM_SET_IV_FIXED) if (EVP_CIPHER_CTX_ctrl(pctx, EVP_CTRL_GCM_SET_IV_FIXED, -1, cipher->iv) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error configuring %s cipher for decryption: %s", cipher->algo, proxy_ssh_crypto_get_errors()); return -1; } #endif /* EVP_CTRL_GCM_SET_IV_FIXED */ pr_trace_msg(trace_channel, 19, "set auth length (%d) for %s cipher for decryption", auth_len, cipher->algo); } /* Next, set the key length. */ key_len = (int) cipher->key_len; if (key_len > 0) { if (EVP_CIPHER_CTX_set_key_length(pctx, key_len) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error setting key length (%d bytes) for %s cipher for decryption: %s", key_len, cipher->algo, proxy_ssh_crypto_get_errors()); return -1; } pr_trace_msg(trace_channel, 19, "set key length (%d) for %s cipher for decryption", key_len, cipher->algo); } #if defined(PR_USE_OPENSSL_EVP_CIPHERINIT_EX) if (EVP_CipherInit_ex(pctx, NULL, NULL, cipher->key, NULL, -1) != 1) { #else if (EVP_CipherInit(pctx, NULL, cipher->key, NULL, -1) != 1) { #endif /* PR_USE_OPENSSL_EVP_CIPHERINIT_EX */ (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error re-initializing %s cipher for decryption: %s", cipher->algo, proxy_ssh_crypto_get_errors()); return -1; } if (set_cipher_discarded(cipher, pctx) < 0) { return -1; } if (strcmp(cipher->algo, "aes128-ctr") == 0 || strcmp(cipher->algo, "aes128-gcm@openssh.com") == 0 || strcmp(cipher->algo, "aes192-ctr") == 0 || strcmp(cipher->algo, "aes256-ctr") == 0 || strcmp(cipher->algo, "aes256-gcm@openssh.com") == 0) { /* For some reason, OpenSSL returns 8 for the AES CTR/GCM block size (even * though the AES block size is 16, per RFC 5647), but OpenSSH wants 16. */ proxy_ssh_cipher_set_read_block_size(16); } else { proxy_ssh_cipher_set_read_block_size(EVP_CIPHER_block_size(cipher->cipher)); } return 0; } int proxy_ssh_cipher_read_data(struct proxy_ssh_packet *pkt, unsigned char *data, uint32_t data_len, unsigned char **buf, uint32_t *buflen) { struct proxy_ssh_cipher *cipher; EVP_CIPHER_CTX *pctx; size_t auth_len, read_blocksz; uint32_t output_buflen; cipher = &(read_ciphers[read_cipher_idx]); pctx = read_ctxs[read_cipher_idx]; read_blocksz = read_blockszs[read_cipher_idx]; auth_len = proxy_ssh_cipher_get_read_auth_size(); output_buflen = *buflen; if (cipher->key != NULL) { int res; unsigned char *ptr = NULL, *buf2 = NULL; if (*buf == NULL) { size_t bufsz; /* Allocate a buffer that's large enough. */ bufsz = (data_len + read_blocksz - 1); ptr = buf2 = pcalloc(pkt->pool, bufsz); } else { ptr = buf2 = *buf; } if (pkt->packet_len == 0) { if (auth_len > 0) { #if defined(EVP_CTRL_GCM_IV_GEN) unsigned char prev_iv[1]; /* Increment the IV. */ if (EVP_CIPHER_CTX_ctrl(pctx, EVP_CTRL_GCM_IV_GEN, 1, prev_iv) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error incrementing %s IV data for server: %s", cipher->algo, proxy_ssh_crypto_get_errors()); errno = EIO; return -1; } #endif } if (pkt->aad_len > 0 && pkt->aad == NULL) { pkt->aad = pcalloc(pkt->pool, pkt->aad_len); memcpy(pkt->aad, data, pkt->aad_len); memcpy(ptr, data, pkt->aad_len); /* Save room at the start of the output buffer `ptr` for the AAD * bytes. */ buf2 += pkt->aad_len; data += pkt->aad_len; data_len -= pkt->aad_len; output_buflen -= pkt->aad_len; if (auth_len > 0) { if (EVP_Cipher(pctx, NULL, pkt->aad, pkt->aad_len) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error setting %s AAD data for server: %s", cipher->algo, proxy_ssh_crypto_get_errors()); errno = EIO; return -1; } } } } if (output_buflen % read_blocksz != 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "bad input length for decryption (%u bytes, %lu AAD bytes, " "%u block size)", output_buflen, (unsigned long) pkt->aad_len, (unsigned int) read_blocksz); return -1; } if (pkt->packet_len > 0 && auth_len > 0) { unsigned char *tag_data = NULL; uint32_t tag_datalen = auth_len; /* The authentication tag appears after the unencrypted AAD bytes, and * the encrypted payload bytes. */ tag_data = data + (data_len - auth_len); #if defined(EVP_CTRL_GCM_GET_TAG) if (EVP_CIPHER_CTX_ctrl(pctx, EVP_CTRL_GCM_SET_TAG, tag_datalen, tag_data) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error setting %s authentication tag for server: %s", cipher->algo, proxy_ssh_crypto_get_errors()); errno = EIO; return -1; } #endif data_len -= auth_len; } res = EVP_Cipher(pctx, buf2, data, data_len); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error decrypting %s data from server: %s", cipher->algo, proxy_ssh_crypto_get_errors()); return -1; } if (pkt->packet_len > 0) { *buflen = data_len; } else { /* If we don't know the packet length yet, it means we need to allow for * the processing of the AAD bytes. */ *buflen = pkt->aad_len + data_len; } *buf = ptr; if (pkt->packet_len > 0 && auth_len > 0) { /* Verify the authentication tag, but only if we have the full packet. */ if (EVP_Cipher(pctx, NULL, NULL, 0) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error verifying %s authentication tag for server: %s", cipher->algo, proxy_ssh_crypto_get_errors()); errno = EIO; return -1; } } return 0; } *buf = data; *buflen = data_len; return 0; } const char *proxy_ssh_cipher_get_write_algo(void) { if (write_ciphers[write_cipher_idx].key != NULL || strcmp(write_ciphers[write_cipher_idx].algo, "none") == 0) { return write_ciphers[write_cipher_idx].algo; } return NULL; } int proxy_ssh_cipher_set_write_algo(pool *p, const char *algo) { unsigned int idx = write_cipher_idx; size_t key_len = 0, auth_len = 0, discard_len = 0; if (write_ciphers[idx].key != NULL) { /* If we have an existing key, it means that we are currently rekeying. */ idx = get_next_write_index(); } write_ciphers[idx].cipher = proxy_ssh_crypto_get_cipher(algo, &key_len, &auth_len, &discard_len); if (write_ciphers[idx].cipher == NULL) { return -1; } if (key_len > 0) { pr_trace_msg(trace_channel, 19, "setting write key for cipher %s: key len = %lu", algo, (unsigned long) key_len); } if (auth_len > 0) { pr_trace_msg(trace_channel, 19, "setting write key for cipher %s: auth len = %lu", algo, (unsigned long) auth_len); } if (discard_len > 0) { pr_trace_msg(trace_channel, 19, "setting write key for cipher %s: discard len = %lu", algo, (unsigned long) discard_len); } /* Note that we use a new pool, each time the algorithm is set (which * happens during key exchange) to prevent undue memory growth for * long-lived sessions with many rekeys. */ if (write_ciphers[idx].pool != NULL) { destroy_pool(write_ciphers[idx].pool); } write_ciphers[idx].pool = make_sub_pool(p); pr_pool_tag(write_ciphers[idx].pool, "Proxy SFTP cipher write pool"); write_ciphers[idx].algo = pstrdup(write_ciphers[idx].pool, algo); write_ciphers[idx].key_len = (uint32_t) key_len; write_ciphers[idx].auth_len = (uint32_t) auth_len; write_ciphers[idx].discard_len = discard_len; return 0; } int proxy_ssh_cipher_set_write_key(pool *p, const EVP_MD *md, const unsigned char *k, uint32_t klen, const char *h, uint32_t hlen, int role) { const unsigned char *id = NULL; char letter; uint32_t id_len; int key_len, auth_len; struct proxy_ssh_cipher *cipher; EVP_CIPHER_CTX *pctx; /* Currently unused. */ (void) p; switch_write_cipher(); cipher = &(write_ciphers[write_cipher_idx]); pctx = write_ctxs[write_cipher_idx]; id_len = proxy_ssh_session_get_id(&id); /* The letters used depend on the role; see: * https://tools.ietf.org/html/rfc4253#section-7.2 * * If we are the CLIENT, then we use the letters for the "client to server" * flows, since we are WRITING to the server. */ /* client-to-server IV: HASH(K || H || "A" || session_id) * server-to-client IV: HASH(K || H || "B" || session_id) */ letter = (role == PROXY_SSH_ROLE_CLIENT ? 'A' : 'B'); if (set_cipher_iv(cipher, md, k, klen, h, hlen, letter, id, id_len) < 0) { return -1; } /* client-to-server key: HASH(K || H || "C" || session_id) * server-to-client key: HASH(K || H || "D" || session_id) */ letter = (role == PROXY_SSH_ROLE_CLIENT ? 'C' : 'D'); if (set_cipher_key(cipher, md, k, klen, h, hlen, letter, id, id_len) < 0) { return -1; } #if OPENSSL_VERSION_NUMBER < 0x10100000L || \ defined(HAVE_LIBRESSL) EVP_CIPHER_CTX_init(pctx); #else EVP_CIPHER_CTX_reset(pctx); #endif #if defined(PR_USE_OPENSSL_EVP_CIPHERINIT_EX) if (EVP_CipherInit_ex(pctx, cipher->cipher, NULL, NULL, cipher->iv, 1) != 1) { #else if (EVP_CipherInit(pctx, cipher->cipher, NULL, cipher->iv, 1) != 1) { #endif /* PR_USE_OPENSSL_EVP_CIPHERINIT_EX */ (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error initializing %s cipher for encryption: %s", cipher->algo, proxy_ssh_crypto_get_errors()); return -1; } auth_len = (int) cipher->auth_len; if (auth_len > 0) { #if defined(EVP_CTRL_GCM_SET_IV_FIXED) if (EVP_CIPHER_CTX_ctrl(pctx, EVP_CTRL_GCM_SET_IV_FIXED, -1, cipher->iv) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error configuring %s cipher for encryption: %s", cipher->algo, proxy_ssh_crypto_get_errors()); return -1; } #endif /* EVP_CTRL_GCM_SET_IV_FIXED */ pr_trace_msg(trace_channel, 19, "set auth length (%d) for %s cipher for encryption", auth_len, cipher->algo); } /* Next, set the key length. */ key_len = (int) cipher->key_len; if (key_len > 0) { if (EVP_CIPHER_CTX_set_key_length(pctx, key_len) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error setting key length (%d bytes) for %s cipher for decryption: %s", key_len, cipher->algo, proxy_ssh_crypto_get_errors()); return -1; } pr_trace_msg(trace_channel, 19, "set key length (%d) for %s cipher for encryption", key_len, cipher->algo); } #if defined(PR_USE_OPENSSL_EVP_CIPHERINIT_EX) if (EVP_CipherInit_ex(pctx, NULL, NULL, cipher->key, NULL, -1) != 1) { #else if (EVP_CipherInit(pctx, NULL, cipher->key, NULL, -1) != 1) { #endif /* PR_USE_OPENSSL_EVP_CIPHERINIT_EX */ (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error re-initializing %s cipher for encryption: %s", cipher->algo, proxy_ssh_crypto_get_errors()); return -1; } if (set_cipher_discarded(cipher, pctx) < 0) { return -1; } if (strcmp(cipher->algo, "aes128-ctr") == 0 || strcmp(cipher->algo, "aes128-gcm@openssh.com") == 0 || strcmp(cipher->algo, "aes192-ctr") == 0 || strcmp(cipher->algo, "aes256-ctr") == 0 || strcmp(cipher->algo, "aes256-gcm@openssh.com") == 0) { /* For some reason, OpenSSL returns 8 for the AES CTR/GCM block size (even * though the AES block size is 16, per RFC 5647), but OpenSSH wants 16. */ proxy_ssh_cipher_set_write_block_size(16); } else { proxy_ssh_cipher_set_write_block_size(EVP_CIPHER_block_size(cipher->cipher)); } pr_trace_msg(trace_channel, 19, "set block size (%d) for %s cipher for encryption", (int) proxy_ssh_cipher_get_write_block_size(), cipher->algo); return 0; } int proxy_ssh_cipher_write_data(struct proxy_ssh_packet *pkt, unsigned char *buf, size_t *buflen) { struct proxy_ssh_cipher *cipher; EVP_CIPHER_CTX *pctx; size_t auth_len = 0; cipher = &(write_ciphers[write_cipher_idx]); pctx = write_ctxs[write_cipher_idx]; auth_len = proxy_ssh_cipher_get_write_auth_size(); if (cipher->key != NULL) { int res; unsigned char *data, *ptr; uint32_t datalen, datasz, len = 0; /* Always leave a little extra room in the buffer. */ datasz = sizeof(uint32_t) + pkt->packet_len + 64; if (pkt->aad_len > 0) { /* Packet length is not encrypted for authentication encryption, or * Encrypt-Then-MAC modes. */ datasz -= pkt->aad_len; /* And, for ETM modes, we may need a little more space. */ datasz += proxy_ssh_cipher_get_write_block_size(); } datalen = datasz; ptr = data = palloc(pkt->pool, datasz); if (auth_len > 0) { #if defined(EVP_CTRL_GCM_IV_GEN) unsigned char prev_iv[1]; /* Increment the IV. */ if (EVP_CIPHER_CTX_ctrl(pctx, EVP_CTRL_GCM_IV_GEN, 1, prev_iv) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error incrementing %s IV data for server: %s", cipher->algo, proxy_ssh_crypto_get_errors()); errno = EIO; return -1; } #endif } if (pkt->aad_len > 0 && pkt->aad == NULL) { uint32_t packet_len; packet_len = htonl(pkt->packet_len); pkt->aad = pcalloc(pkt->pool, pkt->aad_len); memcpy(pkt->aad, &packet_len, pkt->aad_len); if (auth_len > 0) { if (EVP_Cipher(pctx, NULL, pkt->aad, pkt->aad_len) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error setting %s AAD (%lu bytes) for server: %s", cipher->algo, (unsigned long) pkt->aad_len, proxy_ssh_crypto_get_errors()); errno = EIO; return -1; } } } else { len += proxy_ssh_msg_write_int(&data, &datalen, pkt->packet_len); } len += proxy_ssh_msg_write_byte(&data, &datalen, pkt->padding_len); len += proxy_ssh_msg_write_data(&data, &datalen, pkt->payload, pkt->payload_len, FALSE); len += proxy_ssh_msg_write_data(&data, &datalen, pkt->padding, pkt->padding_len, FALSE); res = EVP_Cipher(pctx, buf, ptr, len); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error encrypting %s data for server: %s", cipher->algo, proxy_ssh_crypto_get_errors()); errno = EIO; return -1; } *buflen = len; #ifdef SFTP_DEBUG_PACKET { unsigned int i; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "encrypted packet data (len %lu):", (unsigned long) *buflen); for (i = 0; i < *buflen;) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, " %02x%02x %02x%02x %02x%02x %02x%02x", ((unsigned char *) buf)[i], ((unsigned char *) buf)[i+1], ((unsigned char *) buf)[i+2], ((unsigned char *) buf)[i+3], ((unsigned char *) buf)[i+4], ((unsigned char *) buf)[i+5], ((unsigned char *) buf)[i+6], ((unsigned char *) buf)[i+7]); i += 8; } } #endif if (auth_len > 0) { unsigned char *tag_data = NULL; uint32_t tag_datalen = 0; if (EVP_Cipher(pctx, NULL, NULL, 0) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error generating %s authentication tag for server: %s", cipher->algo, proxy_ssh_crypto_get_errors()); errno = EIO; return -1; } tag_datalen = auth_len; tag_data = pcalloc(pkt->pool, tag_datalen); #if defined(EVP_CTRL_GCM_GET_TAG) if (EVP_CIPHER_CTX_ctrl(pctx, EVP_CTRL_GCM_GET_TAG, tag_datalen, tag_data) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error getting %s authentication tag for server: %s", cipher->algo, proxy_ssh_crypto_get_errors()); errno = EIO; return -1; } #endif pkt->mac_len = tag_datalen; pkt->mac = tag_data; } return 0; } *buflen = 0; return 0; } #if OPENSSL_VERSION_NUMBER < 0x1000000fL /* In older versions of OpenSSL, there was not a way to dynamically allocate * an EVP_CIPHER_CTX object. Thus we have these static objects for those * older versions. */ static EVP_CIPHER_CTX read_ctx1, read_ctx2; static EVP_CIPHER_CTX write_ctx1, write_ctx2; #endif /* prior to OpenSSL-1.0.0 */ int proxy_ssh_cipher_init(void) { #if OPENSSL_VERSION_NUMBER < 0x1000000fL read_ctxs[0] = &read_ctx1; read_ctxs[1] = &read_ctx2; write_ctxs[0] = &write_ctx1; write_ctxs[1] = &write_ctx2; #else read_ctxs[0] = EVP_CIPHER_CTX_new(); read_ctxs[1] = EVP_CIPHER_CTX_new(); write_ctxs[0] = EVP_CIPHER_CTX_new(); write_ctxs[1] = EVP_CIPHER_CTX_new(); #endif /* OpenSSL-1.0.0 and later */ return 0; } int proxy_ssh_cipher_free(void) { #if OPENSSL_VERSION_NUMBER >= 0x1000000fL EVP_CIPHER_CTX_free(read_ctxs[0]); EVP_CIPHER_CTX_free(read_ctxs[1]); EVP_CIPHER_CTX_free(write_ctxs[0]); EVP_CIPHER_CTX_free(write_ctxs[1]); #endif /* OpenSSL-1.0.0 and later */ return 0; } #endif /* PR_USE_OPENSSL */ proftpd-mod_proxy-0.9.5/lib/proxy/ssh/compress.c000066400000000000000000000344231475737016700217730ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH compression * Copyright (c) 2021-2022 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "proxy/ssh/msg.h" #include "proxy/ssh/packet.h" #include "proxy/ssh/crypto.h" #include "proxy/ssh/compress.h" #if defined(PR_USE_OPENSSL) #ifdef HAVE_ZLIB_H #include static const char *trace_channel = "proxy.ssh.compress"; struct proxy_ssh_compress { int use_zlib; int stream_ready; }; /* We need to keep the old compression contexts around, so that we can handle * N arbitrary packets to/from the client using the old contexts, as during * rekeying. Thus we have two read compression contexts, two write compression * contexts. The compression idx variable indicates which of the contexts is * currently in use. */ static struct proxy_ssh_compress read_compresses[] = { { 0, FALSE }, { 0, FALSE } }; static z_stream read_streams[2]; static struct proxy_ssh_compress write_compresses[] = { { 0, FALSE }, { 0, FALSE } }; static z_stream write_streams[2]; static unsigned int read_comp_idx = 0; static unsigned int write_comp_idx = 0; static unsigned int get_next_read_index(void) { if (read_comp_idx == 1) { return 0; } return 1; } static unsigned int get_next_write_index(void) { if (write_comp_idx == 1) { return 0; } return 1; } static void switch_read_compress(int flags) { struct proxy_ssh_compress *comp; z_stream *stream; comp = &(read_compresses[read_comp_idx]); stream = &(read_streams[read_comp_idx]); /* First we can free up the read stream, kept from rekeying. */ if (comp->use_zlib == flags && comp->stream_ready == TRUE) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "done decompressing data: decompressed %" PR_LU " bytes to %" PR_LU " bytes of data (%.2f)", (pr_off_t) stream->total_in, (pr_off_t) stream->total_out, stream->total_in == 0 ? 0.0 : (float) stream->total_out / stream->total_in); inflateEnd(stream); comp->use_zlib = 0; comp->stream_ready = FALSE; /* Now we can switch the index. */ if (read_comp_idx == 1) { read_comp_idx = 0; return; } read_comp_idx = 1; } } static void switch_write_compress(int flags) { struct proxy_ssh_compress *comp; z_stream *stream; comp = &(write_compresses[write_comp_idx]); stream = &(write_streams[write_comp_idx]); /* First we can free up the write stream, kept from rekeying. */ if (comp->use_zlib == flags && comp->stream_ready == TRUE) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "done compressing data: compressed %" PR_LU " bytes to %" PR_LU " bytes of data (%.2f)", (pr_off_t) stream->total_in, (pr_off_t) stream->total_out, stream->total_in == 0 ? 0.0 : (float) stream->total_out / stream->total_in); deflateEnd(stream); comp->use_zlib = 0; comp->stream_ready = FALSE; /* Now we can switch the index. */ if (write_comp_idx == 1) { write_comp_idx = 0; return; } write_comp_idx = 1; } } const char *proxy_ssh_compress_get_read_algo(void) { struct proxy_ssh_compress *comp; comp = &(read_compresses[read_comp_idx]); if (comp->use_zlib != 0) { if (comp->use_zlib == PROXY_SSH_COMPRESS_FL_NEW_KEY) { return "zlib"; } if (comp->use_zlib == PROXY_SSH_COMPRESS_FL_AUTHENTICATED) { return "zlib@openssh.com"; } } return "none"; } int proxy_ssh_compress_set_read_algo(pool *p, const char *algo) { unsigned int idx = read_comp_idx; /* Currently unused. */ (void) p; if (read_compresses[idx].stream_ready) { /* If we have an existing stream, it means that we are currently * rekeying. */ idx = get_next_read_index(); } if (strcmp(algo, "zlib@openssh.com") == 0) { read_compresses[idx].use_zlib = PROXY_SSH_COMPRESS_FL_AUTHENTICATED; return 0; } if (strcmp(algo, "zlib") == 0) { read_compresses[idx].use_zlib = PROXY_SSH_COMPRESS_FL_NEW_KEY; return 0; } if (strcmp(algo, "none") == 0) { return 0; } errno = EINVAL; return -1; } int proxy_ssh_compress_init_read(int flags) { struct proxy_ssh_compress *comp; z_stream *stream; switch_read_compress(flags); comp = &(read_compresses[read_comp_idx]); stream = &(read_streams[read_comp_idx]); if (comp->use_zlib == flags && comp->stream_ready == FALSE) { int zres; zres = inflateInit(stream); if (zres != Z_OK) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error preparing decompression stream (%d)", zres); } comp->stream_ready = TRUE; } return 0; } int proxy_ssh_compress_read_data(struct proxy_ssh_packet *pkt) { struct proxy_ssh_compress *comp; z_stream *stream; comp = &(read_compresses[read_comp_idx]); stream = &(read_streams[read_comp_idx]); if (comp->use_zlib != 0 && comp->stream_ready == TRUE) { unsigned char buf[16384], *input; char *payload; uint32_t input_len, payload_len = 0, payload_sz; pool *sub_pool; int zres; if (pkt->payload_len == 0) { return 0; } sub_pool = make_sub_pool(pkt->pool); /* Use a copy of the payload, rather than the actual payload itself, * as zlib may alter the payload contents and then encounter an error. */ input_len = pkt->payload_len; input = palloc(sub_pool, input_len); memcpy(input, pkt->payload, input_len); /* Try to guess at how big the uncompressed data will be. Optimistic * estimate, for now, will be a factor of 8. */ payload_sz = input_len * 8; payload = palloc(sub_pool, payload_sz); stream->next_in = input; stream->avail_in = input_len; while (TRUE) { size_t copy_len = 0; pr_signals_handle(); stream->next_out = buf; stream->avail_out = sizeof(buf); zres = inflate(stream, Z_SYNC_FLUSH); switch (zres) { case Z_OK: copy_len = sizeof(buf) - stream->avail_out; /* Allocate more space for the data if necessary. */ if ((payload_len + copy_len) > payload_sz) { uint32_t new_sz; char *tmp; pr_signals_handle(); new_sz = payload_sz; while ((payload_len + copy_len) > new_sz) { pr_signals_handle(); /* Keep increasing the size until it is large enough. */ new_sz += payload_sz; } pr_trace_msg(trace_channel, 20, "allocating larger payload size (%lu bytes) for " "inflated data (%lu bytes) plus existing payload %lu bytes", (unsigned long) new_sz, (unsigned long) copy_len, (unsigned long) payload_len); tmp = palloc(sub_pool, new_sz); memcpy(tmp, payload, payload_len); payload = tmp; payload_sz = new_sz; } if (copy_len > 0) { memcpy(payload + payload_len, buf, copy_len); payload_len += copy_len; pr_trace_msg(trace_channel, 20, "inflated %lu bytes to %lu bytes", (unsigned long) input_len, (unsigned long) copy_len); } continue; case Z_BUF_ERROR: break; default: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unhandled zlib error (%d) while decompressing", zres); destroy_pool(sub_pool); return -1; } break; } /* Make sure that pkt->payload has enough room for the uncompressed data. * If not, allocate a larger buffer. */ if (pkt->payload_len < payload_len) { pkt->payload = palloc(pkt->pool, payload_len); } memcpy(pkt->payload, payload, payload_len); pkt->payload_len = payload_len; pr_trace_msg(trace_channel, 20, "finished inflating (payload len = %lu bytes)", (unsigned long) payload_len); destroy_pool(sub_pool); } return 0; } const char *proxy_ssh_compress_get_write_algo(void) { struct proxy_ssh_compress *comp; comp = &(write_compresses[write_comp_idx]); if (comp->use_zlib != 0) { if (comp->use_zlib == PROXY_SSH_COMPRESS_FL_NEW_KEY) { return "zlib"; } if (comp->use_zlib == PROXY_SSH_COMPRESS_FL_AUTHENTICATED) { return "zlib@openssh.com"; } } return "none"; } int proxy_ssh_compress_set_write_algo(pool *p, const char *algo) { unsigned int idx = write_comp_idx; /* Currently unused. */ (void) p; if (write_compresses[idx].stream_ready) { /* If we have an existing stream, it means that we are currently * rekeying. */ idx = get_next_write_index(); } if (strcmp(algo, "zlib@openssh.com") == 0) { write_compresses[idx].use_zlib = PROXY_SSH_COMPRESS_FL_AUTHENTICATED; return 0; } if (strcmp(algo, "zlib") == 0) { write_compresses[idx].use_zlib = PROXY_SSH_COMPRESS_FL_NEW_KEY; return 0; } if (strcmp(algo, "none") == 0) { return 0; } errno = EINVAL; return -1; } int proxy_ssh_compress_init_write(int flags) { struct proxy_ssh_compress *comp; z_stream *stream; switch_write_compress(flags); comp = &(write_compresses[write_comp_idx]); stream = &(write_streams[write_comp_idx]); if (comp->use_zlib == flags && comp->stream_ready == FALSE) { int zres; zres = deflateInit(stream, Z_DEFAULT_COMPRESSION); if (zres != Z_OK) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error preparing compression stream (%d)", zres); } comp->stream_ready = TRUE; } return 0; } int proxy_ssh_compress_write_data(struct proxy_ssh_packet *pkt) { struct proxy_ssh_compress *comp; z_stream *stream; comp = &(write_compresses[write_comp_idx]); stream = &(write_streams[write_comp_idx]); if (comp->use_zlib != 0 && comp->stream_ready == TRUE) { unsigned char buf[16384], *input; char *payload; uint32_t input_len, payload_len = 0, payload_sz; pool *sub_pool; int zres; if (pkt->payload_len == 0) { return 0; } sub_pool = make_sub_pool(pkt->pool); /* Use a copy of the payload, rather than the actual payload itself, * as zlib may alter the payload contents and then encounter an error. */ input_len = pkt->payload_len; input = palloc(sub_pool, input_len); memcpy(input, pkt->payload, input_len); /* Try to guess at how small the compressed data will be. Optimistic * estimate, for now, will be a factor of 2, with a minimum of 1K. */ payload_sz = 1024; if ((input_len * 2) > payload_sz) { payload_sz = input_len * 2; } payload = palloc(sub_pool, payload_sz); stream->next_in = input; stream->avail_in = input_len; stream->avail_out = 0; while (stream->avail_out == 0) { size_t copy_len = 0; pr_signals_handle(); stream->next_out = buf; stream->avail_out = sizeof(buf); zres = deflate(stream, Z_SYNC_FLUSH); if (zres == Z_OK) { copy_len = sizeof(buf) - stream->avail_out; /* Allocate more space for the data if necessary. */ if ((payload_len + copy_len) > payload_sz) { uint32_t new_sz; char *tmp; new_sz = payload_sz; while ((payload_len + copy_len) > new_sz) { pr_signals_handle(); /* Keep increasing the size until it is large enough. */ new_sz += payload_sz; } pr_trace_msg(trace_channel, 20, "allocating larger payload size (%lu bytes) for " "deflated data (%lu bytes) plus existing payload %lu bytes", (unsigned long) new_sz, (unsigned long) copy_len, (unsigned long) payload_len); tmp = palloc(sub_pool, new_sz); memcpy(tmp, payload, payload_len); payload = tmp; payload_sz = new_sz; } memcpy(payload + payload_len, buf, copy_len); payload_len += copy_len; pr_trace_msg(trace_channel, 20, "deflated %lu bytes to %lu bytes", (unsigned long) input_len, (unsigned long) copy_len); } else { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unhandled zlib error (%d) while compressing", zres); destroy_pool(sub_pool); errno = EIO; return -1; } } if (payload_len > 0) { if (pkt->payload_len < payload_len) { pkt->payload = palloc(pkt->pool, payload_len); } memcpy(pkt->payload, payload, payload_len); pkt->payload_len = payload_len; pr_trace_msg(trace_channel, 20, "finished deflating (payload len = %lu bytes)", (unsigned long) payload_len); } destroy_pool(sub_pool); } return 0; } #else int proxy_ssh_compress_init_read(int flags) { return 0; } const char *proxy_ssh_compress_get_read_algo(void) { return "none"; } int proxy_ssh_compress_set_read_algo(pool *p, const char *algo) { (void) p; if (strcmp(algo, "none") == 0) { return 0; } errno = EINVAL; return -1; } int proxy_ssh_compress_read_data(struct proxy_ssh_packet *pkt) { return 0; } int proxy_ssh_compress_init_write(int flags) { return 0; } const char *proxy_ssh_compress_get_write_algo(void) { return "none"; } int proxy_ssh_compress_set_write_algo(pool *p, const char *algo) { (void) p; if (strcmp(algo, "none") == 0) { return 0; } errno = EINVAL; return -1; } int proxy_ssh_compress_write_data(struct proxy_ssh_packet *pkt) { return 0; } #endif /* !HAVE_ZLIB_H */ #endif /* PR_USE_OPENSSL */ proftpd-mod_proxy-0.9.5/lib/proxy/ssh/crypto.c000066400000000000000000001201701475737016700214530ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH crypto * Copyright (c) 2021-2024 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "proxy/ssh/crypto.h" #include "proxy/ssh/umac.h" #if defined(PR_USE_OPENSSL) #include #if !defined(OPENSSL_NO_BF) # include #endif /* !OPENSSL_NO_BF */ #if !defined(OPENSSL_NO_DES) # include #endif /* !OPENSSL_NO_DES */ #include #if OPENSSL_VERSION_NUMBER > 0x000907000L && \ OPENSSL_VERSION_NUMBER < 0x10100000L && \ defined(PR_USE_OPENSSL_ENGINE) # include static const char *crypto_engine = NULL; #endif struct proxy_ssh_cipher { const char *name; const char *openssl_name; /* Used mostly for AEAD algorithms like GCM. */ size_t auth_len; /* Used mostly for the RC4/ArcFour algorithms, for mitigating attacks * based on the first N bytes of the keystream. */ size_t discard_len; const EVP_CIPHER *(*get_type)(void); /* Is this cipher enabled by default? If FALSE, then this cipher must * be explicitly requested via SFTPCiphers. */ int enabled; /* Is this cipher usable when FIPS is enabled? If FALSE, then this * cipher must NOT be advertised to clients in the KEXINIT. */ int fips_allowed; }; /* Currently, OpenSSL does NOT support AES CTR modes (not sure why). * Until then, we have to provide our own CTR code, for some of the ciphers * recommended by RFC4344. * * And according to: * * http://www.cpni.gov.uk/Docs/Vulnerability_Advisory_SSH.txt * * it is highly recommended to use CTR mode ciphers, rather than CBC mode, * in order to avoid leaking plaintext. */ static struct proxy_ssh_cipher ciphers[] = { /* The handling of NULL openssl_name and get_type fields is done in * proxy_ssh_crypto_get_cipher(), as special cases. */ { "aes256-ctr", NULL, 0, 0, NULL, TRUE, TRUE }, { "aes192-ctr", NULL, 0, 0, NULL, TRUE, TRUE }, { "aes128-ctr", NULL, 0, 0, NULL, TRUE, TRUE }, #if defined(HAVE_EVP_AES_256_GCM_OPENSSL) { "aes256-gcm@openssh.com", "aes-256-gcm", 16, 0, EVP_aes_256_gcm, TRUE, TRUE }, { "aes128-gcm@openssh.com", "aes-128-gcm", 16, 0, EVP_aes_128_gcm, TRUE, TRUE }, #endif #if !defined(HAVE_AES_CRIPPLED_OPENSSL) { "aes256-cbc", "aes-256-cbc", 0, 0, EVP_aes_256_cbc, TRUE, TRUE }, { "aes192-cbc", "aes-192-cbc", 0, 0, EVP_aes_192_cbc, TRUE, TRUE }, #endif /* !HAVE_AES_CRIPPLED_OPENSSL */ { "aes128-cbc", "aes-128-cbc", 0, 0, EVP_aes_128_cbc, TRUE, TRUE }, #if !defined(OPENSSL_NO_BF) # if OPENSSL_VERSION_NUMBER < 0x30000000L { "blowfish-ctr", NULL, 0, 0, NULL, FALSE, FALSE }, # endif /* Prior to OpenSSL 3.x */ { "blowfish-cbc", "bf-cbc", 0, 0, EVP_bf_cbc, FALSE, FALSE }, #endif /* !OPENSSL_NO_BF */ #if !defined(OPENSSL_NO_CAST) { "cast128-cbc", "cast5-cbc", 0, 0, EVP_cast5_cbc, TRUE, FALSE }, #endif /* !OPENSSL_NO_CAST */ #if !defined(OPENSSL_NO_RC4) { "arcfour256", "rc4", 0, 1536, EVP_rc4, FALSE, FALSE }, { "arcfour128", "rc4", 0, 1536, EVP_rc4, FALSE, FALSE }, #endif /* !OPENSSL_NO_RC4 */ #if !defined(OPENSSL_NO_DES) # if OPENSSL_VERSION_NUMBER < 0x30000000L { "3des-ctr", NULL, 0, 0, NULL, TRUE, TRUE }, # endif /* Prior to OpenSSL 3.x */ { "3des-cbc", "des-ede3-cbc", 0, 0, EVP_des_ede3_cbc, TRUE, TRUE }, #endif /* !OPENSSL_NO_DES */ { "none", "null", 0, 0, EVP_enc_null, FALSE, TRUE }, { NULL, NULL, 0, 0, NULL, FALSE, FALSE } }; struct proxy_ssh_digest { const char *name; const char *openssl_name; const EVP_MD *(*get_type)(void); uint32_t mac_len; /* Is this MAC enabled by default? If FALSE, then this MAC must be * explicitly requested via SFTPDigests. */ int enabled; /* Is this MAC usable when FIPS is enabled? If FALSE, then this digest must * NOT be advertised to clients in the KEXINIT. */ int fips_allowed; }; static struct proxy_ssh_digest digests[] = { /* The handling of NULL openssl_name and get_type fields is done in * proxy_ssh_crypto_get_digest(), as special cases. */ #if defined(HAVE_SHA256_OPENSSL) { "hmac-sha2-256", "sha256", EVP_sha256, 0, TRUE, TRUE }, { "hmac-sha2-256-etm@openssh.com", "sha256", EVP_sha256, 0, TRUE, TRUE }, #endif /* SHA256 support in OpenSSL */ #if defined(HAVE_SHA512_OPENSSL) { "hmac-sha2-512", "sha512", EVP_sha512, 0, TRUE, TRUE }, { "hmac-sha2-512-etm@openssh.com", "sha512", EVP_sha512, 0, TRUE, TRUE }, #endif /* SHA512 support in OpenSSL */ { "hmac-sha1", "sha1", EVP_sha1, 0, TRUE, TRUE }, { "hmac-sha1-etm@openssh.com", "sha1",EVP_sha1, 0, TRUE, TRUE }, { "hmac-sha1-96", "sha1", EVP_sha1, 12, TRUE, TRUE }, { "hmac-sha1-96-etm@openssh.com", "sha1", EVP_sha1, 12, TRUE, TRUE }, { "hmac-md5", "md5", EVP_md5, 0, FALSE, FALSE }, { "hmac-md5-etm@openssh.com", "md5", EVP_md5, 0, FALSE, FALSE }, { "hmac-md5-96", "md5", EVP_md5, 12, FALSE, FALSE }, { "hmac-md5-96-etm@openssh.com", "md5",EVP_md5, 12, FALSE, FALSE }, #if !defined(OPENSSL_NO_RIPEMD) { "hmac-ripemd160", "rmd160", EVP_ripemd160, 0, FALSE, FALSE }, #endif /* !OPENSSL_NO_RIPEMD */ #if OPENSSL_VERSION_NUMBER > 0x000907000L { "umac-64@openssh.com", NULL, NULL, 8, TRUE, FALSE }, { "umac-64-etm@openssh.com", NULL, NULL, 8, TRUE, FALSE }, { "umac-128@openssh.com", NULL, NULL, 16, TRUE, FALSE }, { "umac-128-etm@openssh.com", NULL, NULL, 16, TRUE, FALSE }, #endif /* OpenSSL-0.9.7 or later */ { "none", "null", EVP_md_null, 0, FALSE, TRUE }, { NULL, NULL, NULL, 0, FALSE, FALSE } }; static const char *trace_channel = "proxy.ssh.crypto"; #if OPENSSL_VERSION_NUMBER < 0x30000000L static void ctr_incr(unsigned char *ctr, size_t len) { register int i; if (len == 0) { return; } for (i = len - 1; i >= 0; i--) { /* If we haven't overflowed, we're done. */ if (++ctr[i]) { return; } } } #endif /* Prior to OpenSSL 3.x */ #if !defined(OPENSSL_NO_BF) && \ OPENSSL_VERSION_NUMBER < 0x30000000L /* Blowfish CTR mode implementation */ struct bf_ctr_ex { BF_KEY key; unsigned char counter[BF_BLOCK]; }; static int init_bf_ctr(EVP_CIPHER_CTX *ctx, const unsigned char *key, const unsigned char *iv, int enc) { struct bf_ctr_ex *bce; bce = EVP_CIPHER_CTX_get_app_data(ctx); if (bce == NULL) { /* Allocate our data structure. */ bce = calloc(1, sizeof(struct bf_ctr_ex)); if (bce == NULL) { pr_log_pri(PR_LOG_ALERT, MOD_PROXY_VERSION ": Out of memory!"); _exit(1); } EVP_CIPHER_CTX_set_app_data(ctx, bce); } if (key != NULL) { int key_len; # if OPENSSL_VERSION_NUMBER == 0x0090805fL /* OpenSSL 0.9.8e had a bug where EVP_CIPHER_CTX_key_length() returned * the cipher key length rather than the context key length. */ key_len = ctx->key_len; # else key_len = EVP_CIPHER_CTX_key_length(ctx); # endif BF_set_key(&(bce->key), key_len, key); } if (iv != NULL) { memcpy(bce->counter, iv, BF_BLOCK); } return 1; } static int cleanup_bf_ctr(EVP_CIPHER_CTX *ctx) { struct bf_ctr_ex *bce; bce = EVP_CIPHER_CTX_get_app_data(ctx); if (bce != NULL) { pr_memscrub(bce, sizeof(struct bf_ctr_ex)); free(bce); EVP_CIPHER_CTX_set_app_data(ctx, NULL); } return 1; } static int do_bf_ctr(EVP_CIPHER_CTX *ctx, unsigned char *dst, const unsigned char *src, size_t len) { struct bf_ctr_ex *bce; unsigned int n; unsigned char buf[BF_BLOCK]; if (len == 0) return 1; bce = EVP_CIPHER_CTX_get_app_data(ctx); if (bce == NULL) return 0; n = 0; while ((len--) > 0) { pr_signals_handle(); if (n == 0) { BF_LONG ctr[2]; /* Ideally, we would not be using htonl/ntohl here, and the following * code would be as simple as: * * memcpy(buf, bce->counter, BF_BLOCK); * BF_encrypt((BF_LONG *) buf, &(bce->key)); * * However, the above is susceptible to endianness issues. The only * client that I could find which implements the blowfish-ctr cipher, * PuTTy, uses its own big-endian Blowfish implementation. So the * above code will work with PuTTy, but only on big-endian machines. * For little-endian machines, we need to handle the endianness * ourselves. Whee. */ memcpy(&(ctr[0]), bce->counter, sizeof(BF_LONG)); memcpy(&(ctr[1]), bce->counter + sizeof(BF_LONG), sizeof(BF_LONG)); /* Convert to big-endian values before encrypting the counter... */ ctr[0] = htonl(ctr[0]); ctr[1] = htonl(ctr[1]); BF_encrypt(ctr, &(bce->key)); /* ...and convert back to little-endian before XOR'ing the counter in. */ ctr[0] = ntohl(ctr[0]); ctr[1] = ntohl(ctr[1]); memcpy(buf, ctr, BF_BLOCK); ctr_incr(bce->counter, BF_BLOCK); } *(dst++) = *(src++) ^ buf[n]; n = (n + 1) % BF_BLOCK; } return 1; } static const EVP_CIPHER *get_bf_ctr_cipher(void) { EVP_CIPHER *cipher; #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) /* XXX TODO: At some point, we also need to call EVP_CIPHER_meth_free() on * this, to avoid a resource leak. */ cipher = EVP_CIPHER_meth_new(NID_bf_cbc, BF_BLOCK, 32); EVP_CIPHER_meth_set_iv_length(cipher, BF_BLOCK); EVP_CIPHER_meth_set_init(cipher, init_bf_ctr); EVP_CIPHER_meth_set_cleanup(cipher, cleanup_bf_ctr); EVP_CIPHER_meth_set_do_cipher(cipher, do_bf_ctr); EVP_CIPHER_meth_set_flags(cipher, EVP_CIPH_CBC_MODE|EVP_CIPH_VARIABLE_LENGTH|EVP_CIPH_ALWAYS_CALL_INIT|EVP_CIPH_CUSTOM_IV); #else static EVP_CIPHER bf_ctr_cipher; memset(&bf_ctr_cipher, 0, sizeof(EVP_CIPHER)); bf_ctr_cipher.nid = NID_bf_cbc; bf_ctr_cipher.block_size = BF_BLOCK; bf_ctr_cipher.iv_len = BF_BLOCK; bf_ctr_cipher.key_len = 32; bf_ctr_cipher.init = init_bf_ctr; bf_ctr_cipher.cleanup = cleanup_bf_ctr; bf_ctr_cipher.do_cipher = do_bf_ctr; bf_ctr_cipher.flags = EVP_CIPH_CBC_MODE|EVP_CIPH_VARIABLE_LENGTH|EVP_CIPH_ALWAYS_CALL_INIT|EVP_CIPH_CUSTOM_IV; cipher = &bf_ctr_cipher; #endif /* prior to OpenSSL-1.1.0 */ return cipher; } #endif /* !OPENSSL_NO_BF and OpenSSL prior to 3.x */ #if !defined(OPENSSL_NO_DES) && \ OPENSSL_VERSION_NUMBER < 0x30000000L /* 3DES CTR mode implementation */ struct des3_ctr_ex { DES_key_schedule sched[3]; unsigned char counter[8]; int big_endian; }; static uint32_t byteswap32(uint32_t in) { uint32_t out; out = (((in & 0x000000ff) << 24) | ((in & 0x0000ff00) << 8) | ((in & 0x00ff0000) >> 8) | ((in & 0xff000000) >> 24)); return out; } static int init_des3_ctr(EVP_CIPHER_CTX *ctx, const unsigned char *key, const unsigned char *iv, int enc) { struct des3_ctr_ex *dce; dce = EVP_CIPHER_CTX_get_app_data(ctx); if (dce == NULL) { /* Allocate our data structure. */ dce = calloc(1, sizeof(struct des3_ctr_ex)); if (dce == NULL) { pr_log_pri(PR_LOG_ALERT, MOD_PROXY_VERSION ": Out of memory!"); _exit(1); } /* Simple test to see if we're on a big- or little-endian machine: * on big-endian machines, the ntohl() et al will be no-ops. */ dce->big_endian = (ntohl(1234) == 1234); EVP_CIPHER_CTX_set_app_data(ctx, dce); } if (key != NULL) { register unsigned int i; unsigned char *ptr; ptr = (unsigned char *) key; for (i = 0; i < 3; i++) { DES_cblock material[8]; memcpy(material, ptr, 8); ptr += 8; DES_set_key_unchecked(material, &(dce->sched[i])); } } if (iv != NULL) { memcpy(dce->counter, iv, 8); } return 1; } static int cleanup_des3_ctr(EVP_CIPHER_CTX *ctx) { struct des3_ctr_ex *dce; dce = EVP_CIPHER_CTX_get_app_data(ctx); if (dce != NULL) { pr_memscrub(dce, sizeof(struct des3_ctr_ex)); free(dce); EVP_CIPHER_CTX_set_app_data(ctx, NULL); } return 1; } static int do_des3_ctr(EVP_CIPHER_CTX *ctx, unsigned char *dst, const unsigned char *src, size_t len) { struct des3_ctr_ex *dce; unsigned int n; unsigned char buf[8]; if (len == 0) { return 1; } dce = EVP_CIPHER_CTX_get_app_data(ctx); if (dce == NULL) { return 0; } n = 0; while ((len--) > 0) { pr_signals_handle(); if (n == 0) { DES_LONG ctr[2]; memcpy(&(ctr[0]), dce->counter, sizeof(DES_LONG)); memcpy(&(ctr[1]), dce->counter + sizeof(DES_LONG), sizeof(DES_LONG)); if (dce->big_endian) { /* If we are on a big-endian machine, we need to initialize the counter * using little-endian values, since that is what OpenSSL's * DES_encryptX() functions expect. */ ctr[0] = byteswap32(ctr[0]); ctr[1] = byteswap32(ctr[1]); } DES_encrypt3(ctr, &(dce->sched[0]), &(dce->sched[1]), &(dce->sched[2])); if (dce->big_endian) { ctr[0] = byteswap32(ctr[0]); ctr[1] = byteswap32(ctr[1]); } memcpy(buf, ctr, 8); ctr_incr(dce->counter, 8); } *(dst++) = *(src++) ^ buf[n]; n = (n + 1) % 8; } return 1; } static const EVP_CIPHER *get_des3_ctr_cipher(void) { EVP_CIPHER *cipher; #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) unsigned long flags; /* XXX TODO: At some point, we also need to call EVP_CIPHER_meth_free() on * this, to avoid a resource leak. */ cipher = EVP_CIPHER_meth_new(NID_des_ede3_ecb, 8, 24); EVP_CIPHER_meth_set_iv_length(cipher, 8); EVP_CIPHER_meth_set_init(cipher, init_des3_ctr); EVP_CIPHER_meth_set_cleanup(cipher, cleanup_des3_ctr); EVP_CIPHER_meth_set_do_cipher(cipher, do_des3_ctr); flags = EVP_CIPH_CBC_MODE|EVP_CIPH_VARIABLE_LENGTH|EVP_CIPH_ALWAYS_CALL_INIT|EVP_CIPH_CUSTOM_IV; #ifdef OPENSSL_FIPS flags |= EVP_CIPH_FLAG_FIPS; #endif /* OPENSSL_FIPS */ EVP_CIPHER_meth_set_flags(cipher, flags); #else static EVP_CIPHER des3_ctr_cipher; memset(&des3_ctr_cipher, 0, sizeof(EVP_CIPHER)); des3_ctr_cipher.nid = NID_des_ede3_ecb; des3_ctr_cipher.block_size = 8; des3_ctr_cipher.iv_len = 8; des3_ctr_cipher.key_len = 24; des3_ctr_cipher.init = init_des3_ctr; des3_ctr_cipher.cleanup = cleanup_des3_ctr; des3_ctr_cipher.do_cipher = do_des3_ctr; des3_ctr_cipher.flags = EVP_CIPH_CBC_MODE|EVP_CIPH_VARIABLE_LENGTH|EVP_CIPH_ALWAYS_CALL_INIT|EVP_CIPH_CUSTOM_IV; #ifdef OPENSSL_FIPS des3_ctr_cipher.flags |= EVP_CIPH_FLAG_FIPS; #endif /* OPENSSL_FIPS */ cipher = &des3_ctr_cipher; #endif /* prior to OpenSSL-1.1.0 */ return cipher; } #endif /* !OPENSSL_NO_DES and OpenSSL prior to 3.x */ #if !defined(HAVE_EVP_AES_128_CTR_OPENSSL) && \ !defined(HAVE_EVP_AES_192_CTR_OPENSSL) && \ !defined(HAVE_EVP_AES_256_CTR_OPENSSL) /* AES CTR mode implementation */ struct aes_ctr_ex { AES_KEY key; unsigned char counter[AES_BLOCK_SIZE]; unsigned char enc_counter[AES_BLOCK_SIZE]; unsigned int num; }; static int init_aes_ctr(EVP_CIPHER_CTX *ctx, const unsigned char *key, const unsigned char *iv, int enc) { struct aes_ctr_ex *ace; ace = EVP_CIPHER_CTX_get_app_data(ctx); if (ace == NULL) { /* Allocate our data structure. */ ace = calloc(1, sizeof(struct aes_ctr_ex)); if (ace == NULL) { pr_log_pri(PR_LOG_ALERT, MOD_PROXY_VERSION ": Out of memory!"); _exit(1); } EVP_CIPHER_CTX_set_app_data(ctx, ace); } if (key != NULL) { int nbits; nbits = EVP_CIPHER_CTX_key_length(ctx) * 8; AES_set_encrypt_key(key, nbits, &(ace->key)); } if (iv != NULL) { memcpy(ace->counter, iv, AES_BLOCK_SIZE); } return 1; } static int cleanup_aes_ctr(EVP_CIPHER_CTX *ctx) { struct aes_ctr_ex *ace; ace = EVP_CIPHER_CTX_get_app_data(ctx); if (ace != NULL) { pr_memscrub(ace, sizeof(struct aes_ctr_ex)); free(ace); EVP_CIPHER_CTX_set_app_data(ctx, NULL); } return 1; } static int do_aes_ctr(EVP_CIPHER_CTX *ctx, unsigned char *dst, const unsigned char *src, size_t len) { struct aes_ctr_ex *ace; # if OPENSSL_VERSION_NUMBER <= 0x0090704fL || \ OPENSSL_VERSION_NUMBER >= 0x10100000L unsigned int n; unsigned char buf[AES_BLOCK_SIZE]; # endif if (len == 0) { return 1; } ace = EVP_CIPHER_CTX_get_app_data(ctx); if (ace == NULL) { return 0; } # if OPENSSL_VERSION_NUMBER <= 0x0090704fL || \ OPENSSL_VERSION_NUMBER >= 0x10100000L /* In OpenSSL-0.9.7d and earlier, the AES CTR code did not properly handle * the IV as big-endian; this would cause the dreaded "Incorrect MAC * received on packet" error when using clients e.g. PuTTy. To see * the difference in OpenSSL, you have do manually do: * * diff -u openssl-0.9.7d/crypto/aes/aes_ctr.c \ * openssl-0.9.7e/crypto/aes/aes_ctr.c * * This change is not documented in OpenSSL's CHANGES file. Sigh. * * And in OpenSSL-1.1.0 and later, the AES CTR code was removed entirely. * * Thus for these versions, we have to use our own AES CTR code. */ n = 0; while ((len--) > 0) { pr_signals_handle(); if (n == 0) { AES_encrypt(ace->counter, buf, &(ace->key)); ctr_incr(ace->counter, AES_BLOCK_SIZE); } *(dst++) = *(src++) ^ buf[n]; n = (n + 1) % AES_BLOCK_SIZE; } return 1; # else /* Thin wrapper around AES_ctr128_encrypt(). */ AES_ctr128_encrypt(src, dst, len, &(ace->key), ace->counter, ace->enc_counter, &(ace->num)); # endif return 1; } static int get_aes_ctr_cipher_nid(int key_len) { int nid; #ifdef OPENSSL_FIPS /* Set the NID depending on the key len. */ switch (key_len) { case 16: nid = NID_aes_128_cbc; break; case 24: nid = NID_aes_192_cbc; break; case 32: nid = NID_aes_256_cbc; break; default: nid = NID_undef; break; } #else /* Setting this nid member to something other than NID_undef causes * interesting problems on an OpenSolaris system, using the provided * OpenSSL installation's pkcs11 engine via: * * ProxySSHCryptoDevice pkcs11 * * for the mod_sftp config. I'm not sure why; I need to look into this * issue more. * * For posterity, the issues seen when using the above config are * described below. After sending the NEWKEYS request, mod_sftp * would log the following, upon receiving the next message from sftp(1): * * : SSH2 packet len = 1500737511 bytes * : SSH2 packet padding len = 95 bytes * : SSH2 packet payload len = 1500737415 bytes * : payload len (1500737415 bytes) exceeds max payload len (262144), ignoring payload * client sent buggy/malicious packet payload length, ignoring * * and sftp(1), for its side, would report: * * debug1: send SSH2_MSG_SERVICE_REQUEST * Disconnecting: Bad packet length. * debug1: Calling cleanup 0x807cc14(0x0) * Couldn't read packet: Error 0 */ nid = NID_undef; #endif /* OPENSSL_FIPS */ return nid; } static const EVP_CIPHER *get_aes_ctr_cipher(int key_len) { EVP_CIPHER *cipher; #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) unsigned long flags; /* XXX TODO: At some point, we also need to call EVP_CIPHER_meth_free() on * this, to avoid a resource leak. */ cipher = EVP_CIPHER_meth_new(get_aes_ctr_cipher_nid(key_len), AES_BLOCK_SIZE, key_len); EVP_CIPHER_meth_set_iv_length(cipher, AES_BLOCK_SIZE); EVP_CIPHER_meth_set_init(cipher, init_aes_ctr); EVP_CIPHER_meth_set_cleanup(cipher, cleanup_aes_ctr); EVP_CIPHER_meth_set_do_cipher(cipher, do_aes_ctr); flags = EVP_CIPH_CBC_MODE|EVP_CIPH_VARIABLE_LENGTH|EVP_CIPH_ALWAYS_CALL_INIT|EVP_CIPH_CUSTOM_IV; #ifdef OPENSSL_FIPS flags |= EVP_CIPH_FLAG_FIPS; #endif /* OPENSSL_FIPS */ EVP_CIPHER_meth_set_flags(cipher, flags); #else static EVP_CIPHER aes_ctr_cipher; memset(&aes_ctr_cipher, 0, sizeof(EVP_CIPHER)); aes_ctr_cipher.nid = get_aes_ctr_cipher_nid(key_len); aes_ctr_cipher.block_size = AES_BLOCK_SIZE; aes_ctr_cipher.iv_len = AES_BLOCK_SIZE; aes_ctr_cipher.key_len = key_len; aes_ctr_cipher.init = init_aes_ctr; aes_ctr_cipher.cleanup = cleanup_aes_ctr; aes_ctr_cipher.do_cipher = do_aes_ctr; aes_ctr_cipher.flags = EVP_CIPH_CBC_MODE|EVP_CIPH_VARIABLE_LENGTH|EVP_CIPH_ALWAYS_CALL_INIT|EVP_CIPH_CUSTOM_IV; # ifdef OPENSSL_FIPS aes_ctr_cipher.flags |= EVP_CIPH_FLAG_FIPS; # endif /* OPENSSL_FIPS */ cipher = &aes_ctr_cipher; #endif /* prior to OpenSSL-1.1.0 */ return cipher; } #endif /* OpenSSL implements AES CTR modes */ static int update_umac64(EVP_MD_CTX *ctx, const void *data, size_t len) { int res; void *md_data; #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) md_data = EVP_MD_CTX_md_data(ctx); #else md_data = ctx->md_data; #endif /* prior to OpenSSL-1.1.0 */ if (md_data == NULL) { struct umac_ctx *umac; void **ptr; umac = proxy_ssh_umac_new((unsigned char *) data); if (umac == NULL) { return 0; } ptr = &md_data; *ptr = umac; return 1; } res = proxy_ssh_umac_update(md_data, (unsigned char *) data, (long) len); return res; } static int update_umac128(EVP_MD_CTX *ctx, const void *data, size_t len) { int res; void *md_data; #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) md_data = EVP_MD_CTX_md_data(ctx); #else md_data = ctx->md_data; #endif /* prior to OpenSSL-1.1.0 */ if (md_data == NULL) { struct umac_ctx *umac; void **ptr; umac = proxy_ssh_umac128_new((unsigned char *) data); if (umac == NULL) { return 0; } ptr = &md_data; *ptr = umac; return 1; } res = proxy_ssh_umac128_update(md_data, (unsigned char *) data, (long) len); return res; } static int final_umac64(EVP_MD_CTX *ctx, unsigned char *md) { unsigned char nonce[8]; int res; void *md_data; #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) md_data = EVP_MD_CTX_md_data(ctx); #else md_data = ctx->md_data; #endif /* prior to OpenSSL-1.1.0 */ res = proxy_ssh_umac_final(md_data, md, nonce); return res; } static int final_umac128(EVP_MD_CTX *ctx, unsigned char *md) { unsigned char nonce[8]; int res; void *md_data; #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) md_data = EVP_MD_CTX_md_data(ctx); #else md_data = ctx->md_data; #endif /* prior to OpenSSL-1.1.0 */ res = proxy_ssh_umac128_final(md_data, md, nonce); return res; } static int delete_umac64(EVP_MD_CTX *ctx) { struct umac_ctx *umac; void *md_data, **ptr; #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) md_data = EVP_MD_CTX_md_data(ctx); #else md_data = ctx->md_data; #endif /* prior to OpenSSL-1.1.0 */ umac = md_data; proxy_ssh_umac_delete(umac); ptr = &md_data; *ptr = NULL; return 1; } static int delete_umac128(EVP_MD_CTX *ctx) { struct umac_ctx *umac; void *md_data, **ptr; #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) md_data = EVP_MD_CTX_md_data(ctx); #else md_data = ctx->md_data; #endif /* prior to OpenSSL-1.1.0 */ umac = md_data; proxy_ssh_umac128_delete(umac); ptr = &md_data; *ptr = NULL; return 1; } static const EVP_MD *get_umac64_digest(void) { EVP_MD *md; #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) /* XXX TODO: At some point, we also need to call EVP_MD_meth_free() on * this, to avoid a resource leak. */ md = EVP_MD_meth_new(NID_undef, NID_undef); EVP_MD_meth_set_input_blocksize(md, 32); EVP_MD_meth_set_result_size(md, 8); EVP_MD_meth_set_flags(md, 0UL); EVP_MD_meth_set_update(md, update_umac64); EVP_MD_meth_set_final(md, final_umac64); EVP_MD_meth_set_cleanup(md, delete_umac64); #else static EVP_MD umac64_digest; memset(&umac64_digest, 0, sizeof(EVP_MD)); umac64_digest.type = NID_undef; umac64_digest.pkey_type = NID_undef; umac64_digest.md_size = 8; umac64_digest.flags = 0UL; umac64_digest.update = update_umac64; umac64_digest.final = final_umac64; umac64_digest.cleanup = delete_umac64; umac64_digest.block_size = 32; md = &umac64_digest; #endif /* prior to OpenSSL-1.1.0 */ return md; } static const EVP_MD *get_umac128_digest(void) { EVP_MD *md; #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) /* XXX TODO: At some point, we also need to call EVP_MD_meth_free() on * this, to avoid a resource leak. */ md = EVP_MD_meth_new(NID_undef, NID_undef); EVP_MD_meth_set_input_blocksize(md, 64); EVP_MD_meth_set_result_size(md, 16); EVP_MD_meth_set_flags(md, 0UL); EVP_MD_meth_set_update(md, update_umac128); EVP_MD_meth_set_final(md, final_umac128); EVP_MD_meth_set_cleanup(md, delete_umac128); #else static EVP_MD umac128_digest; memset(&umac128_digest, 0, sizeof(EVP_MD)); umac128_digest.type = NID_undef; umac128_digest.pkey_type = NID_undef; umac128_digest.md_size = 16; umac128_digest.flags = 0UL; umac128_digest.update = update_umac128; umac128_digest.final = final_umac128; umac128_digest.cleanup = delete_umac128; umac128_digest.block_size = 64; md = &umac128_digest; #endif /* prior to OpenSSL-1.1.0 */ return md; } const EVP_CIPHER *proxy_ssh_crypto_get_cipher(const char *name, size_t *key_len, size_t *auth_len, size_t *discard_len) { register unsigned int i; for (i = 0; ciphers[i].name; i++) { if (strcmp(ciphers[i].name, name) == 0) { const EVP_CIPHER *cipher; if (strcmp(name, "blowfish-ctr") == 0) { #if !defined(OPENSSL_NO_BF) && \ OPENSSL_VERSION_NUMBER < 0x30000000L cipher = get_bf_ctr_cipher(); #else (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "'%s' cipher unsupported", name); errno = ENOENT; return NULL; #endif /* !OPENSSL_NO_BF and OpenSSL prior to 3.x */ #if OPENSSL_VERSION_NUMBER > 0x000907000L } else if (strcmp(name, "3des-ctr") == 0) { # if !defined(OPENSSL_NO_DES) && \ OPENSSL_VERSION_NUMBER < 0x30000000L cipher = get_des3_ctr_cipher(); # else (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "'%s' cipher unsupported", name); errno = ENOENT; return NULL; # endif /* !OPENSSL_NO_DES and OpenSSL prior to 3.x */ } else if (strcmp(name, "aes256-ctr") == 0) { # if defined(HAVE_EVP_AES_256_CTR_OPENSSL) cipher = EVP_aes_256_ctr(); # else cipher = get_aes_ctr_cipher(32); # endif /* HAVE_EVP_AES_256_CTR_OPENSSL */ } else if (strcmp(name, "aes192-ctr") == 0) { # if defined(HAVE_EVP_AES_192_CTR_OPENSSL) cipher = EVP_aes_192_ctr(); # else cipher = get_aes_ctr_cipher(24); # endif /* HAVE_EVP_AES_192_CTR_OPENSSL */ } else if (strcmp(name, "aes128-ctr") == 0) { # if defined(HAVE_EVP_AES_128_CTR_OPENSSL) cipher = EVP_aes_128_ctr(); # else cipher = get_aes_ctr_cipher(16); # endif /* HAVE_EVP_AES_128_CTR_OPENSSL */ #endif /* OpenSSL older than 0.9.7 */ } else { cipher = ciphers[i].get_type(); } if (key_len != NULL) { if (strcmp(name, "arcfour256") != 0) { *key_len = 0; } else { /* The arcfour256 cipher is special-cased here in order to use * a longer key (32 bytes), rather than the normal 16 bytes for the * RC4 cipher. */ *key_len = 32; } } if (auth_len != NULL) { *auth_len = ciphers[i].auth_len; } if (discard_len != NULL) { *discard_len = ciphers[i].discard_len; } return cipher; } } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "no cipher matching '%s' found", name); errno = ENOENT; return NULL; } const EVP_MD *proxy_ssh_crypto_get_digest(const char *name, uint32_t *mac_len) { register unsigned int i; if (name == NULL) { errno = EINVAL; return NULL; } for (i = 0; digests[i].name; i++) { if (strcmp(digests[i].name, name) == 0) { const EVP_MD *digest = NULL; #if OPENSSL_VERSION_NUMBER > 0x000907000L if (strcmp(name, "umac-64@openssh.com") == 0 || strcmp(name, "umac-64-etm@openssh.com") == 0) { digest = get_umac64_digest(); } else if (strcmp(name, "umac-128@openssh.com") == 0 || strcmp(name, "umac-128-etm@openssh.com") == 0) { digest = get_umac128_digest(); #else if (FALSE) { #endif /* OpenSSL older than 0.9.7 */ } else { digest = digests[i].get_type(); } if (mac_len != NULL) { *mac_len = digests[i].mac_len; } return digest; } } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "no digest matching '%s' found", name); return NULL; } const char *proxy_ssh_crypto_get_kexinit_cipher_list(pool *p) { char *res = ""; config_rec *c; /* Make sure that OpenSSL can use these ciphers. For example, in FIPS mode, * some ciphers cannot be used. So we should not advertise ciphers that we * know we cannot use. */ c = find_config(main_server->conf, CONF_PARAM, "ProxySFTPCiphers", FALSE); if (c) { register unsigned int i; for (i = 0; i < c->argc; i++) { register unsigned int j; for (j = 0; ciphers[j].name; j++) { if (strcmp(c->argv[i], ciphers[j].name) == 0) { #ifdef OPENSSL_FIPS if (FIPS_mode()) { /* If FIPS mode is enabled, check whether the cipher is allowed * for use. */ if (ciphers[j].fips_allowed == FALSE) { pr_trace_msg(trace_channel, 5, "cipher '%s' is disabled in FIPS mode, skipping", ciphers[j].name); continue; } } #endif /* OPENSSL_FIPS */ if (strcmp(c->argv[i], "none") != 0) { if (EVP_get_cipherbyname(ciphers[j].openssl_name) != NULL) { res = pstrcat(p, res, *res ? "," : "", pstrdup(p, ciphers[j].name), NULL); } else { /* The CTR modes are special cases. */ if (strcmp(ciphers[j].name, "blowfish-ctr") == 0 || strcmp(ciphers[j].name, "3des-ctr") == 0 || strcmp(ciphers[j].name, "aes256-ctr") == 0 || strcmp(ciphers[j].name, "aes192-ctr") == 0 || strcmp(ciphers[j].name, "aes128-ctr") == 0 #if defined(HAVE_EVP_AES_256_GCM_OPENSSL) || strcmp(ciphers[j].name, "aes128-gcm@openssh.com") == 0 || strcmp(ciphers[j].name, "aes256-gcm@openssh.com") == 0 #endif ) { res = pstrcat(p, res, *res ? "," : "", pstrdup(p, ciphers[j].name), NULL); } else { pr_trace_msg(trace_channel, 3, "unable to use '%s' cipher: Unsupported by OpenSSL", ciphers[j].name); } } } else { res = pstrcat(p, res, *res ? "," : "", pstrdup(p, ciphers[j].name), NULL); } } } } } else { register unsigned int i; for (i = 0; ciphers[i].name; i++) { if (ciphers[i].enabled) { #ifdef OPENSSL_FIPS if (FIPS_mode()) { /* If FIPS mode is enabled, check whether the cipher is allowed * for use. */ if (ciphers[i].fips_allowed == FALSE) { pr_trace_msg(trace_channel, 5, "cipher '%s' is disabled in FIPS mode, skipping", ciphers[i].name); continue; } } #endif /* OPENSSL_FIPS */ if (strcmp(ciphers[i].name, "none") != 0) { if (EVP_get_cipherbyname(ciphers[i].openssl_name) != NULL) { res = pstrcat(p, res, *res ? "," : "", pstrdup(p, ciphers[i].name), NULL); } else { /* The CTR modes are special cases. */ if (strcmp(ciphers[i].name, "blowfish-ctr") == 0 || strcmp(ciphers[i].name, "3des-ctr") == 0 || strcmp(ciphers[i].name, "aes256-ctr") == 0 || strcmp(ciphers[i].name, "aes192-ctr") == 0 || strcmp(ciphers[i].name, "aes128-ctr") == 0 #if defined(HAVE_EVP_AES_256_GCM_OPENSSL) || strcmp(ciphers[i].name, "aes128-gcm@openssh.com") == 0 || strcmp(ciphers[i].name, "aes256-gcm@openssh.com") == 0 #endif ) { res = pstrcat(p, res, *res ? "," : "", pstrdup(p, ciphers[i].name), NULL); } else { pr_trace_msg(trace_channel, 3, "unable to use '%s' cipher: Unsupported by OpenSSL", ciphers[i].name); } } } else { res = pstrcat(p, res, *res ? "," : "", pstrdup(p, ciphers[i].name), NULL); } } else { pr_trace_msg(trace_channel, 3, "unable to use '%s' cipher: " "Must be explicitly requested via ProxySFTPCiphers", ciphers[i].name); } } } return res; } const char *proxy_ssh_crypto_get_kexinit_digest_list(pool *p) { char *res = ""; config_rec *c; /* Make sure that OpenSSL can use these digests. For example, in FIPS * mode, some digests cannot be used. So we should not advertise digests * that we know we cannot use. */ c = find_config(main_server->conf, CONF_PARAM, "ProxySFTPDigests", FALSE); if (c != NULL) { register unsigned int i; for (i = 0; i < c->argc; i++) { register unsigned int j; for (j = 0; digests[j].name; j++) { if (strcmp(c->argv[i], digests[j].name) == 0) { #ifdef OPENSSL_FIPS if (FIPS_mode()) { /* If FIPS mode is enabled, check whether the MAC is allowed * for use. */ if (digests[j].fips_allowed == FALSE) { pr_trace_msg(trace_channel, 5, "digest '%s' is disabled in FIPS mode, skipping", digests[j].name); continue; } } #endif /* OPENSSL_FIPS */ if (strcmp(c->argv[i], "none") != 0) { if (digests[j].openssl_name != NULL && EVP_get_digestbyname(digests[j].openssl_name) != NULL) { res = pstrcat(p, res, *res ? "," : "", pstrdup(p, digests[j].name), NULL); } else { /* The umac-64/umac-128 digests are special cases. */ if (strcmp(digests[j].name, "umac-64@openssh.com") == 0 || strcmp(digests[j].name, "umac-64-etm@openssh.com") == 0 || strcmp(digests[j].name, "umac-128@openssh.com") == 0 || strcmp(digests[j].name, "umac-128-etm@openssh.com") == 0) { res = pstrcat(p, res, *res ? "," : "", pstrdup(p, digests[j].name), NULL); } else { pr_trace_msg(trace_channel, 3, "unable to use '%s' digest: Unsupported by OpenSSL", digests[j].name); } } } else { res = pstrcat(p, res, *res ? "," : "", pstrdup(p, digests[j].name), NULL); } } } } } else { register unsigned int i; for (i = 0; digests[i].name; i++) { if (digests[i].enabled) { #ifdef OPENSSL_FIPS if (FIPS_mode()) { /* If FIPS mode is enabled, check whether the digest is allowed * for use. */ if (digests[i].fips_allowed == FALSE) { pr_trace_msg(trace_channel, 5, "digest '%s' is disabled in FIPS mode, skipping", digests[i].name); continue; } } #endif /* OPENSSL_FIPS */ if (strcmp(digests[i].name, "none") != 0) { if (digests[i].openssl_name != NULL && EVP_get_digestbyname(digests[i].openssl_name) != NULL) { res = pstrcat(p, res, *res ? "," : "", pstrdup(p, digests[i].name), NULL); } else { /* The umac-64/umac-128 digests are special cases. */ if (strcmp(digests[i].name, "umac-64@openssh.com") == 0 || strcmp(digests[i].name, "umac-64-etm@openssh.com") == 0 || strcmp(digests[i].name, "umac-128@openssh.com") == 0 || strcmp(digests[i].name, "umac-128-etm@openssh.com") == 0) { res = pstrcat(p, res, *res ? "," : "", pstrdup(p, digests[i].name), NULL); } else { pr_trace_msg(trace_channel, 3, "unable to use '%s' digest: Unsupported by OpenSSL", digests[i].name); } } } else { res = pstrcat(p, res, *res ? "," : "", pstrdup(p, digests[i].name), NULL); } } else { pr_trace_msg(trace_channel, 3, "unable to use '%s' digest: " "Must be explicitly requested via ProxySFTPDigests", digests[i].name); } } } return res; } const char *proxy_ssh_crypto_get_errors(void) { unsigned int count = 0; unsigned long error_code; BIO *bio = NULL; char *data = NULL; long datalen; const char *error_data = NULL, *str = "(unknown)"; int error_flags = 0; /* Use ERR_print_errors() and a memory BIO to build up a string with * all of the error messages from the error queue. */ error_code = ERR_get_error_line_data(NULL, NULL, &error_data, &error_flags); if (error_code) { bio = BIO_new(BIO_s_mem()); } while (error_code) { if (error_flags & ERR_TXT_STRING) { BIO_printf(bio, "\n (%u) %s [%s]", ++count, ERR_error_string(error_code, NULL), error_data); } else { BIO_printf(bio, "\n (%u) %s", ++count, ERR_error_string(error_code, NULL)); } error_data = NULL; error_flags = 0; error_code = ERR_get_error_line_data(NULL, NULL, &error_data, &error_flags); } datalen = BIO_get_mem_data(bio, &data); if (data != NULL) { data[datalen] = '\0'; str = pstrdup(proxy_pool, data); } if (bio != NULL) { BIO_free(bio); } return str; } /* Try to find the best multiple/block size which accommodates the two given * sizes by rounding up. */ size_t proxy_ssh_crypto_get_size(size_t first, size_t second) { #ifdef roundup return roundup(first, second); #else return (((first + (second - 1)) / second) * second); #endif /* !roundup */ } void proxy_ssh_crypto_free(int flags) { /* XXX TODO! */ /* Only call EVP_cleanup() et al if other OpenSSL-using modules are not * present. If we called EVP_cleanup() here during a restart, * and other modules want to use OpenSSL, we may be depriving those modules * of OpenSSL functionality. * * At the moment, the modules known to use OpenSSL are mod_ldap, mod_radius, * mod_sftp, mod_sql, and mod_sql_passwd, and mod_tls. */ if (pr_module_get("mod_auth_otp.c") == NULL && pr_module_get("mod_digest.c") == NULL && pr_module_get("mod_ldap.c") == NULL && pr_module_get("mod_radius.c") == NULL && pr_module_get("mod_sftp.c") == NULL && pr_module_get("mod_sql.c") == NULL && pr_module_get("mod_sql_passwd.c") == NULL && pr_module_get("mod_tls.c") == NULL) { #if OPENSSL_VERSION_NUMBER > 0x000907000L && \ OPENSSL_VERSION_NUMBER < 0x10100000L && \ defined(PR_USE_OPENSSL_ENGINE) if (crypto_engine != NULL) { ENGINE_cleanup(); crypto_engine = NULL; } #endif #if OPENSSL_VERSION_NUMBER >= 0x10000001L /* The ERR_remove_state(0) usage is deprecated due to thread ID * differences among platforms; see the OpenSSL-1.0.0 CHANGES file * for details. So for new enough OpenSSL installations, use the * proper way to clear the error queue state. */ # if OPENSSL_VERSION_NUMBER < 0x10100000L ERR_remove_thread_state(NULL); # endif /* prior to OpenSSL-1.1.x */ #else ERR_remove_state(0); #endif /* OpenSSL prior to 1.0.0-beta1 */ #if OPENSSL_VERSION_NUMBER < 0x10100000L ERR_free_strings(); EVP_cleanup(); RAND_cleanup(); #endif /* prior to OpenSSL-1.1.x */ } } #endif /* PR_USE_OPENSSL */ proftpd-mod_proxy-0.9.5/lib/proxy/ssh/db.c000066400000000000000000000302571475737016700205260ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH database implementation * Copyright (c) 2021 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "proxy/db.h" #include "proxy/ssh.h" #include "proxy/ssh/db.h" #if defined(PR_USE_OPENSSL) extern xaset_t *server_list; static const char *trace_channel = "proxy.ssh.db"; #define PROXY_SSH_DB_SCHEMA_NAME "proxy_ssh" #define PROXY_SSH_DB_SCHEMA_VERSION 1 static unsigned long db_opts = 0UL; static int ssh_db_add_hostkey(pool *p, void *dsh, unsigned int vhost_id, const char *backend_uri, const char *algo, const unsigned char *hostkey_data, uint32_t hostkey_datalen) { int res, xerrno = 0; struct proxy_dbh *dbh; const char *stmt, *errstr = NULL; array_header *results; dbh = dsh; stmt = "INSERT INTO proxy_ssh_hostkeys (vhost_id, backend_uri, algo, hostkey) VALUES (?, ?, ?, ?);"; res = proxy_db_prepare_stmt(p, dbh, stmt); if (res < 0) { xerrno = errno; (void) pr_log_debug(DEBUG3, MOD_PROXY_VERSION ": error preparing statement '%s': %s", stmt, strerror(xerrno)); errno = xerrno; return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT, (void *) &vhost_id, 0); if (res < 0) { return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT, (void *) backend_uri, -1); if (res < 0) { return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 3, PROXY_DB_BIND_TYPE_TEXT, (void *) algo, -1); if (res < 0) { return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 4, PROXY_DB_BIND_TYPE_BLOB, (void *) hostkey_data, (int) hostkey_datalen); if (res < 0) { return -1; } results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr); if (results == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr ? errstr : strerror(errno)); errno = EPERM; return -1; } return 0; } static const unsigned char *ssh_db_get_hostkey(pool *p, void *dsh, unsigned int vhost_id, const char *backend_uri, const char **algo, uint32_t *hostkey_datalen) { int res, xerrno; struct proxy_dbh *dbh; const char *stmt, *errstr = NULL; array_header *results; const unsigned char *hostkey_data = NULL; dbh = dsh; stmt = "SELECT algo, hostkey FROM proxy_ssh_hostkeys WHERE vhost_id = ? AND backend_uri = ?;"; res = proxy_db_prepare_stmt(p, dbh, stmt); if (res < 0) { xerrno = errno; (void) pr_log_debug(DEBUG3, MOD_PROXY_VERSION ": error preparing statement '%s': %s", stmt, strerror(xerrno)); errno = xerrno; return NULL; } res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT, (void *) &vhost_id, 0); if (res < 0) { return NULL; } res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT, (void *) backend_uri, -1); if (res < 0) { return NULL; } results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr); if (results == NULL || results->nelts == 0) { errno = ENOENT; return NULL; } /* We expect 3 items: one for the algo, one for the hostkey BLOB, and one for * BLOB length. */ if (results->nelts != 3) { pr_log_debug(DEBUG3, MOD_PROXY_VERSION ": expected 3 results from statement '%s', got %d", stmt, results->nelts); errno = EINVAL; return NULL; } *algo = ((char **) results->elts)[0]; hostkey_data = (const unsigned char *) ((char **) results->elts)[1]; *hostkey_datalen = atoi(((char **) results->elts)[2]); pr_trace_msg(trace_channel, 19, "retrieved hostkey (algo '%s', %lu bytes) for vhost ID %u, URI '%s'", *algo, (unsigned long) *hostkey_datalen, vhost_id, backend_uri); return hostkey_data; } static int ssh_db_update_hostkey(pool *p, void *dsh, unsigned int vhost_id, const char *backend_uri, const char *algo, const unsigned char *hostkey_data, uint32_t hostkey_datalen) { int res, xerrno = 0; struct proxy_dbh *dbh; const char *stmt, *errstr = NULL; array_header *results; dbh = dsh; stmt = "UPDATE proxy_ssh_hostkeys SET algo = ?, hostkey = ? WHERE vhost_id = ? AND backend_uri = ?;"; res = proxy_db_prepare_stmt(p, dbh, stmt); if (res < 0) { xerrno = errno; (void) pr_log_debug(DEBUG3, MOD_PROXY_VERSION ": error preparing statement '%s': %s", stmt, strerror(xerrno)); errno = xerrno; return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_TEXT, (void *) algo, -1); if (res < 0) { return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_BLOB, (void *) hostkey_data, (int) hostkey_datalen); if (res < 0) { return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 3, PROXY_DB_BIND_TYPE_INT, (void *) &vhost_id, 0); if (res < 0) { return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 4, PROXY_DB_BIND_TYPE_TEXT, (void *) backend_uri, -1); if (res < 0) { return -1; } results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr); if (results == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr ? errstr : strerror(errno)); errno = EPERM; return -1; } return 0; } /* Initialization routines */ static int ssh_db_add_schema(pool *p, void *dbh, const char *db_path) { int res; const char *stmt, *errstr = NULL; /* CREATE TABLE proxy_ssh_vhosts ( * vhost_id INTEGER NOT NULL PRIMARY KEY, * vhost_name TEXT NOT NULL * ); */ stmt = "CREATE TABLE IF NOT EXISTS proxy_ssh_vhosts (vhost_id INTEGER NOT NULL PRIMARY KEY, vhost_name TEXT NOT NULL);"; res = proxy_db_exec_stmt(p, dbh, stmt, &errstr); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr); errno = EPERM; return -1; } /* CREATE TABLE proxy_ssh_hostkeys ( * backend_uri STRING NOT NULL PRIMARY KEY, * vhost_id INTEGER NOT NULL, * algo TEXT NOT NULL, * hostkey BLOB NOT NULL, * FOREIGN KEY (vhost_id) REFERENCES proxy_ssh_vhosts (vhost_id) * ); */ stmt = "CREATE TABLE IF NOT EXISTS proxy_ssh_hostkeys (backend_uri STRING NOT NULL PRIMARY KEY, vhost_id INTEGER NOT NULL, algo TEXT NOT NULL, hostkey BLOB NOT NULL, FOREIGN KEY (vhost_id) REFERENCES proxy_ssh_hosts (vhost_id));"; res = proxy_db_exec_stmt(p, dbh, stmt, &errstr); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr); errno = EPERM; return -1; } /* Note that we deliberately do NOT truncate the hostkeys table. */ return 0; } static int ssh_truncate_db_tables(pool *p, void *dbh) { int res; const char *stmt, *errstr = NULL; stmt = "DELETE FROM proxy_ssh_vhosts;"; res = proxy_db_exec_stmt(p, dbh, stmt, &errstr); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr); errno = EPERM; return -1; } /* Note that we deliberately do NOT truncate the hostkeys table. */ return 0; } static int ssh_db_add_vhost(pool *p, void *dbh, server_rec *s) { int res, xerrno = 0; const char *stmt, *errstr = NULL; array_header *results; stmt = "INSERT INTO proxy_ssh_vhosts (vhost_id, vhost_name) VALUES (?, ?);"; res = proxy_db_prepare_stmt(p, dbh, stmt); if (res < 0) { xerrno = errno; (void) pr_log_debug(DEBUG3, MOD_PROXY_VERSION ": error preparing statement '%s': %s", stmt, strerror(xerrno)); errno = xerrno; return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT, (void *) &(s->sid), 0); if (res < 0) { return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT, (void *) s->ServerName, -1); if (res < 0) { return -1; } results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr); if (results == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr ? errstr : strerror(errno)); errno = EPERM; return -1; } return 0; } static int ssh_db_init(pool *p, const char *tables_path, int flags) { int db_flags, res, xerrno = 0; server_rec *s; struct proxy_dbh *dbh = NULL; const char *db_path = NULL; if (tables_path == NULL) { errno = EINVAL; return -1; } db_path = pdircat(p, tables_path, "proxy-ssh.db", NULL); db_flags = PROXY_DB_OPEN_FL_SCHEMA_VERSION_CHECK|PROXY_DB_OPEN_FL_INTEGRITY_CHECK|PROXY_DB_OPEN_FL_VACUUM; if (flags & PROXY_DB_OPEN_FL_SKIP_VACUUM) { /* If the caller needs us to skip the vacuum, we will. */ db_flags &= ~PROXY_DB_OPEN_FL_VACUUM; } PRIVS_ROOT dbh = proxy_db_open_with_version(p, db_path, PROXY_SSH_DB_SCHEMA_NAME, PROXY_SSH_DB_SCHEMA_VERSION, db_flags); xerrno = errno; PRIVS_RELINQUISH if (dbh == NULL) { (void) pr_log_pri(PR_LOG_NOTICE, MOD_PROXY_VERSION ": error opening database '%s' for schema '%s', version %u: %s", db_path, PROXY_SSH_DB_SCHEMA_NAME, PROXY_SSH_DB_SCHEMA_VERSION, strerror(xerrno)); errno = xerrno; return -1; } res = ssh_db_add_schema(p, dbh, db_path); if (res < 0) { xerrno = errno; (void) pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": error creating schema in database '%s' for '%s': %s", db_path, PROXY_SSH_DB_SCHEMA_NAME, strerror(xerrno)); (void) proxy_db_close(p, dbh); errno = xerrno; return -1; } res = ssh_truncate_db_tables(p, dbh); if (res < 0) { xerrno = errno; (void) proxy_db_close(p, dbh); errno = xerrno; return -1; } for (s = (server_rec *) server_list->xas_list; s; s = s->next) { res = ssh_db_add_vhost(p, dbh, s); if (res < 0) { xerrno = errno; (void) pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": error adding database entry for server '%s' in '%s': %s", s->ServerName, PROXY_SSH_DB_SCHEMA_NAME, strerror(xerrno)); (void) proxy_db_close(p, dbh); errno = xerrno; return -1; } } (void) proxy_db_close(p, dbh); return 0; } static int ssh_db_close(pool *p, void *dbh) { if (dbh != NULL) { if (proxy_db_close(p, dbh) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error closing %s database: %s", PROXY_SSH_DB_SCHEMA_NAME, strerror(errno)); } } return 0; } static void *ssh_db_open(pool *p, const char *tables_dir, unsigned long opts) { int xerrno = 0; struct proxy_dbh *dbh; const char *db_path; db_path = pdircat(p, tables_dir, "proxy-ssh.db", NULL); PRIVS_ROOT dbh = proxy_db_open_with_version(p, db_path, PROXY_SSH_DB_SCHEMA_NAME, PROXY_SSH_DB_SCHEMA_VERSION, 0); xerrno = errno; PRIVS_RELINQUISH if (dbh == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error opening database '%s' for schema '%s', version %u: %s", db_path, PROXY_SSH_DB_SCHEMA_NAME, PROXY_SSH_DB_SCHEMA_VERSION, strerror(xerrno)); errno = xerrno; return NULL; } db_opts = opts; return dbh; } int proxy_ssh_db_as_datastore(struct proxy_ssh_datastore *ds, void *ds_data, size_t ds_datasz) { if (ds == NULL) { errno = EINVAL; return -1; } (void) ds_data; (void) ds_datasz; ds->hostkey_add = ssh_db_add_hostkey; ds->hostkey_get = ssh_db_get_hostkey; ds->hostkey_update = ssh_db_update_hostkey; ds->init = ssh_db_init; ds->open = ssh_db_open; ds->close = ssh_db_close; return 0; } #endif /* PR_USE_OPENSSL */ proftpd-mod_proxy-0.9.5/lib/proxy/ssh/disconnect.c000066400000000000000000000120621475737016700222640ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH disconnects * Copyright (c) 2021 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "proxy/ssh/ssh2.h" #include "proxy/ssh/msg.h" #include "proxy/ssh/packet.h" #include "proxy/ssh/disconnect.h" #if defined(PR_USE_OPENSSL) struct disconnect_reason { uint32_t code; const char *explain; const char *lang; }; static struct disconnect_reason explanations[] = { { PROXY_SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT, "Host not allowed to connect", NULL }, { PROXY_SSH_DISCONNECT_PROTOCOL_ERROR, "Protocol error", NULL }, { PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, "Key exchange failed", NULL }, { PROXY_SSH_DISCONNECT_MAC_ERROR, "MAC error", NULL }, { PROXY_SSH_DISCONNECT_COMPRESSION_ERROR, "Compression error", NULL }, { PROXY_SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, "Requested service not available", NULL }, { PROXY_SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED, "Protocol version not supported", NULL }, { PROXY_SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE, "Host key not verifiable", NULL }, { PROXY_SSH_DISCONNECT_CONNECTION_LOST, "Connection lost", NULL }, { PROXY_SSH_DISCONNECT_BY_APPLICATION, "Application disconnected", NULL }, { PROXY_SSH_DISCONNECT_TOO_MANY_CONNECTIONS, "Too many connections", NULL }, { PROXY_SSH_DISCONNECT_AUTH_CANCELLED_BY_USER, "Authentication cancelled by user", NULL }, { PROXY_SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, "No other authentication mechanisms available", NULL }, { PROXY_SSH_DISCONNECT_ILLEGAL_USER_NAME, "Illegal user name", NULL }, { 0, NULL, NULL } }; static const char *trace_channel = "proxy.ssh.disconnect"; const char *proxy_ssh_disconnect_get_text(uint32_t reason_code) { register unsigned int i; for (i = 0; explanations[i].explain; i++) { if (explanations[i].code == reason_code) { return explanations[i].explain; } } errno = ENOENT; return NULL; } void proxy_ssh_disconnect_send(pool *p, conn_t *conn, uint32_t reason, const char *explain, const char *file, int lineno, const char *func) { struct proxy_ssh_packet *pkt; const char *lang = "en-US"; unsigned char *buf, *ptr; uint32_t buflen, bufsz, len = 0; /* Send the server a DISCONNECT mesg. */ pkt = proxy_ssh_packet_create(p); buflen = bufsz = 1024; ptr = buf = palloc(pkt->pool, bufsz); if (explain == NULL) { register unsigned int i; for (i = 0; explanations[i].explain; i++) { if (explanations[i].code == reason) { explain = explanations[i].explain; lang = explanations[i].lang; if (lang == NULL) { lang = "en-US"; } break; } } if (explain == NULL) { explain = "Unknown reason"; } } else { lang = "en-US"; } if (strlen(func) > 0) { pr_trace_msg(trace_channel, 9, "disconnecting (%s) [at %s:%d:%s()]", explain, file, lineno, func); } else { pr_trace_msg(trace_channel, 9, "disconnecting (%s) [at %s:%d]", explain, file, lineno); } len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_DISCONNECT); len += proxy_ssh_msg_write_int(&buf, &buflen, reason); len += proxy_ssh_msg_write_string(&buf, &buflen, explain); len += proxy_ssh_msg_write_string(&buf, &buflen, lang); pkt->payload = ptr; pkt->payload_len = len; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "disconnecting %s (%s)", pr_netaddr_get_ipstr(conn->remote_addr), explain); /* Explicitly set a short poll timeout of 2 secs. */ proxy_ssh_packet_set_poll_timeout(2, 0); if (proxy_ssh_packet_write(conn, pkt) < 0) { int xerrno = errno; pr_trace_msg(trace_channel, 12, "error writing DISCONNECT message: %s", strerror(xerrno)); } destroy_pool(pkt->pool); } void proxy_ssh_disconnect_conn(conn_t *conn, uint32_t reason, const char *explain, const char *file, int lineno, const char *func) { proxy_ssh_disconnect_send(proxy_pool, conn, reason, explain, file, lineno, func); #if defined(PR_DEVEL_COREDUMP) pr_session_end(PR_SESS_END_FL_NOEXIT); abort(); #else pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION, NULL); #endif /* PR_DEVEL_COREDUMP */ } #endif /* PR_USE_OPENSSL */ proftpd-mod_proxy-0.9.5/lib/proxy/ssh/interop.c000066400000000000000000000232771475737016700216250ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH interoperability * Copyright (c) 2021-2022 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "proxy/ssh/ssh2.h" #include "proxy/ssh/disconnect.h" #include "proxy/ssh/interop.h" #if defined(PR_USE_OPENSSL) /* By default, each server is assumed to support all of the features in * which we are interested. */ static unsigned int default_flags = PROXY_SSH_FEAT_IGNORE_MSG | PROXY_SSH_FEAT_MAC_LEN | PROXY_SSH_FEAT_CIPHER_USE_K | PROXY_SSH_FEAT_REKEYING | PROXY_SSH_FEAT_USERAUTH_BANNER | PROXY_SSH_FEAT_HAVE_PUBKEY_ALGO | PROXY_SSH_FEAT_SERVICE_IN_HOST_SIG | PROXY_SSH_FEAT_SERVICE_IN_PUBKEY_SIG | PROXY_SSH_FEAT_HAVE_PUBKEY_ALGO_IN_DSA_SIG | PROXY_SSH_FEAT_NO_DATA_WHILE_REKEYING | PROXY_SSH_FEAT_DH_NEW_GEX; struct proxy_ssh_version_pattern { const char *pattern; int disabled_flags; pr_regex_t *pre; }; static struct proxy_ssh_version_pattern known_versions[] = { { "^OpenSSH-2\\.0.*|" "^OpenSSH-2\\.1.*|" "^OpenSSH_2\\.1.*|" "^OpenSSH_2\\.2.*|" "^OpenSSH_2\\.3\\.0.*", PROXY_SSH_FEAT_USERAUTH_BANNER| PROXY_SSH_FEAT_REKEYING| PROXY_SSH_FEAT_DH_NEW_GEX, NULL }, { "^OpenSSH_2\\.3\\..*|" "^OpenSSH_2\\.5\\.0p1.*|" "^OpenSSH_2\\.5\\.1p1.*|" "^OpenSSH_2\\.5\\.0.*|" "^OpenSSH_2\\.5\\.1.*|" "^OpenSSH_2\\.5\\.2.*|" "^OpenSSH_2\\.5\\.3.*", PROXY_SSH_FEAT_REKEYING| PROXY_SSH_FEAT_DH_NEW_GEX, NULL }, { "^OpenSSH.*", 0, NULL }, { ".*J2SSH_Maverick.*", PROXY_SSH_FEAT_REKEYING, NULL }, { ".*MindTerm.*", 0, NULL }, { "^Sun_SSH_1\\.0.*", PROXY_SSH_FEAT_REKEYING, NULL }, { "^2\\.1\\.0.*|" "^2\\.1 .*", PROXY_SSH_FEAT_HAVE_PUBKEY_ALGO_IN_DSA_SIG| PROXY_SSH_FEAT_SERVICE_IN_HOST_SIG| PROXY_SSH_FEAT_MAC_LEN, NULL }, { "^2\\.0\\.13.*|" "^2\\.0\\.14.*|" "^2\\.0\\.15.*|" "^2\\.0\\.16.*|" "^2\\.0\\.17.*|" "^2\\.0\\.18.*|" "^2\\.0\\.19.*", PROXY_SSH_FEAT_HAVE_PUBKEY_ALGO_IN_DSA_SIG| PROXY_SSH_FEAT_SERVICE_IN_HOST_SIG| PROXY_SSH_FEAT_SERVICE_IN_PUBKEY_SIG| PROXY_SSH_FEAT_MAC_LEN, NULL }, { "^2\\.0\\.11.*|" "^2\\.0\\.12.*", PROXY_SSH_FEAT_HAVE_PUBKEY_ALGO_IN_DSA_SIG| PROXY_SSH_FEAT_SERVICE_IN_PUBKEY_SIG| PROXY_SSH_FEAT_HAVE_PUBKEY_ALGO| PROXY_SSH_FEAT_MAC_LEN, NULL }, { "^2\\.0\\..*", PROXY_SSH_FEAT_HAVE_PUBKEY_ALGO_IN_DSA_SIG| PROXY_SSH_FEAT_SERVICE_IN_PUBKEY_SIG| PROXY_SSH_FEAT_HAVE_PUBKEY_ALGO| PROXY_SSH_FEAT_CIPHER_USE_K| PROXY_SSH_FEAT_MAC_LEN, NULL }, { "^2\\.2\\.0.*|" "^2\\.3\\.0.*", PROXY_SSH_FEAT_MAC_LEN, NULL }, { "^1\\.2\\.18.*|" "^1\\.2\\.19.*|" "^1\\.2\\.20.*|" "^1\\.2\\.21.*|" "^1\\.2\\.22.*|" "^1\\.3\\.2.*|" "^3\\.2\\.9.*", PROXY_SSH_FEAT_IGNORE_MSG, NULL }, { ".*PuTTY.*|" ".*PUTTY.*|" ".*WinSCP.*", PROXY_SSH_FEAT_NO_DATA_WHILE_REKEYING, NULL }, { NULL, 0, NULL }, }; static const char *trace_channel = "proxy.ssh.interop"; int proxy_ssh_interop_handle_version(pool *p, const struct proxy_session *proxy_sess, const char *server_version) { register unsigned int i; size_t version_len; const char *version = NULL; char *ptr = NULL; config_rec *c; if (server_version == NULL) { errno = EINVAL; return -1; } version_len = strlen(server_version); /* The version string MUST conform to the following, as per Section 4.2 * of RFC4253: * * SSH-protoversion-softwareversion [SP comments] * * The 'comments' field is optional. The 'protoversion' MUST be "2.0". * The 'softwareversion' field MUST be printable ASCII characters and * cannot contain SP or the '-' character. */ for (i = 0; i < version_len; i++) { if (!PR_ISPRINT(server_version[i]) && server_version[i] != '-' && server_version[i] != ' ') { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "server-sent version contains non-printable or illegal characters, " "disconnecting"); PROXY_SSH_DISCONNECT_CONN(proxy_sess->backend_ctrl_conn, PROXY_SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED, NULL); } } /* Skip past the leading "SSH-2.0-" (or "SSH-1.99-") to get the actual * server info. */ if (strncmp(server_version, "SSH-2.0-", 8) == 0) { version = pstrdup(p, server_version + 8); } else if (strncmp(server_version, "SSH-1.99-", 9) == 0) { version = pstrdup(p, server_version + 9); } else { /* An illegally formatted server version. How did it get here? */ (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "server-sent version (%s) is illegally formmated, disconnecting", server_version); PROXY_SSH_DISCONNECT_CONN(proxy_sess->backend_ctrl_conn, PROXY_SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED, NULL); } /* Look for the optional comments field in the received server version; if * present, trim it out, so that we do not try to match on it. */ ptr = strchr(version, ' '); if (ptr != NULL) { pr_trace_msg(trace_channel, 11, "read server version with comments: '%s'", version); *ptr = '\0'; } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "handling connection to SSH2 server '%s'", version); pr_trace_msg(trace_channel, 5, "handling connection to SSH2 server '%s'", version); /* First matching pattern wins. */ for (i = 0; known_versions[i].pattern != NULL; i++) { int res; pr_signals_handle(); pr_trace_msg(trace_channel, 18, "checking server version '%s' against regex '%s'", version, known_versions[i].pattern); res = pr_regexp_exec(known_versions[i].pre, version, 0, NULL, 0, 0, 0); if (res == 0) { pr_trace_msg(trace_channel, 18, "server version '%s' matched against regex '%s'", version, known_versions[i].pattern); /* We have a match. */ default_flags &= ~(known_versions[i].disabled_flags); break; } else { pr_trace_msg(trace_channel, 18, "server version '%s' did not match regex '%s'", version, known_versions[i].pattern); } } /* Now iterate through any ProxySFTPServerMatch rules. */ c = find_config(main_server->conf, CONF_PARAM, "ProxySFTPServerMatch", FALSE); while (c != NULL) { int res; char *pattern; pr_regex_t *pre; pr_signals_handle(); pattern = c->argv[0]; pre = c->argv[1]; pr_trace_msg(trace_channel, 18, "checking server version '%s' against ProxySFTPServerMatch regex '%s'", version, pattern); res = pr_regexp_exec(pre, version, 0, NULL, 0, 0, 0); if (res == 0) { pr_table_t *tab; const void *v; /* We have a match. */ tab = c->argv[2]; /* Look for the following keys: * pessimisticNewkeys */ v = pr_table_get(tab, "pessimisticNewkeys", NULL); if (v != NULL) { int pessimistic_newkeys; pessimistic_newkeys = *((int *) v); pr_trace_msg(trace_channel, 16, "setting pessimistic NEWKEYS behavior to %s, as per " "ProxySFTPServerMatch", pessimistic_newkeys ? "true" : "false"); if (pessimistic_newkeys == TRUE) { default_flags |= PROXY_SSH_FEAT_PESSIMISTIC_NEWKEYS; } } /* Once we're done, we can destroy the table. */ (void) pr_table_empty(tab); (void) pr_table_free(tab); c->argv[2] = NULL; } else { pr_trace_msg(trace_channel, 18, "server version '%s' did not match ProxySFTPServerMatch regex '%s'", version, pattern); } c = find_config_next(c, c->next, CONF_PARAM, "ProxySFTPServerMatch", FALSE); } return 0; } int proxy_ssh_interop_supports_feature(int feat_flag) { if (!(default_flags & feat_flag)) { return FALSE; } return TRUE; } int proxy_ssh_interop_init(void) { register unsigned int i; /* Compile the regexps for all of the known server versions, to save the * time when connecting to a server. */ for (i = 0; known_versions[i].pattern != NULL; i++) { pr_regex_t *pre; int res; pr_signals_handle(); pre = pr_regexp_alloc(&proxy_module); res = pr_regexp_compile(pre, known_versions[i].pattern, REG_EXTENDED|REG_NOSUB); if (res != 0) { char errmsg[256]; memset(errmsg, '\0', sizeof(errmsg)); pr_regexp_error(res, pre, errmsg, sizeof(errmsg)); pr_regexp_free(NULL, pre); pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": error compiling regex pattern '%s' (known_versions[%u]): %s", known_versions[i].pattern, i, errmsg); continue; } known_versions[i].pre = pre; } return 0; } int proxy_ssh_interop_free(void) { register unsigned int i; for (i = 0; known_versions[i].pattern != NULL; i++) { if (known_versions[i].pre != NULL) { pr_regexp_free(NULL, known_versions[i].pre); known_versions[i].pre = NULL; } } return 0; } #endif /* PR_USE_OPENSSL */ proftpd-mod_proxy-0.9.5/lib/proxy/ssh/kex.c000066400000000000000000004672511475737016700207400ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH key exchange (kex) * Copyright (c) 2021-2023 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "proxy/conn.h" #include "proxy/ssh.h" #include "proxy/ssh/ssh2.h" #include "proxy/ssh/msg.h" #include "proxy/ssh/packet.h" #include "proxy/ssh/session.h" #include "proxy/ssh/cipher.h" #include "proxy/ssh/mac.h" #include "proxy/ssh/compress.h" #include "proxy/ssh/kex.h" #include "proxy/ssh/keys.h" #include "proxy/ssh/crypto.h" #include "proxy/ssh/disconnect.h" #include "proxy/ssh/interop.h" #include "proxy/ssh/misc.h" #if defined(PR_USE_OPENSSL) # include # include # include # include # include # if defined(PR_USE_OPENSSL_ECC) # include # include # endif /* PR_USE_OPENSSL_ECC */ #if defined(PR_USE_SODIUM) # include # define CURVE25519_SIZE 32 #endif /* PR_USE_SODIUM */ #if defined(HAVE_X448_OPENSSL) && defined(HAVE_SHA512_OPENSSL) # define CURVE448_SIZE 56 #endif /* HAVE_X448_OPENSSL and HAVE_SHA512_OPENSSL */ /* This needs to align/match with the SFTP_ROLE_CLIENT macro from mod_sftp.h, * for now. */ #define PROXY_SSH_ROLE_CLIENT 2 /* Define the min/preferred/max DH group lengths we request; see RFC 4419. */ #define PROXY_SSH_DH_MIN_LEN 2048 #define PROXY_SSH_DH_MAX_LEN 8192 extern pr_response_t *resp_list, *resp_err_list; /* For managing the kexinit process */ static pool *kex_pool = NULL; /* For hostkey verification. */ static struct proxy_ssh_datastore *kex_ds = NULL; static int kex_verify_hostkeys = FALSE; struct proxy_ssh_kex_names { const char *kex_algo; const char *server_hostkey_algo; const char *c2s_encrypt_algo; const char *s2c_encrypt_algo; const char *c2s_mac_algo; const char *s2c_mac_algo; const char *c2s_comp_algo; const char *s2c_comp_algo; const char *c2s_lang; const char *s2c_lang; }; struct proxy_ssh_kex { pool *pool; /* Versions */ const char *client_version; const char *server_version; /* KEXINIT lists from client */ struct proxy_ssh_kex_names *client_names; /* KEXINIT lists from server. */ struct proxy_ssh_kex_names *server_names; /* Session algorithms */ struct proxy_ssh_kex_names *session_names; /* For constructing the session ID/hash */ unsigned char *client_kexinit_payload; size_t client_kexinit_payload_len; unsigned char *server_kexinit_payload; size_t server_kexinit_payload_len; int first_kex_follows; /* Server-preferred hostkey type, based on algorithm: * * "ssh-dss" --> PROXY_SSH_KEY_DSA * "ssh-rsa" --> PROXY_SSH_KEY_RSA * "ecdsa-sha2-*" --> PROXY_SSH_KEY_ECDSA_* * "ssh-ed25519" --> PROXY_SSH_KEY_ED25519 * "ssh-ed448" --> PROXY_SSH_KEY_ED448 * "rsa-sha2-256" --> PROXY_SSH_KEY_RSA_SHA256 * "rsa-sha2-512" --> PROXY_SSH_KEY_RSA_SHA512 */ enum proxy_ssh_key_type_e use_hostkey_type; /* Using DH group-exchange? */ int use_gex; /* Using RSA key exchange? */ int use_kexrsa; /* Using ECDH? */ int use_ecdh; /* Using Curve25519? */ int use_curve25519; /* Using Curve448? */ int use_curve448; /* Using extension negotiations? */ int use_ext_info; /* For generating the session ID */ DH *dh; const BIGNUM *e; const EVP_MD *hash; const BIGNUM *k; const char *h; uint32_t hlen; uint32_t dh_gex_min; uint32_t dh_gex_pref; uint32_t dh_gex_max; RSA *rsa; unsigned char *rsa_encrypted; uint32_t rsa_encrypted_len; #if defined(PR_USE_OPENSSL_ECC) EC_KEY *ec; EC_POINT *server_point; #endif /* PR_USE_OPENSSL_ECC */ #if defined(PR_USE_SODIUM) && defined(HAVE_SHA256_OPENSSL) unsigned char *client_curve25519_priv_key; unsigned char *client_curve25519_pub_key; unsigned char *server_curve25519_pub_key; #endif /* PR_USE_SODIUM and HAVE_SHA256_OPENSSL */ #if defined(HAVE_X448_OPENSSL) && defined(HAVE_SHA512_OPENSSL) unsigned char *client_curve448_priv_key; unsigned char *client_curve448_pub_key; unsigned char *server_curve448_pub_key; #endif /* HAVE_X448_OPENSSL and HAVE_SHA512_OPENSSL */ }; static struct proxy_ssh_kex *kex_first_kex = NULL; static struct proxy_ssh_kex *kex_rekey_kex = NULL; static int kex_sent_kexinit = FALSE; /* Using strict kex? Note that we maintain this value here, rather than * in the proxy_ssh_kex struct, so that any "use strict KEX" flag set via the * first KEXINIT is used through any subsequent KEXINITs. */ static int use_strict_kex = FALSE; static int kex_done_first_kex = FALSE; /* Diffie-Hellman group moduli */ static const char *dh_group1_str = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74" "020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437" "4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF"; static const char *dh_group14_str = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74" "020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437" "4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05" "98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB" "9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718" "3995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF"; static const char *dh_group16_str = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" "83655D23DCA3AD961C62F356208552BB9ED529077096966D" "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199" "FFFFFFFFFFFFFFFF"; static const char *dh_group18_str = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" "83655D23DCA3AD961C62F356208552BB9ED529077096966D" "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BD" "F8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831" "179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B" "DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF" "5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6" "D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F3" "23A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE328" "06A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55C" "DA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE" "12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E4" "38777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300" "741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F568" "3423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9" "22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B" "4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A" "062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A36" "4597E899A0255DC164F31CC50846851DF9AB48195DED7EA1" "B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F92" "4009438B481C6CD7889A002ED5EE382BC9190DA6FC026E47" "9558E4475677E9AA9E3050E2765694DFC81F56E880B96E71" "60C980DD98EDD3DFFFFFFFFFFFFFFFFF"; #define PROXY_SSH_DH_GROUP1_SHA1 1 #define PROXY_SSH_DH_GROUP14_SHA1 2 #define PROXY_SSH_DH_GEX_SHA1 3 #define PROXY_SSH_DH_GEX_SHA256 4 #define PROXY_SSH_KEXRSA_SHA1 5 #define PROXY_SSH_KEXRSA_SHA256 6 #define PROXY_SSH_ECDH_SHA256 7 #define PROXY_SSH_ECDH_SHA384 8 #define PROXY_SSH_ECDH_SHA512 9 #define PROXY_SSH_DH_GROUP14_SHA256 10 #define PROXY_SSH_DH_GROUP16_SHA512 11 #define PROXY_SSH_DH_GROUP18_SHA512 12 #define PROXY_SSH_KEXRSA_SHA1_SIZE 2048 #define PROXY_SSH_KEXRSA_SHA256_SIZE 3072 static const char *kex_client_version = NULL; static const char *kex_server_version = NULL; static unsigned char kex_digest_buf[EVP_MAX_MD_SIZE]; /* Necessary prototypes. */ static struct proxy_ssh_packet *read_kex_packet(pool *, struct proxy_ssh_kex *, conn_t *, int, char *, unsigned int, ...); static const char *trace_channel = "proxy.ssh.kex"; static int digest_data(struct proxy_ssh_kex *kex, unsigned char *buf, uint32_t len, uint32_t *hlen) { #if OPENSSL_VERSION_NUMBER < 0x10100000L || \ defined(HAVE_LIBRESSL) EVP_MD_CTX ctx; #endif /* prior to OpenSSL-1.1.0 */ EVP_MD_CTX *pctx; #if OPENSSL_VERSION_NUMBER >= 0x10100000LL && \ !defined(HAVE_LIBRESSL) pctx = EVP_MD_CTX_new(); #else pctx = &ctx; #endif /* OpenSSL-1.1.0 and later */ /* In OpenSSL 0.9.6, many of the EVP_Digest* functions returned void, not * int. Without these ugly OpenSSL version preprocessor checks, the * compiler will error out with "void value not ignored as it ought to be". */ #if OPENSSL_VERSION_NUMBER >= 0x000907000L if (EVP_DigestInit(pctx, kex->hash) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error initializing message digest: %s", proxy_ssh_crypto_get_errors()); # if OPENSSL_VERSION_NUMBER >= 0x10100000LL && \ !defined(HAVE_LIBRESSL) EVP_MD_CTX_free(pctx); # endif /* OpenSSL-1.1.0 and later */ return -1; } #else EVP_DigestInit(pctx, kex->hash); #endif #if OPENSSL_VERSION_NUMBER >= 0x000907000L if (EVP_DigestUpdate(pctx, buf, len) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error updating message digest: %s", proxy_ssh_crypto_get_errors()); # if OPENSSL_VERSION_NUMBER >= 0x10100000LL && \ !defined(HAVE_LIBRESSL) EVP_MD_CTX_free(pctx); # endif /* OpenSSL-1.1.0 and later */ return -1; } #else EVP_DigestUpdate(pctx, buf, len); #endif #if OPENSSL_VERSION_NUMBER >= 0x000907000L if (EVP_DigestFinal(pctx, kex_digest_buf, hlen) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error finalizing message digest: %s", proxy_ssh_crypto_get_errors()); # if OPENSSL_VERSION_NUMBER >= 0x10100000LL && \ !defined(HAVE_LIBRESSL) EVP_MD_CTX_free(pctx); # endif /* OpenSSL-1.1.0 and later */ return -1; } #else EVP_DigestFinal(pctx, kex_digest_buf, hlen); #endif #if OPENSSL_VERSION_NUMBER >= 0x10100000LL && \ !defined(HAVE_LIBRESSL) EVP_MD_CTX_free(pctx); #endif /* OpenSSL-1.1.0 and later */ return 0; } static const unsigned char *calculate_h(pool *p, struct proxy_ssh_kex *kex, const unsigned char *hostkey_data, uint32_t hostkey_datalen, const BIGNUM *server_pub_key, const BIGNUM *k, uint32_t *hlen) { const BIGNUM *dh_pub_key; unsigned char *buf, *ptr; uint32_t buflen, bufsz, len = 0; bufsz = buflen = 8192; /* XXX Is this buffer large enough? Too large? */ ptr = buf = palloc(p, bufsz); /* Write all of the data into the buffer in the SSH2 format, and hash it. */ /* First, the version strings */ len += proxy_ssh_msg_write_string(&buf, &buflen, kex->client_version); len += proxy_ssh_msg_write_string(&buf, &buflen, kex->server_version); /* Client's KEXINIT */ len += proxy_ssh_msg_write_int(&buf, &buflen, kex->client_kexinit_payload_len + 1); len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEXINIT); len += proxy_ssh_msg_write_data(&buf, &buflen, kex->client_kexinit_payload, kex->client_kexinit_payload_len, FALSE); /* Server's KEXINIT */ len += proxy_ssh_msg_write_int(&buf, &buflen, kex->server_kexinit_payload_len + 1); len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEXINIT); len += proxy_ssh_msg_write_data(&buf, &buflen, kex->server_kexinit_payload, kex->server_kexinit_payload_len, FALSE); /* Server hostkey data */ len += proxy_ssh_msg_write_data(&buf, &buflen, hostkey_data, hostkey_datalen, TRUE); #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) DH_get0_key(kex->dh, &dh_pub_key, NULL); #else dh_pub_key = kex->dh->pub_key; #endif /* prior to OpenSSL-1.1.0 */ /* Client's key */ len += proxy_ssh_msg_write_mpint(&buf, &buflen, dh_pub_key); /* Server's key */ len += proxy_ssh_msg_write_mpint(&buf, &buflen, server_pub_key); /* Shared secret */ len += proxy_ssh_msg_write_mpint(&buf, &buflen, k); if (digest_data(kex, ptr, len, hlen) < 0) { pr_memscrub(ptr, bufsz); return NULL; } pr_memscrub(ptr, bufsz); return kex_digest_buf; } static int verify_h(pool *p, struct proxy_ssh_kex *kex, const unsigned char *key_data, uint32_t key_datalen, const unsigned char *sig_data, uint32_t sig_datalen, const unsigned char *h, uint32_t hlen) { int res, xerrno; const char *pubkey_algo = NULL; switch (kex->use_hostkey_type) { case PROXY_SSH_KEY_DSA: pubkey_algo = "ssh-dss"; break; case PROXY_SSH_KEY_RSA: pubkey_algo = "ssh-rsa"; break; #if defined(HAVE_SHA256_OPENSSL) case PROXY_SSH_KEY_RSA_SHA256: pubkey_algo = "rsa-sha2-256"; break; #endif /* HAVE_SHA256_OPENSSL */ #if defined(HAVE_SHA512_OPENSSL) case PROXY_SSH_KEY_RSA_SHA512: pubkey_algo = "rsa-sha2-512"; break; #endif /* HAVE_SHA512_OPENSSL */ #if defined(PR_USE_OPENSSL_ECC) case PROXY_SSH_KEY_ECDSA_256: pubkey_algo = "ecdsa-sha2-nistp256"; break; case PROXY_SSH_KEY_ECDSA_384: pubkey_algo = "ecdsa-sha2-nistp384"; break; case PROXY_SSH_KEY_ECDSA_521: pubkey_algo = "ecdsa-sha2-nistp521"; break; #endif /* PR_USE_OPENSSL_ECC */ #if defined(PR_USE_SODIUM) case PROXY_SSH_KEY_ED25519: pubkey_algo = "ssh-ed25519"; break; #endif /* PR_USE_SODIUM */ #if defined(HAVE_X448_OPENSSL) case PROXY_SSH_KEY_ED448: pubkey_algo = "ssh-ed448"; break; #endif /* HAVE_X448_OPENSSL */ default: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to verify signed data: Unknown public key algorithm"); errno = EINVAL; return -1; } res = proxy_ssh_keys_verify_signed_data(p, pubkey_algo, (unsigned char *) key_data, key_datalen, (unsigned char *) sig_data, sig_datalen, (unsigned char *) h, hlen); xerrno = errno; if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to verify server signature on H: %s", strerror(xerrno)); errno = xerrno; } return res; } static const unsigned char *calculate_gex_h(pool *p, struct proxy_ssh_kex *kex, const unsigned char *hostkey_data, uint32_t hostkey_datalen, const BIGNUM *server_pub_key, const BIGNUM *k, uint32_t *hlen) { const BIGNUM *dh_p, *dh_g, *dh_pub_key; unsigned char *buf, *ptr; uint32_t buflen, bufsz, len = 0; /* XXX Is this buffer large enough? Too large? */ bufsz = buflen = 8192; ptr = buf = palloc(p, bufsz); /* Write all of the data into the buffer in the SSH2 format, and hash it. * The ordering of these fields is described in RFC4419. */ /* First, the version strings */ len += proxy_ssh_msg_write_string(&buf, &buflen, kex->client_version); len += proxy_ssh_msg_write_string(&buf, &buflen, kex->server_version); /* Client's KEXINIT */ len += proxy_ssh_msg_write_int(&buf, &buflen, kex->client_kexinit_payload_len + 1); len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEXINIT); len += proxy_ssh_msg_write_data(&buf, &buflen, kex->client_kexinit_payload, kex->client_kexinit_payload_len, FALSE); /* Server's KEXINIT */ len += proxy_ssh_msg_write_int(&buf, &buflen, kex->server_kexinit_payload_len + 1); len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEXINIT); len += proxy_ssh_msg_write_data(&buf, &buflen, kex->server_kexinit_payload, kex->server_kexinit_payload_len, FALSE); /* Hostkey data */ len += proxy_ssh_msg_write_data(&buf, &buflen, hostkey_data, hostkey_datalen, TRUE); if (kex->dh_gex_min == 0 || kex->dh_gex_max == 0) { len += proxy_ssh_msg_write_int(&buf, &buflen, kex->dh_gex_pref); } else { len += proxy_ssh_msg_write_int(&buf, &buflen, kex->dh_gex_min); len += proxy_ssh_msg_write_int(&buf, &buflen, kex->dh_gex_pref); len += proxy_ssh_msg_write_int(&buf, &buflen, kex->dh_gex_max); } #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) DH_get0_pqg(kex->dh, &dh_p, NULL, &dh_g); #else dh_p = kex->dh->p; dh_g = kex->dh->g; #endif /* prior to OpenSSL-1.1.0 */ len += proxy_ssh_msg_write_mpint(&buf, &buflen, dh_p); len += proxy_ssh_msg_write_mpint(&buf, &buflen, dh_g); /* Client's key */ #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) DH_get0_key(kex->dh, &dh_pub_key, NULL); #else dh_pub_key = kex->dh->pub_key; #endif /* prior to OpenSSL-1.1.0 */ len += proxy_ssh_msg_write_mpint(&buf, &buflen, dh_pub_key); /* Server's key */ len += proxy_ssh_msg_write_mpint(&buf, &buflen, server_pub_key); /* Shared secret */ len += proxy_ssh_msg_write_mpint(&buf, &buflen, k); if (digest_data(kex, ptr, len, hlen) < 0) { pr_memscrub(ptr, bufsz); return NULL; } pr_memscrub(ptr, bufsz); return kex_digest_buf; } static const unsigned char *calculate_kexrsa_h(pool *p, struct proxy_ssh_kex *kex, const unsigned char *hostkey_data, uint32_t hostkey_datalen, const BIGNUM *k, uint32_t *hlen) { const BIGNUM *rsa_e = NULL, *rsa_n = NULL; unsigned char *buf, *ptr, *rsa_key, *rsa_data; uint32_t buflen, bufsz, len = 0, rsa_datalen, rsa_keysz, rsa_keylen = 0; /* XXX Is this buffer large enough? Too large? */ rsa_keysz = rsa_datalen = 4096; rsa_key = rsa_data = palloc(p, rsa_keysz); /* Write the transient RSA public key into its own buffer, to then be * written in its entirety as an SSH2 string. */ rsa_keylen += proxy_ssh_msg_write_string(&rsa_data, &rsa_datalen, "ssh-rsa"); #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) RSA_get0_key(kex->rsa, &rsa_n, &rsa_e, NULL); #else rsa_e = kex->rsa->e; rsa_n = kex->rsa->n; #endif /* prior to OpenSSL-1.1.0 */ rsa_keylen += proxy_ssh_msg_write_mpint(&rsa_data, &rsa_datalen, rsa_e); rsa_keylen += proxy_ssh_msg_write_mpint(&rsa_data, &rsa_datalen, rsa_n); bufsz = buflen = 4096; /* XXX Is this buffer large enough? Too large? */ ptr = buf = palloc(p, bufsz); /* Write all of the data into the buffer in the SSH2 format, and hash it. */ /* First, the version strings */ len += proxy_ssh_msg_write_string(&buf, &buflen, kex->client_version); len += proxy_ssh_msg_write_string(&buf, &buflen, kex->server_version); /* Client's KEXINIT */ len += proxy_ssh_msg_write_int(&buf, &buflen, kex->client_kexinit_payload_len + 1); len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEXINIT); len += proxy_ssh_msg_write_data(&buf, &buflen, kex->client_kexinit_payload, kex->client_kexinit_payload_len, FALSE); /* Server's KEXINIT */ len += proxy_ssh_msg_write_int(&buf, &buflen, kex->server_kexinit_payload_len + 1); len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEXINIT); len += proxy_ssh_msg_write_data(&buf, &buflen, kex->server_kexinit_payload, kex->server_kexinit_payload_len, FALSE); /* Hostkey data */ len += proxy_ssh_msg_write_data(&buf, &buflen, hostkey_data, hostkey_datalen, TRUE); /* Transient RSA public key */ len += proxy_ssh_msg_write_data(&buf, &buflen, rsa_key, rsa_keylen, TRUE); /* RSA-encrypted secret */ len += proxy_ssh_msg_write_data(&buf, &buflen, kex->rsa_encrypted, kex->rsa_encrypted_len, TRUE); /* Shared secret. */ len += proxy_ssh_msg_write_mpint(&buf, &buflen, k); if (digest_data(kex, ptr, len, hlen) < 0) { pr_memscrub(ptr, bufsz); return NULL; } pr_memscrub(rsa_key, rsa_keysz); pr_memscrub(ptr, bufsz); return kex_digest_buf; } #if defined(PR_USE_OPENSSL_ECC) static const unsigned char *calculate_ecdh_h(pool *p, struct proxy_ssh_kex *kex, const unsigned char *hostkey_data, uint32_t hostkey_datalen, const BIGNUM *k, uint32_t *hlen) { unsigned char *buf, *ptr; uint32_t buflen, bufsz, len = 0; bufsz = buflen = 4096; /* XXX Is this buffer large enough? Too large? */ ptr = buf = palloc(p, bufsz); /* Write all of the data into the buffer in the SSH2 format, and hash it. * The ordering of these fields is described in RFC5656. */ /* First, the version strings */ len += proxy_ssh_msg_write_string(&buf, &buflen, kex->client_version); len += proxy_ssh_msg_write_string(&buf, &buflen, kex->server_version); /* Client's KEXINIT */ len += proxy_ssh_msg_write_int(&buf, &buflen, kex->client_kexinit_payload_len + 1); len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEXINIT); len += proxy_ssh_msg_write_data(&buf, &buflen, kex->client_kexinit_payload, kex->client_kexinit_payload_len, FALSE); /* Server's KEXINIT */ len += proxy_ssh_msg_write_int(&buf, &buflen, kex->server_kexinit_payload_len + 1); len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEXINIT); len += proxy_ssh_msg_write_data(&buf, &buflen, kex->server_kexinit_payload, kex->server_kexinit_payload_len, FALSE); /* Hostkey data */ len += proxy_ssh_msg_write_data(&buf, &buflen, hostkey_data, hostkey_datalen, TRUE); /* Client public key */ len += proxy_ssh_msg_write_ecpoint(&buf, &buflen, EC_KEY_get0_group(kex->ec), EC_KEY_get0_public_key(kex->ec)); /* Server public key */ len += proxy_ssh_msg_write_ecpoint(&buf, &buflen, EC_KEY_get0_group(kex->ec), kex->server_point); /* Shared secret */ len += proxy_ssh_msg_write_mpint(&buf, &buflen, k); if (digest_data(kex, ptr, len, hlen) < 0) { pr_memscrub(ptr, bufsz); return NULL; } pr_memscrub(ptr, bufsz); return kex_digest_buf; } #endif /* PR_USE_OPENSSL_ECC */ /* Make sure that the DH key we're generating is good enough. */ static int have_good_dh(DH *dh, const BIGNUM *pub_key) { register int i; unsigned int nbits = 0; const BIGNUM *dh_p = NULL; BIGNUM *tmp; if (dh == NULL || pub_key == NULL) { errno = EINVAL; return -1; } #if OPENSSL_VERSION_NUMBER >= 0x0090801fL if (BN_is_negative(pub_key)) { pr_trace_msg(trace_channel, 10, "DH public keys cannot have negative numbers"); errno = EINVAL; return -1; } #endif /* OpenSSL-0.9.8a or later */ if (BN_cmp(pub_key, BN_value_one()) != 1) { pr_trace_msg(trace_channel, 10, "bad DH public key exponent (<= 1)"); errno = EINVAL; return -1; } #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) DH_get0_pqg(dh, &dh_p, NULL, NULL); #else dh_p = dh->p; #endif /* prior to OpenSSL-1.1.0 */ tmp = BN_new(); if (!BN_sub(tmp, dh_p, BN_value_one()) || BN_cmp(pub_key, tmp) != -1) { BN_clear_free(tmp); pr_trace_msg(trace_channel, 10, "bad DH public key (>= p-1)"); errno = EINVAL; return -1; } BN_clear_free(tmp); for (i = 0; i <= BN_num_bits(pub_key); i++) { if (BN_is_bit_set(pub_key, i)) { nbits++; } } /* The number of bits set in the public key must be greater than one. * Otherwise, the public key will not hold up under scrutiny, not for * our needs. (The OpenSSH client is picky about the DH public keys it * will accept as well, so this is necessary to pass OpenSSH's requirements). */ if (nbits <= 1) { errno = EINVAL; return -1; } pr_trace_msg(trace_channel, 10, "good DH public key: %u bits set", nbits); return 0; } static int get_dh_nbits(struct proxy_ssh_kex *kex) { int dh_nbits = 0, dh_size = 0; const char *algo; const EVP_CIPHER *cipher; const EVP_MD *digest; algo = kex->session_names->c2s_encrypt_algo; cipher = proxy_ssh_crypto_get_cipher(algo, NULL, NULL, NULL); if (cipher != NULL) { int block_size, key_len; key_len = EVP_CIPHER_key_length(cipher); if (strcmp(algo, "none") == 0 && key_len < 32) { key_len = 32; } if (dh_size < key_len) { dh_size = key_len; pr_trace_msg(trace_channel, 19, "set DH size to %d bytes, matching client-to-server '%s' cipher " "key length", dh_size, algo); } block_size = EVP_CIPHER_block_size(cipher); if (dh_size < block_size) { dh_size = block_size; pr_trace_msg(trace_channel, 19, "set DH size to %d bytes, matching client-to-server '%s' cipher " "block size", dh_size, algo); } } algo = kex->session_names->s2c_encrypt_algo; cipher = proxy_ssh_crypto_get_cipher(algo, NULL, NULL, NULL); if (cipher != NULL) { int block_size, key_len; key_len = EVP_CIPHER_key_length(cipher); if (strcmp(algo, "none") == 0 && key_len < 32) { key_len = 32; } if (dh_size < key_len) { dh_size = key_len; pr_trace_msg(trace_channel, 19, "set DH size to %d bytes, matching server-to-client '%s' cipher " "key length", dh_size, algo); } block_size = EVP_CIPHER_block_size(cipher); if (dh_size < block_size) { dh_size = block_size; pr_trace_msg(trace_channel, 19, "set DH size to %d bytes, matching server-to-client '%s' cipher " "block size", dh_size, algo); } } algo = kex->session_names->c2s_mac_algo; digest = proxy_ssh_crypto_get_digest(algo, NULL); if (digest != NULL) { int mac_len; mac_len = EVP_MD_size(digest); if (dh_size < mac_len) { dh_size = mac_len; pr_trace_msg(trace_channel, 19, "set DH size to %d bytes, matching client-to-server '%s' digest size", dh_size, algo); } } algo = kex->session_names->s2c_mac_algo; digest = proxy_ssh_crypto_get_digest(algo, NULL); if (digest != NULL) { int mac_len; mac_len = EVP_MD_size(digest); if (dh_size < mac_len) { dh_size = mac_len; pr_trace_msg(trace_channel, 19, "set DH size to %d bytes, matching server-to-client '%s' digest size", dh_size, algo); } } /* We want to return bits, not bytes. */ dh_nbits = dh_size * 8; pr_trace_msg(trace_channel, 8, "requesting DH size of %d bits", dh_nbits); return dh_nbits; } static int create_dh(struct proxy_ssh_kex *kex, int type) { unsigned int attempts = 0; int dh_nbits; DH *dh; if (type != PROXY_SSH_DH_GROUP1_SHA1 && type != PROXY_SSH_DH_GROUP14_SHA1 && type != PROXY_SSH_DH_GROUP14_SHA256 && type != PROXY_SSH_DH_GROUP16_SHA512 && type != PROXY_SSH_DH_GROUP18_SHA512) { errno = EINVAL; return -1; } if (kex->dh != NULL) { #if OPENSSL_VERSION_NUMBER < 0x10100000L if (kex->dh->p != NULL) { BN_clear_free(kex->dh->p); kex->dh->p = NULL; } if (kex->dh->g != NULL) { BN_clear_free(kex->dh->g); kex->dh->g = NULL; } if (kex->dh->priv_key != NULL) { BN_clear_free(kex->dh->priv_key); kex->dh->priv_key = NULL; } if (kex->dh->pub_key != NULL) { BN_clear_free(kex->dh->pub_key); kex->dh->pub_key = NULL; } #endif /* prior to OpenSSL-1.1.0 */ DH_free(kex->dh); kex->dh = NULL; } dh_nbits = get_dh_nbits(kex); /* We have 10 attempts to make a DH key which passes muster. */ while (attempts <= 10) { const BIGNUM *dh_p, *dh_g, *dh_pub_key = NULL, *dh_priv_key = NULL; pr_signals_handle(); attempts++; pr_trace_msg(trace_channel, 9, "attempt #%u to create a good DH key", attempts); dh = DH_new(); if (dh == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error creating DH: %s", proxy_ssh_crypto_get_errors()); return -1; } dh_p = BN_new(); switch (type) { case PROXY_SSH_DH_GROUP18_SHA512: if (BN_hex2bn((BIGNUM **) &dh_p, dh_group18_str) == 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error setting DH (group18) P: %s", proxy_ssh_crypto_get_errors()); BN_clear_free((BIGNUM *) dh_p); DH_free(dh); return -1; } break; case PROXY_SSH_DH_GROUP16_SHA512: if (BN_hex2bn((BIGNUM **) &dh_p, dh_group16_str) == 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error setting DH (group16) P: %s", proxy_ssh_crypto_get_errors()); BN_clear_free((BIGNUM *) dh_p); DH_free(dh); return -1; } break; case PROXY_SSH_DH_GROUP14_SHA1: case PROXY_SSH_DH_GROUP14_SHA256: if (BN_hex2bn((BIGNUM **) &dh_p, dh_group14_str) == 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error setting DH (group14) P: %s", proxy_ssh_crypto_get_errors()); BN_clear_free((BIGNUM *) dh_p); DH_free(dh); return -1; } break; default: if (BN_hex2bn((BIGNUM **) &dh_p, dh_group1_str) == 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error setting DH (group1) P: %s", proxy_ssh_crypto_get_errors()); BN_clear_free((BIGNUM *) dh_p); DH_free(dh); return -1; } break; } dh_g = BN_new(); if (BN_hex2bn((BIGNUM **) &dh_g, "2") == 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error setting DH G: %s", proxy_ssh_crypto_get_errors()); BN_clear_free((BIGNUM *) dh_p); BN_clear_free((BIGNUM *) dh_g); DH_free(dh); return -1; } #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) DH_set0_pqg(dh, (BIGNUM *) dh_p, NULL, (BIGNUM *) dh_g); #else dh->p = dh_p; dh->g = dh_g; #endif /* prior to OpenSSL-1.1.0 */ dh_priv_key = BN_new(); /* Generate a random private exponent of the desired size, in bits. */ if (!BN_rand((BIGNUM *) dh_priv_key, dh_nbits, 0, 0)) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error generating DH random key (%d bits): %s", dh_nbits, proxy_ssh_crypto_get_errors()); BN_clear_free((BIGNUM *) dh_priv_key); DH_free(dh); return -1; } dh_pub_key = BN_new(); #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) DH_set0_key(dh, (BIGNUM *) dh_pub_key, (BIGNUM *) dh_priv_key); #else dh->pub_key = dh_pub_key; dh->priv_key = dh_priv_key; #endif /* prior to OpenSSL-1.1.0 */ pr_trace_msg(trace_channel, 12, "generating DH key"); if (DH_generate_key(dh) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error generating DH key: %s", proxy_ssh_crypto_get_errors()); DH_free(dh); return -1; } #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) DH_get0_key(dh, &dh_pub_key, NULL); #else dh_pub_key = dh->pub_key; #endif /* prior to OpenSSL-1.1.0 */ if (have_good_dh(dh, dh_pub_key) < 0) { DH_free(dh); continue; } kex->dh = dh; switch (type) { #if defined(HAVE_SHA512_OPENSSL) case PROXY_SSH_DH_GROUP16_SHA512: case PROXY_SSH_DH_GROUP18_SHA512: kex->hash = EVP_sha512(); break; #endif /* HAVE_SHA512_OPENSSL */ #if defined(HAVE_SHA256_OPENSSL) case PROXY_SSH_DH_GROUP14_SHA256: kex->hash = EVP_sha256(); break; #endif /* HAVE_SHA256_OPENSSL */ default: kex->hash = EVP_sha1(); } return 0; } errno = EPERM; return -1; } static int prepare_dh(struct proxy_ssh_kex *kex, int type) { DH *dh; if (type != PROXY_SSH_DH_GEX_SHA1 && type != PROXY_SSH_DH_GEX_SHA256) { errno = EINVAL; return -1; } if (kex->dh != NULL) { #if OPENSSL_VERSION_NUMBER < 0x10100000L if (kex->dh->p != NULL) { BN_clear_free(kex->dh->p); kex->dh->p = NULL; } if (kex->dh->g != NULL) { BN_clear_free(kex->dh->g); kex->dh->g = NULL; } if (kex->dh->priv_key != NULL) { BN_clear_free(kex->dh->priv_key); kex->dh->priv_key = NULL; } if (kex->dh->pub_key != NULL) { BN_clear_free(kex->dh->pub_key); kex->dh->pub_key = NULL; } #endif /* prior to OpenSSL-1.1.0 */ DH_free(kex->dh); kex->dh = NULL; } dh = DH_new(); if (dh == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error creating DH: %s", proxy_ssh_crypto_get_errors()); return -1; } kex->dh = dh; if (type == PROXY_SSH_DH_GEX_SHA1) { kex->hash = EVP_sha1(); #if ((OPENSSL_VERSION_NUMBER > 0x000907000L && defined(OPENSSL_FIPS)) || \ (OPENSSL_VERSION_NUMBER > 0x000908000L)) && \ defined(HAVE_SHA256_OPENSSL) } else if (type == PROXY_SSH_DH_GEX_SHA256) { kex->hash = EVP_sha256(); #endif } return 0; } static int create_kexrsa(struct proxy_ssh_kex *kex, int type) { if (type != PROXY_SSH_KEXRSA_SHA1 && type != PROXY_SSH_KEXRSA_SHA256) { errno = EINVAL; return -1; } if (kex->rsa != NULL) { RSA_free(kex->rsa); kex->rsa = NULL; } if (kex->rsa_encrypted != NULL) { pr_memscrub(kex->rsa_encrypted, kex->rsa_encrypted_len); kex->rsa_encrypted = NULL; kex->rsa_encrypted_len = 0; } if (type == PROXY_SSH_KEXRSA_SHA1) { kex->hash = EVP_sha1(); #if ((OPENSSL_VERSION_NUMBER > 0x000907000L && defined(OPENSSL_FIPS)) || \ (OPENSSL_VERSION_NUMBER > 0x000908000L)) && \ defined(HAVE_SHA256_OPENSSL) } else if (type == PROXY_SSH_KEXRSA_SHA256) { kex->hash = EVP_sha256(); #endif } return 0; } #if defined(PR_USE_OPENSSL_ECC) static int create_ecdh(struct proxy_ssh_kex *kex, int type) { EC_KEY *ec; int curve_nid = -1; char *curve_name = NULL; switch (type) { case PROXY_SSH_ECDH_SHA256: curve_name = "NID_X9_62_prime256v1"; # if defined(HAVE_SHA256_OPENSSL) curve_nid = NID_X9_62_prime256v1; kex->hash = EVP_sha256(); # else (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to generate EC key using '%s': OpenSSL lacks SHA256 support", curve_name); errno = ENOSYS; return -1; # endif /* HAVE_SHA256_OPENSSL */ break; case PROXY_SSH_ECDH_SHA384: curve_name = "NID_secp384r1"; # if defined(HAVE_SHA256_OPENSSL) curve_nid = NID_secp384r1; kex->hash = EVP_sha384(); # else (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to generate EC key using '%s': OpenSSL lacks SHA256 support", curve_name); errno = ENOSYS; return -1; # endif /* HAVE_SHA256_OPENSSL */ break; case PROXY_SSH_ECDH_SHA512: curve_name = "NID_secp521r1"; # if defined(HAVE_SHA512_OPENSSL) curve_nid = NID_secp521r1; kex->hash = EVP_sha512(); # else (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to generate EC key using '%s': OpenSSL lacks SHA512 support", curve_name); errno = ENOSYS; return -1; # endif /* HAVE_SHA512_OPENSSL */ break; default: errno = EINVAL; return -1; } ec = EC_KEY_new_by_curve_name(curve_nid); if (ec == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error generating new EC key using '%s': %s", curve_name, proxy_ssh_crypto_get_errors()); return -1; } if (EC_KEY_generate_key(ec) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error generating new EC key: %s", proxy_ssh_crypto_get_errors()); EC_KEY_free(ec); return -1; } kex->ec = ec; return 0; } #endif /* PR_USE_OPENSSL_ECC */ #if defined(PR_USE_SODIUM) && defined(HAVE_SHA256_OPENSSL) static int generate_curve25519_keys(unsigned char *priv_key, unsigned char *pub_key) { static const unsigned char basepoint[CURVE25519_SIZE] = {9}; unsigned char zero_curve25519[CURVE25519_SIZE]; int res; randombytes_buf(priv_key, CURVE25519_SIZE); res = crypto_scalarmult_curve25519(pub_key, priv_key, basepoint); if (res < 0) { pr_trace_msg(trace_channel, 3, "error performing Curve25519 scalar multiplication"); errno = EINVAL; return -1; } /* Check for all-zero public keys. */ sodium_memzero(zero_curve25519, CURVE25519_SIZE); if (sodium_memcmp(pub_key, zero_curve25519, CURVE25519_SIZE) == 0) { pr_trace_msg(trace_channel, 12, "generated all-zero Curve25519 public key, trying again"); return generate_curve25519_keys(priv_key, pub_key); } return 0; } static int get_curve25519_shared_key(unsigned char *shared_key, unsigned char *pub_key, unsigned char *priv_key) { int res; res = crypto_scalarmult_curve25519(shared_key, priv_key, pub_key); if (res < 0) { pr_trace_msg(trace_channel, 3, "error performing Curve25519 scalar multiplication"); errno = EINVAL; return -1; } return CURVE25519_SIZE; } static const unsigned char *calculate_curve25519_h(pool *p, struct proxy_ssh_kex *kex, const unsigned char *hostkey_data, uint32_t hostkey_datalen, const BIGNUM *k, uint32_t *hlen) { unsigned char *buf, *ptr; uint32_t buflen, bufsz, len = 0; bufsz = buflen = 4096; /* XXX Is this buffer large enough? Too large? */ ptr = buf = palloc(p, bufsz); /* Write all of the data into the buffer in the SSH2 format, and hash it. * The ordering of these fields is described in RFC5656. */ /* First, the version strings */ len += proxy_ssh_msg_write_string(&buf, &buflen, kex->client_version); len += proxy_ssh_msg_write_string(&buf, &buflen, kex->server_version); /* Client's KEXINIT */ len += proxy_ssh_msg_write_int(&buf, &buflen, kex->client_kexinit_payload_len + 1); len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEXINIT); len += proxy_ssh_msg_write_data(&buf, &buflen, kex->client_kexinit_payload, kex->client_kexinit_payload_len, FALSE); /* Server's KEXINIT */ len += proxy_ssh_msg_write_int(&buf, &buflen, kex->server_kexinit_payload_len + 1); len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEXINIT); len += proxy_ssh_msg_write_data(&buf, &buflen, kex->server_kexinit_payload, kex->server_kexinit_payload_len, FALSE); /* Hostkey data */ len += proxy_ssh_msg_write_data(&buf, &buflen, hostkey_data, hostkey_datalen, TRUE); /* Client's key */ len += proxy_ssh_msg_write_data(&buf, &buflen, kex->client_curve25519_pub_key, CURVE25519_SIZE, TRUE); /* Server's key */ len += proxy_ssh_msg_write_data(&buf, &buflen, kex->server_curve25519_pub_key, CURVE25519_SIZE, TRUE); /* Shared secret */ len += proxy_ssh_msg_write_mpint(&buf, &buflen, k); if (digest_data(kex, ptr, len, hlen) < 0) { pr_memscrub(ptr, bufsz); return NULL; } pr_memscrub(ptr, bufsz); return kex_digest_buf; } static int create_curve25519(struct proxy_ssh_kex *kex) { kex->client_curve25519_priv_key = palloc(kex_pool, CURVE25519_SIZE); kex->client_curve25519_pub_key = palloc(kex_pool, CURVE25519_SIZE); return generate_curve25519_keys(kex->client_curve25519_priv_key, kex->client_curve25519_pub_key); } #endif /* PR_USE_SODIUM and HAVE_SHA256_OPENSSL */ #if defined(HAVE_X448_OPENSSL) && defined(HAVE_SHA512_OPENSSL) static int generate_curve448_keys(unsigned char *priv_key, unsigned char *pub_key) { EVP_PKEY_CTX *pctx = NULL; EVP_PKEY *pkey = NULL; size_t key_len = 0; pctx = EVP_PKEY_CTX_new_id(NID_X448, NULL); if (pctx == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error initializing context for Curve448 key: %s", proxy_ssh_crypto_get_errors()); return -1; } if (EVP_PKEY_keygen_init(pctx) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error preparing to generate Curve448 key: %s", proxy_ssh_crypto_get_errors()); EVP_PKEY_CTX_free(pctx); return -1; } if (EVP_PKEY_keygen(pctx, &pkey) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error generating Curve448 shared key: %s", proxy_ssh_crypto_get_errors()); EVP_PKEY_CTX_free(pctx); return -1; } key_len = CURVE448_SIZE; if (EVP_PKEY_get_raw_private_key(pkey, priv_key, &key_len) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error obtaining Curve448 private key: %s", proxy_ssh_crypto_get_errors()); EVP_PKEY_CTX_free(pctx); EVP_PKEY_free(pkey); return -1; } key_len = CURVE448_SIZE; if (EVP_PKEY_get_raw_public_key(pkey, pub_key, &key_len) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error obtaining Curve448 public key: %s", proxy_ssh_crypto_get_errors()); EVP_PKEY_CTX_free(pctx); EVP_PKEY_free(pkey); return -1; } EVP_PKEY_CTX_free(pctx); EVP_PKEY_free(pkey); return 0; } static int get_curve448_shared_key(unsigned char *shared_key, unsigned char *pub_key, unsigned char *priv_key) { EVP_PKEY_CTX *pctx = NULL; EVP_PKEY *client_pkey = NULL, *server_pkey = NULL; size_t shared_keylen = 0; server_pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_X448, NULL, priv_key, CURVE448_SIZE); if (server_pkey == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error initializing Curve448 server key: %s", proxy_ssh_crypto_get_errors()); return -1; } client_pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_X448, NULL, pub_key, CURVE448_SIZE); if (client_pkey == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error initializing Curve448 client key: %s", proxy_ssh_crypto_get_errors()); EVP_PKEY_free(server_pkey); return -1; } pctx = EVP_PKEY_CTX_new(server_pkey, NULL); if (pctx == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error initializing context for Curve448 shared key: %s", proxy_ssh_crypto_get_errors()); EVP_PKEY_free(server_pkey); EVP_PKEY_free(client_pkey); return -1; } if (EVP_PKEY_derive_init(pctx) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error preparing for Curve448 shared key: %s", proxy_ssh_crypto_get_errors()); EVP_PKEY_CTX_free(pctx); EVP_PKEY_free(server_pkey); EVP_PKEY_free(client_pkey); return -1; } if (EVP_PKEY_derive_set_peer(pctx, client_pkey) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error setting peer for Curve448 shared key: %s", proxy_ssh_crypto_get_errors()); EVP_PKEY_CTX_free(pctx); EVP_PKEY_free(server_pkey); EVP_PKEY_free(client_pkey); return -1; } shared_keylen = CURVE448_SIZE; if (EVP_PKEY_derive(pctx, shared_key, &shared_keylen) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error generating Curve448 shared key: %s", proxy_ssh_crypto_get_errors()); EVP_PKEY_CTX_free(pctx); EVP_PKEY_free(server_pkey); EVP_PKEY_free(client_pkey); return -1; } if (shared_keylen != CURVE448_SIZE) { pr_trace_msg(trace_channel, 1, "generated Curve448 shared key length (%lu bytes) is not as expected " "(%lu bytes)", (unsigned long) shared_keylen, (unsigned long) CURVE448_SIZE); } EVP_PKEY_CTX_free(pctx); EVP_PKEY_free(server_pkey); EVP_PKEY_free(client_pkey); return CURVE448_SIZE; } static const unsigned char *calculate_curve448_h(pool *p, struct proxy_ssh_kex *kex, const unsigned char *hostkey_data, uint32_t hostkey_datalen, const BIGNUM *k, uint32_t *hlen) { unsigned char *buf, *ptr; uint32_t buflen, bufsz, len = 0; bufsz = buflen = 4096; /* XXX Is this buffer large enough? Too large? */ ptr = buf = palloc(p, bufsz); /* Write all of the data into the buffer in the SSH2 format, and hash it. * The ordering of these fields is described in RFC5656. */ /* First, the version strings */ len += proxy_ssh_msg_write_string(&buf, &buflen, kex->client_version); len += proxy_ssh_msg_write_string(&buf, &buflen, kex->server_version); /* Client's KEXINIT */ len += proxy_ssh_msg_write_int(&buf, &buflen, kex->client_kexinit_payload_len + 1); len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEXINIT); len += proxy_ssh_msg_write_data(&buf, &buflen, kex->client_kexinit_payload, kex->client_kexinit_payload_len, FALSE); /* Server's KEXINIT */ len += proxy_ssh_msg_write_int(&buf, &buflen, kex->server_kexinit_payload_len + 1); len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEXINIT); len += proxy_ssh_msg_write_data(&buf, &buflen, kex->server_kexinit_payload, kex->server_kexinit_payload_len, FALSE); /* Hostkey data */ len += proxy_ssh_msg_write_data(&buf, &buflen, hostkey_data, hostkey_datalen, TRUE); /* Client's key */ len += proxy_ssh_msg_write_data(&buf, &buflen, kex->client_curve448_pub_key, CURVE448_SIZE, TRUE); /* Server's key */ len += proxy_ssh_msg_write_data(&buf, &buflen, kex->server_curve448_pub_key, CURVE448_SIZE, TRUE); /* Shared secret */ len += proxy_ssh_msg_write_mpint(&buf, &buflen, k); if (digest_data(kex, ptr, len, hlen) < 0) { pr_memscrub(ptr, bufsz); return NULL; } pr_memscrub(ptr, bufsz); return kex_digest_buf; } static int create_curve448(struct proxy_ssh_kex *kex) { kex->client_curve448_priv_key = palloc(kex_pool, CURVE448_SIZE); kex->client_curve448_pub_key = palloc(kex_pool, CURVE448_SIZE); return generate_curve448_keys(kex->client_curve448_priv_key, kex->client_curve448_pub_key); } #endif /* HAVE_X448_OPENSSL and HAVE_SHA512_OPENSSL */ /* Given a name-list, return the first (i.e. preferred) name in the list. */ static const char *get_preferred_name(pool *p, const char *names) { register unsigned int i; /* Advance to the first comma, or NUL. */ for (i = 0; names[i] && names[i] != ','; i++); if (names[i] == ',' || names[i] == '\0') { char *pref; pref = pcalloc(p, i + 1); memcpy(pref, names, i); return pref; } /* This should never happen. */ (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to find preferred name in '%s'", names); return NULL; } /* Note that in this default list of key exchange algorithms, one of the * REQUIRED algorithms is conspicuously absent: * * diffie-hellman-group1-sha1 * * This exchange has a weak hardcoded DH group, and will thus only be used * if explicitly requested via ProxySFTPKeyExchanges, or if the AllowWeakDH * SFTPOption is used. */ static const char *kex_exchanges[] = { #if defined(HAVE_X448_OPENSSL) && defined(HAVE_SHA512_OPENSSL) "curve448-sha512", #endif /* HAVE_X448_OPENSSL and HAVE_SHA512_OPENSSL */ #if defined(PR_USE_SODIUM) && defined(HAVE_SHA256_OPENSSL) "curve25519-sha256", "curve25519-sha256@libssh.org", #endif /* PR_USE_SODIUM and HAVE_SHA256_OPENSSL */ #if defined(PR_USE_OPENSSL_ECC) "ecdh-sha2-nistp521", "ecdh-sha2-nistp384", "ecdh-sha2-nistp256", #endif /* PR_USE_OPENSSL_ECC */ #if (OPENSSL_VERSION_NUMBER > 0x000907000L && defined(OPENSSL_FIPS)) || \ (OPENSSL_VERSION_NUMBER > 0x000908000L) # if defined(HAVE_SHA512_OPENSSL) "diffie-hellman-group18-sha512", "diffie-hellman-group16-sha512", # endif /* HAVE_SHA512_OPENSSL */ # if defined(HAVE_SHA256_OPENSSL) "diffie-hellman-group14-sha256", "diffie-hellman-group-exchange-sha256", # endif /* HAVE_SHA256_OPENSSL */ #endif "diffie-hellman-group-exchange-sha1", "diffie-hellman-group14-sha1", #if 0 /* We cannot currently support rsa2048-sha256, since it requires support * for PKCS#1 v2.1 (RFC3447). OpenSSL only supports PKCS#1 v2.0 (RFC2437) * at present, which only allows EME-OAEP using SHA1. v2.1 allows for * using other message digests, e.g. SHA256, for EME-OAEP. */ #if ((OPENSSL_VERSION_NUMBER > 0x000907000L && defined(OPENSSL_FIPS)) || \ (OPENSSL_VERSION_NUMBER > 0x000908000L)) && \ defined(HAVE_SHA256_OPENSSL) "rsa2048-sha256", #endif #endif "rsa1024-sha1", NULL, }; static const char *get_kexinit_exchange_list(pool *p) { char *res = ""; config_rec *c; c = find_config(main_server->conf, CONF_PARAM, "ProxySFTPKeyExchanges", FALSE); if (c != NULL) { res = pstrdup(p, c->argv[0]); } else { register unsigned int i; for (i = 0; kex_exchanges[i]; i++) { res = pstrcat(p, res, *res ? "," : "", pstrdup(p, kex_exchanges[i]), NULL); } if (proxy_opts & PROXY_OPT_SSH_ALLOW_WEAK_DH) { /* The hardcoded group for this exchange is rather weak in the face of * the "Logjam" vulnerability (see https://weakdh.org). Thus it is * only appended to the end of the default exchanges if the AllowWeakDH * SFTPOption is in effect. */ res = pstrcat(p, res, ",", pstrdup(p, "diffie-hellman-group1-sha1"), NULL); } } if (!(proxy_opts & PROXY_OPT_SSH_NO_EXT_INFO)) { /* Indicate support for RFC 8308's extension negotiation mechanism. */ res = pstrcat(p, res, *res ? "," : "", pstrdup(p, "ext-info-c"), NULL); } if (!(proxy_opts & PROXY_OPT_SSH_NO_STRICT_KEX)) { /* Indicate support for OpenSSH's custom "strict KEX" mode extension, * but only if we have not done/completed our first KEX. */ if (kex_done_first_kex == FALSE) { res = pstrcat(p, res, *res ? "," : "", pstrdup(p, "kex-strict-c-v00@openssh.com"), NULL); } } return res; } static const char *get_kexinit_hostkey_algo_list(pool *p) { char *list = ""; /* Our list of supported hostkey algorithms depends on the hostkeys * that have been configured. Show a preference for RSA over DSA, * and ECDSA over both RSA and DSA, and ED25519/ED448 over all. * * XXX Should this be configurable later? */ #if defined(HAVE_X448_OPENSSL) && defined(HAVE_SHA512_OPENSSL) list = pstrcat(p, list, *list ? "," : "", "ssh-ed448", NULL); #endif /* HAVE_X448_OPENSSL and HAVE_SHA512_OPENSSL */ #if defined(PR_USE_SODIUM) list = pstrcat(p, list, *list ? "," : "", "ssh-ed25519", NULL); #endif /* PR_USE_SODIUM */ #if defined(PR_USE_OPENSSL_ECC) list = pstrcat(p, list, *list ? "," : "", "ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521", NULL); #endif /* PR_USE_OPENSSL_ECC */ #if defined(HAVE_SHA512_OPENSSL) list = pstrcat(p, list, *list ? "," : "", "rsa-sha2-512", NULL); #endif /* HAVE_SHA512_OPENSSL */ #if defined(HAVE_SHA256_OPENSSL) list = pstrcat(p, list, *list ? "," : "", "rsa-sha2-256", NULL); #endif /* HAVE_SHA256_OPENSSL */ list = pstrcat(p, list, *list ? "," : "", "ssh-rsa", NULL); #if !defined(OPENSSL_NO_DSA) list = pstrcat(p, list, *list ? "," : "", "ssh-dss", NULL); #endif /* OPENSSL_NO_DSA */ return list; } static struct proxy_ssh_kex *create_kex(pool *p) { struct proxy_ssh_kex *kex; const char *list; config_rec *c; pool *tmp_pool; tmp_pool = make_sub_pool(p); pr_pool_tag(tmp_pool, "Kex KEXINIT Pool"); kex = pcalloc(tmp_pool, sizeof(struct proxy_ssh_kex)); kex->pool = tmp_pool; kex->client_version = kex_client_version; kex->server_version = kex_server_version; kex->client_names = pcalloc(kex->pool, sizeof(struct proxy_ssh_kex_names)); kex->server_names = pcalloc(kex->pool, sizeof(struct proxy_ssh_kex_names)); kex->session_names = pcalloc(kex->pool, sizeof(struct proxy_ssh_kex_names)); kex->use_hostkey_type = PROXY_SSH_KEY_UNKNOWN; kex->dh = NULL; kex->e = NULL; kex->hash = NULL; kex->k = NULL; kex->h = NULL; kex->hlen = 0; kex->dh_gex_min = kex->dh_gex_pref = kex->dh_gex_max = 0; kex->rsa = NULL; kex->rsa_encrypted = NULL; kex->rsa_encrypted_len = 0; list = get_kexinit_exchange_list(kex->pool); kex->client_names->kex_algo = list; list = get_kexinit_hostkey_algo_list(kex->pool); kex->client_names->server_hostkey_algo = list; list = proxy_ssh_crypto_get_kexinit_cipher_list(kex->pool); kex->client_names->c2s_encrypt_algo = list; kex->client_names->s2c_encrypt_algo = list; list = proxy_ssh_crypto_get_kexinit_digest_list(kex->pool); kex->client_names->c2s_mac_algo = list; kex->client_names->s2c_mac_algo = list; c = find_config(main_server->conf, CONF_PARAM, "ProxySFTPCompression", FALSE); if (c != NULL) { int comp_mode; comp_mode = *((int *) c->argv[0]); switch (comp_mode) { case 2: /* Advertise that we support OpenSSH's "delayed" compression mode. */ kex->client_names->c2s_comp_algo = "zlib@openssh.com,zlib,none"; kex->client_names->s2c_comp_algo = "zlib@openssh.com,zlib,none"; break; case 1: kex->client_names->c2s_comp_algo = "zlib,none"; kex->client_names->s2c_comp_algo = "zlib,none"; break; default: kex->client_names->c2s_comp_algo = "none"; kex->client_names->s2c_comp_algo = "none"; break; } } else { kex->client_names->c2s_comp_algo = "none"; kex->client_names->s2c_comp_algo = "none"; } kex->client_names->c2s_lang = ""; kex->client_names->s2c_lang = ""; return kex; } static void destroy_kex(struct proxy_ssh_kex *kex) { if (kex != NULL) { if (kex->dh != NULL) { #if OPENSSL_VERSION_NUMBER < 0x10100000L if (kex->dh->p != NULL) { BN_clear_free(kex->dh->p); kex->dh->p = NULL; } if (kex->dh->g != NULL) { BN_clear_free(kex->dh->g); kex->dh->g = NULL; } #endif /* prior to OpenSSL-1.1.0 */ DH_free(kex->dh); kex->dh = NULL; } if (kex->rsa != NULL) { RSA_free(kex->rsa); kex->rsa = NULL; } if (kex->rsa_encrypted != NULL) { pr_memscrub(kex->rsa_encrypted, kex->rsa_encrypted_len); kex->rsa_encrypted = NULL; kex->rsa_encrypted_len = 0; } if (kex->e != NULL) { BN_clear_free((BIGNUM *) kex->e); kex->e = NULL; } if (kex->k != NULL) { BN_clear_free((BIGNUM *) kex->k); kex->k = NULL; } if (kex->hlen > 0) { pr_memscrub((char *) kex->h, kex->hlen); kex->hlen = 0; } #if defined(PR_USE_OPENSSL_ECC) if (kex->ec != NULL) { EC_KEY_free(kex->ec); kex->ec = NULL; } if (kex->server_point != NULL) { EC_POINT_free(kex->server_point); kex->server_point = NULL; } #endif /* PR_USE_OPENSSL_ECC */ #if defined(PR_USE_SODIUM) && defined(HAVE_SHA256_OPENSSL) if (kex->client_curve25519_priv_key != NULL) { pr_memscrub(kex->client_curve25519_priv_key, CURVE25519_SIZE); kex->client_curve25519_priv_key = NULL; } if (kex->client_curve25519_pub_key != NULL) { pr_memscrub(kex->client_curve25519_pub_key, CURVE25519_SIZE); kex->client_curve25519_pub_key = NULL; } if (kex->server_curve25519_pub_key != NULL) { pr_memscrub(kex->server_curve25519_pub_key, CURVE25519_SIZE); kex->server_curve25519_pub_key = NULL; } #endif /* PR_USE_SODIUM and HAVE_SHA256_OPENSSL */ #if defined(HAVE_X448_OPENSSL) && defined(HAVE_SHA512_OPENSSL) if (kex->client_curve448_priv_key != NULL) { pr_memscrub(kex->client_curve448_priv_key, CURVE448_SIZE); kex->client_curve448_priv_key = NULL; } if (kex->client_curve448_pub_key != NULL) { pr_memscrub(kex->client_curve448_pub_key, CURVE448_SIZE); kex->client_curve448_pub_key = NULL; } if (kex->server_curve448_pub_key != NULL) { pr_memscrub(kex->server_curve448_pub_key, CURVE448_SIZE); kex->server_curve448_pub_key = NULL; } #endif /* HAVE_X448_OPENSSL and HAVE_SHA512_OPENSSL */ if (kex->pool != NULL) { destroy_pool(kex->pool); } } kex_first_kex = kex_rekey_kex = NULL; } static int setup_kex_algo(struct proxy_ssh_kex *kex, const char *algo) { if (strcmp(algo, "diffie-hellman-group1-sha1") == 0) { if (create_dh(kex, PROXY_SSH_DH_GROUP1_SHA1) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error using '%s' as the key exchange algorithm: %s", algo, strerror(errno)); return -1; } kex->session_names->kex_algo = algo; return 0; } if (strcmp(algo, "diffie-hellman-group14-sha1") == 0) { if (create_dh(kex, PROXY_SSH_DH_GROUP14_SHA1) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error using '%s' as the key exchange algorithm: %s", algo, strerror(errno)); return -1; } kex->session_names->kex_algo = algo; return 0; } if (strcmp(algo, "diffie-hellman-group14-sha256") == 0) { if (create_dh(kex, PROXY_SSH_DH_GROUP14_SHA256) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error using '%s' as the key exchange algorithm: %s", algo, strerror(errno)); return -1; } kex->session_names->kex_algo = algo; return 0; } if (strcmp(algo, "diffie-hellman-group16-sha512") == 0) { if (create_dh(kex, PROXY_SSH_DH_GROUP16_SHA512) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error using '%s' as the key exchange algorithm: %s", algo, strerror(errno)); return -1; } kex->session_names->kex_algo = algo; return 0; } if (strcmp(algo, "diffie-hellman-group18-sha512") == 0) { if (create_dh(kex, PROXY_SSH_DH_GROUP18_SHA512) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error using '%s' as the key exchange algorithm: %s", algo, strerror(errno)); return -1; } kex->session_names->kex_algo = algo; return 0; } if (strcmp(algo, "diffie-hellman-group-exchange-sha1") == 0) { if (prepare_dh(kex, PROXY_SSH_DH_GEX_SHA1) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error using '%s' as the key exchange algorithm: %s", algo, strerror(errno)); return -1; } kex->session_names->kex_algo = algo; kex->use_gex = TRUE; return 0; } if (strcmp(algo, "rsa1024-sha1") == 0) { if (create_kexrsa(kex, PROXY_SSH_KEXRSA_SHA1) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error using '%s' as the key exchange algorithm: %s", algo, strerror(errno)); return -1; } kex->session_names->kex_algo = algo; kex->use_kexrsa = TRUE; return 0; } #if ((OPENSSL_VERSION_NUMBER > 0x000907000L && defined(OPENSSL_FIPS)) || \ (OPENSSL_VERSION_NUMBER > 0x000908000L)) && \ defined(HAVE_SHA256_OPENSSL) if (strcmp(algo, "diffie-hellman-group-exchange-sha256") == 0) { if (prepare_dh(kex, PROXY_SSH_DH_GEX_SHA256) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error using '%s' as the key exchange algorithm: %s", algo, strerror(errno)); return -1; } kex->session_names->kex_algo = algo; kex->use_gex = TRUE; return 0; } if (strcmp(algo, "rsa2048-sha256") == 0) { if (create_kexrsa(kex, PROXY_SSH_KEXRSA_SHA256) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error using '%s' as the key exchange algorithm: %s", algo, strerror(errno)); return -1; } kex->session_names->kex_algo = algo; kex->use_kexrsa = TRUE; return 0; } #endif #if defined(PR_USE_OPENSSL_ECC) if (strcmp(algo, "ecdh-sha2-nistp256") == 0) { if (create_ecdh(kex, PROXY_SSH_ECDH_SHA256) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error using '%s' as the key exchange algorithm: %s", algo, strerror(errno)); return -1; } kex->session_names->kex_algo = algo; kex->use_ecdh = TRUE; return 0; } if (strcmp(algo, "ecdh-sha2-nistp384") == 0) { if (create_ecdh(kex, PROXY_SSH_ECDH_SHA384) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error using '%s' as the key exchange algorithm: %s", algo, strerror(errno)); return -1; } kex->session_names->kex_algo = algo; kex->use_ecdh = TRUE; return 0; } if (strcmp(algo, "ecdh-sha2-nistp521") == 0) { if (create_ecdh(kex, PROXY_SSH_ECDH_SHA512) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error using '%s' as the key exchange algorithm: %s", algo, strerror(errno)); return -1; } kex->session_names->kex_algo = algo; kex->use_ecdh = TRUE; return 0; } #endif /* PR_USE_OPENSSL_ECC */ #if defined(PR_USE_SODIUM) && defined(HAVE_SHA256_OPENSSL) if (strcmp(algo, "curve25519-sha256") == 0 || strcmp(algo, "curve25519-sha256@libssh.org") == 0) { if (create_curve25519(kex) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error using '%s' as the key exchange algorithm: %s", algo, strerror(errno)); return -1; } kex->hash = EVP_sha256(); kex->session_names->kex_algo = algo; kex->use_curve25519 = TRUE; return 0; } #endif /* PR_USE_SODIUM and HAVE_SHA256_OPENSSL */ #if defined(HAVE_X448_OPENSSL) && defined(HAVE_SHA512_OPENSSL) if (strcmp(algo, "curve448-sha512") == 0) { if (create_curve448(kex) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error using '%s' as the key exchange algorithm: %s", algo, strerror(errno)); return -1; } kex->hash = EVP_sha512(); kex->session_names->kex_algo = algo; kex->use_curve448 = TRUE; return 0; } #endif /* HAVE_X448_OPENSSL and HAVE_SHA512_OPENSSL */ if (strcmp(algo, "ext-info-c") == 0 || strcmp(algo, "ext-info-s") == 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to use extension negotiation algorithm '%s' for key exchange", algo); errno = EINVAL; return -1; } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unsupported key exchange algorithm '%s'", algo); errno = EINVAL; return -1; } static int setup_hostkey_algo(struct proxy_ssh_kex *kex, const char *algo) { kex->session_names->server_hostkey_algo = (char *) algo; if (strcmp(algo, "ssh-dss") == 0) { kex->use_hostkey_type = PROXY_SSH_KEY_DSA; return 0; } if (strcmp(algo, "ssh-rsa") == 0) { kex->use_hostkey_type = PROXY_SSH_KEY_RSA; return 0; } #if defined(HAVE_SHA256_OPENSSL) if (strcmp(algo, "rsa-sha2-256") == 0) { kex->use_hostkey_type = PROXY_SSH_KEY_RSA_SHA256; return 0; } #endif /* HAVE_SHA256_OPENSSL */ #if defined(HAVE_SHA512_OPENSSL) if (strcmp(algo, "rsa-sha2-512") == 0) { kex->use_hostkey_type = PROXY_SSH_KEY_RSA_SHA512; return 0; } #endif /* HAVE_SHA512_OPENSSL */ #if defined(PR_USE_OPENSSL_ECC) if (strcmp(algo, "ecdsa-sha2-nistp256") == 0) { kex->use_hostkey_type = PROXY_SSH_KEY_ECDSA_256; return 0; } if (strcmp(algo, "ecdsa-sha2-nistp384") == 0) { kex->use_hostkey_type = PROXY_SSH_KEY_ECDSA_384; return 0; } if (strcmp(algo, "ecdsa-sha2-nistp521") == 0) { kex->use_hostkey_type = PROXY_SSH_KEY_ECDSA_521; return 0; } #endif /* PR_USE_OPENSSL_ECC */ #if defined(PR_USE_SODIUM) if (strcmp(algo, "ssh-ed25519") == 0) { kex->use_hostkey_type = PROXY_SSH_KEY_ED25519; return 0; } #endif /* PR_USE_SODIUM */ #if defined(HAVE_X448_OPENSSL) if (strcmp(algo, "ssh-ed448") == 0) { kex->use_hostkey_type = PROXY_SSH_KEY_ED448; return 0; } #endif /* HAVE_X448_OPENSSL */ errno = EINVAL; return -1; } static int setup_c2s_encrypt_algo(struct proxy_ssh_kex *kex, const char *algo) { if (proxy_ssh_cipher_set_read_algo(kex_pool, algo) < 0) { return -1; } kex->session_names->c2s_encrypt_algo = algo; return 0; } static int setup_s2c_encrypt_algo(struct proxy_ssh_kex *kex, const char *algo) { if (proxy_ssh_cipher_set_write_algo(kex_pool, algo) < 0) { return -1; } kex->session_names->s2c_encrypt_algo = algo; return 0; } static int setup_c2s_mac_algo(struct proxy_ssh_kex *kex, const char *algo) { if (proxy_ssh_mac_set_read_algo(kex_pool, algo) < 0) { return -1; } kex->session_names->c2s_mac_algo = algo; return 0; } static int setup_s2c_mac_algo(struct proxy_ssh_kex *kex, const char *algo) { if (proxy_ssh_mac_set_write_algo(kex_pool, algo) < 0) { return -1; } kex->session_names->s2c_mac_algo = algo; return 0; } static int setup_c2s_comp_algo(struct proxy_ssh_kex *kex, const char *algo) { if (proxy_ssh_compress_set_read_algo(kex_pool, algo) < 0) { return -1; } kex->session_names->c2s_comp_algo = algo; return 0; } static int setup_s2c_comp_algo(struct proxy_ssh_kex *kex, const char *algo) { if (proxy_ssh_compress_set_write_algo(kex_pool, algo) < 0) { return -1; } kex->session_names->s2c_comp_algo = algo; return 0; } static int setup_c2s_lang(struct proxy_ssh_kex *kex, const char *lang) { /* XXX Need to implement the functionality here. */ kex->session_names->c2s_lang = lang; return 0; } static int setup_s2c_lang(struct proxy_ssh_kex *kex, const char *lang) { /* XXX Need to implement the functionality here. */ kex->session_names->s2c_lang = lang; return 0; } static int get_session_names(struct proxy_ssh_kex *kex, int *correct_guess) { const char *kex_algo, *shared, *client_list, *server_list; const char *client_pref, *server_pref; pool *tmp_pool; tmp_pool = make_sub_pool(kex->pool); pr_pool_tag(tmp_pool, "Proxy SSH session shared name pool"); client_list = kex->client_names->kex_algo; server_list = kex->server_names->kex_algo; pr_trace_msg(trace_channel, 8, "client-sent key exchange algorithms: %s", client_list); pr_trace_msg(trace_channel, 8, "server-sent key exchange algorithms: %s", server_list); client_pref = get_preferred_name(tmp_pool, client_list); server_pref = get_preferred_name(tmp_pool, server_list); /* Did the client correctly guess at the key exchange algorithm that * we would list first in our server list, if it says it sent * a guess KEX packet? */ if (kex->first_kex_follows == TRUE && *correct_guess == TRUE && client_pref != NULL && server_pref != NULL) { if (strcmp(client_pref, server_pref) != 0) { *correct_guess = FALSE; pr_trace_msg(trace_channel, 7, "client incorrectly guessed key exchange algorithm '%s'", client_pref); } else { pr_trace_msg(trace_channel, 7, "client correctly guessed key exchange algorithm '%s'", server_pref); } } kex_algo = proxy_ssh_misc_namelist_shared(kex->pool, client_list, server_list); if (kex_algo != NULL) { /* Unlike the following algorithms, we wait to setup the chosen kex algo * until the end. Why? The kex algo setup may require knowledge of the * ciphers chosen for encryption, MAC, etc (Bug#4097). */ (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, " + Session key exchange: %s", kex_algo); pr_trace_msg(trace_channel, 20, "session key exchange algorithm: %s", kex_algo); /* Did the server indicate EXT_INFO support */ kex->use_ext_info = proxy_ssh_misc_namelist_contains(kex->pool, server_list, "ext-info-s"); pr_trace_msg(trace_channel, 20, "server %s EXT_INFO support", kex->use_ext_info ? "signaled" : "did not signal" ); if (!(proxy_opts & PROXY_OPT_SSH_NO_STRICT_KEX)) { /* Did the server indicate "strict kex" support (Issue 257)? * * Note that we only check for this if it is our first KEXINIT. * The "strict kex" extension is ignored in any subsequent KEXINITs, as * for rekeys. */ if (kex_done_first_kex == FALSE) { use_strict_kex = proxy_ssh_misc_namelist_contains(kex->pool, server_list, "kex-strict-s-v00@openssh.com"); pr_trace_msg(trace_channel, 20, "server %s strict KEX support", use_strict_kex ? "signaled" : "did not signal" ); } } } else { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "no shared key exchange algorithm found (client sent '%s', server sent " "'%s')", client_list, server_list); destroy_pool(tmp_pool); return -1; } client_list = kex->client_names->server_hostkey_algo; server_list = kex->server_names->server_hostkey_algo; pr_trace_msg(trace_channel, 8, "client-sent host key algorithms: %s", client_list); pr_trace_msg(trace_channel, 8, "server-sent host key algorithms: %s", server_list); shared = proxy_ssh_misc_namelist_shared(kex->pool, client_list, server_list); if (shared != NULL) { if (setup_hostkey_algo(kex, shared) < 0) { destroy_pool(tmp_pool); return -1; } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, " + Session server hostkey: %s", shared); pr_trace_msg(trace_channel, 20, "session server hostkey algorithm: %s", shared); } else { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "no shared server hostkey algorithm found (client sent '%s', server sent " "'%s'", client_list, server_list); destroy_pool(tmp_pool); return -1; } client_list = kex->client_names->c2s_encrypt_algo; server_list = kex->server_names->c2s_encrypt_algo; pr_trace_msg(trace_channel, 8, "client-sent client encryption algorithms: %s", client_list); pr_trace_msg(trace_channel, 8, "server-sent client encryption algorithms: %s", server_list); shared = proxy_ssh_misc_namelist_shared(kex->pool, client_list, server_list); if (shared != NULL) { if (setup_c2s_encrypt_algo(kex, shared) < 0) { destroy_pool(tmp_pool); return -1; } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, " + Session client-to-server encryption: %s", shared); pr_trace_msg(trace_channel, 20, "session client-to-server encryption algorithm: %s", shared); } else { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "no shared client-to-server encryption algorithm found (client sent '%s'," " server sent '%s')", client_list, server_list); destroy_pool(tmp_pool); return -1; } client_list = kex->client_names->s2c_encrypt_algo; server_list = kex->server_names->s2c_encrypt_algo; pr_trace_msg(trace_channel, 8, "client-sent server encryption algorithms: %s", client_list); pr_trace_msg(trace_channel, 8, "server-sent server encryption algorithms: %s", server_list); shared = proxy_ssh_misc_namelist_shared(kex->pool, client_list, server_list); if (shared != NULL) { if (setup_s2c_encrypt_algo(kex, shared) < 0) { destroy_pool(tmp_pool); return -1; } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, " + Session server-to-client encryption: %s", shared); pr_trace_msg(trace_channel, 20, "session server-to-client encryption algorithm: %s", shared); } else { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "no shared server-to-client encryption algorithm found (client sent '%s'," " server sent '%s')", client_list, server_list); destroy_pool(tmp_pool); return -1; } client_list = kex->client_names->c2s_mac_algo; server_list = kex->server_names->c2s_mac_algo; pr_trace_msg(trace_channel, 8, "client-sent client MAC algorithms: %s", client_list); pr_trace_msg(trace_channel, 8, "server-sent client MAC algorithms: %s", server_list); /* Ignore MAC/digests when authenticated encryption algorithms are used. */ if (proxy_ssh_cipher_get_read_auth_size2() == 0) { shared = proxy_ssh_misc_namelist_shared(kex->pool, client_list, server_list); if (shared != NULL) { if (setup_c2s_mac_algo(kex, shared) < 0) { destroy_pool(tmp_pool); return -1; } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, " + Session client-to-server MAC: %s", shared); pr_trace_msg(trace_channel, 20, "session client-to-server MAC algorithm: %s", shared); } else { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "no shared client-to-server MAC algorithm found (client sent '%s', " "server sent '%s')", client_list, server_list); destroy_pool(tmp_pool); return -1; } } else { pr_trace_msg(trace_channel, 8, "ignoring MAC algorithms due to use of " "client-to-server authenticated cipher algorithm '%s'", kex->session_names->c2s_encrypt_algo); pr_trace_msg(trace_channel, 20, "session client-to-server MAC algorithm: "); } client_list = kex->client_names->s2c_mac_algo; server_list = kex->server_names->s2c_mac_algo; pr_trace_msg(trace_channel, 8, "client-sent server MAC algorithms: %s", client_list); pr_trace_msg(trace_channel, 8, "server-sent server MAC algorithms: %s", server_list); /* Ignore MAC/digests when authenticated encryption algorithms are used. */ if (proxy_ssh_cipher_get_write_auth_size2() == 0) { shared = proxy_ssh_misc_namelist_shared(kex->pool, client_list, server_list); if (shared != NULL) { if (setup_s2c_mac_algo(kex, shared) < 0) { destroy_pool(tmp_pool); return -1; } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, " + Session server-to-client MAC: %s", shared); pr_trace_msg(trace_channel, 20, "session server-to-client MAC algorithm: %s", shared); } else { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "no shared server-to-client MAC algorithm found (client sent '%s', " "server sent '%s')", client_list, server_list); destroy_pool(tmp_pool); return -1; } } else { pr_trace_msg(trace_channel, 8, "ignoring MAC algorithms due to use of " "server-to-client authenticated cipher algorithm '%s'", kex->session_names->s2c_encrypt_algo); pr_trace_msg(trace_channel, 20, "session server-to-client MAC algorithm: "); } client_list = kex->client_names->c2s_comp_algo; server_list = kex->server_names->c2s_comp_algo; pr_trace_msg(trace_channel, 8, "client-sent client compression algorithms: %s", client_list); pr_trace_msg(trace_channel, 8, "server-sent client compression algorithms: %s", server_list); shared = proxy_ssh_misc_namelist_shared(kex->pool, client_list, server_list); if (shared != NULL) { if (setup_c2s_comp_algo(kex, shared) < 0) { destroy_pool(tmp_pool); return -1; } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, " + Session client-to-server compression: %s", shared); pr_trace_msg(trace_channel, 20, "session client-to-server compression algorithm: %s", shared); } else { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "no shared client-to-server compression algorithm found (client sent " "'%s', server sent '%s'", client_list, server_list); destroy_pool(tmp_pool); return -1; } client_list = kex->client_names->s2c_comp_algo; server_list = kex->server_names->s2c_comp_algo; pr_trace_msg(trace_channel, 8, "client-sent server compression algorithms: %s", client_list); pr_trace_msg(trace_channel, 8, "server-sent server compression algorithms: %s", server_list); shared = proxy_ssh_misc_namelist_shared(kex->pool, client_list, server_list); if (shared != NULL) { if (setup_s2c_comp_algo(kex, shared) < 0) { destroy_pool(tmp_pool); return -1; } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, " + Session server-to-client compression: %s", shared); pr_trace_msg(trace_channel, 20, "session server-to-client compression algorithm: %s", shared); } else { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "no shared server-to-client compression algorithm found (client sent " "'%s', server sent '%s'", client_list, server_list); destroy_pool(tmp_pool); return -1; } client_list = kex->client_names->c2s_lang; server_list = kex->server_names->c2s_lang; pr_trace_msg(trace_channel, 8, "client-sent client languages: %s", client_list); pr_trace_msg(trace_channel, 8, "server-sent client languages: %s", client_list); shared = proxy_ssh_misc_namelist_shared(kex->pool, client_list, server_list); if (shared != NULL) { if (setup_c2s_lang(kex, shared) < 0) { destroy_pool(tmp_pool); return -1; } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, " + Session client-to-server language: %s", shared); pr_trace_msg(trace_channel, 20, "session client-to-server language: %s", shared); /* Currently ignore any lack of shared languages. */ } client_list = kex->client_names->s2c_lang; server_list = kex->server_names->s2c_lang; pr_trace_msg(trace_channel, 8, "client-sent server languages: %s", client_list); pr_trace_msg(trace_channel, 8, "server-sent server languages: %s", client_list); shared = proxy_ssh_misc_namelist_shared(kex->pool, client_list, server_list); if (shared != NULL) { if (setup_s2c_lang(kex, shared) < 0) { destroy_pool(tmp_pool); return -1; } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, " + Session server-to-client language: %s", shared); pr_trace_msg(trace_channel, 20, "session server-to-client language: %s", shared); /* Currently ignore any lack of shared languages. */ } /* Now that we've finished setting up the other bits, we can set up the * kex algo. */ if (setup_kex_algo(kex, kex_algo) < 0) { destroy_pool(tmp_pool); return -1; } destroy_pool(tmp_pool); return 0; } static int read_kexinit(struct proxy_ssh_packet *pkt, struct proxy_ssh_kex *kex) { unsigned char *buf; unsigned char *cookie; char *list; uint32_t buflen, reserved; buf = pkt->payload; buflen = pkt->payload_len; /* Make a copy of the payload for later. */ kex->server_kexinit_payload = palloc(kex->pool, pkt->payload_len); kex->server_kexinit_payload_len = pkt->payload_len; memcpy(kex->server_kexinit_payload, pkt->payload, pkt->payload_len); /* Read the cookie, which is a mandated length of 16 bytes. */ proxy_ssh_msg_read_data(pkt->pool, &buf, &buflen, 16, &cookie); proxy_ssh_msg_read_string(kex->pool, &buf, &buflen, &list); kex->server_names->kex_algo = list; proxy_ssh_msg_read_string(kex->pool, &buf, &buflen, &list); kex->server_names->server_hostkey_algo = list; proxy_ssh_msg_read_string(kex->pool, &buf, &buflen, &list); kex->server_names->c2s_encrypt_algo = list; proxy_ssh_msg_read_string(kex->pool, &buf, &buflen, &list); kex->server_names->s2c_encrypt_algo = list; proxy_ssh_msg_read_string(kex->pool, &buf, &buflen, &list); kex->server_names->c2s_mac_algo = list; proxy_ssh_msg_read_string(kex->pool, &buf, &buflen, &list); kex->server_names->s2c_mac_algo = list; proxy_ssh_msg_read_string(kex->pool, &buf, &buflen, &list); kex->server_names->c2s_comp_algo = list; proxy_ssh_msg_read_string(kex->pool, &buf, &buflen, &list); kex->server_names->s2c_comp_algo = list; /* Client-to-server languages */ proxy_ssh_msg_read_string(kex->pool, &buf, &buflen, &list); kex->server_names->c2s_lang = list; /* Server-to-client languages */ proxy_ssh_msg_read_string(kex->pool, &buf, &buflen, &list); kex->server_names->s2c_lang = list; /* Read the "first kex packet follows" byte */ proxy_ssh_msg_read_bool(pkt->pool, &buf, &buflen, &(kex->first_kex_follows)); pr_trace_msg(trace_channel, 3, "first kex packet follows = %s", kex->first_kex_follows ? "true" : "false"); /* Reserved flags */ proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &reserved); return 0; } static int write_kexinit(struct proxy_ssh_packet *pkt, struct proxy_ssh_kex *kex) { unsigned char cookie[16]; unsigned char *buf, *ptr; const char *list; uint32_t bufsz, buflen, len = 0; /* XXX Always have empty language lists; we really don't care. */ const char *langs = ""; bufsz = buflen = sizeof(char) + sizeof(cookie) + sizeof(uint32_t) + strlen(kex->client_names->kex_algo) + sizeof(uint32_t) + strlen(kex->client_names->server_hostkey_algo) + sizeof(uint32_t) + strlen(kex->client_names->c2s_encrypt_algo) + sizeof(uint32_t) + strlen(kex->client_names->s2c_encrypt_algo) + sizeof(uint32_t) + strlen(kex->client_names->c2s_mac_algo) + sizeof(uint32_t) + strlen(kex->client_names->s2c_mac_algo) + sizeof(uint32_t) + strlen(kex->client_names->c2s_comp_algo) + sizeof(uint32_t) + strlen(kex->client_names->s2c_comp_algo) + sizeof(uint32_t) + strlen(langs) + sizeof(uint32_t) + strlen(langs) + sizeof(char) + sizeof(uint32_t); ptr = buf = pcalloc(pkt->pool, bufsz); len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEXINIT); /* Try first to use cryptographically secure bytes for the cookie. * If that fails (e.g. if the PRNG hasn't been seeded well), use * pseudo-cryptographically secure bytes. */ memset(cookie, 0, sizeof(cookie)); #if OPENSSL_VERSION_NUMBER >= 0x10100000L RAND_bytes(cookie, sizeof(cookie)); #else if (RAND_bytes(cookie, sizeof(cookie)) != 1) { RAND_pseudo_bytes(cookie, sizeof(cookie)); } #endif /* prior to OpenSSL-1.1.0 */ len += proxy_ssh_msg_write_data(&buf, &buflen, cookie, sizeof(cookie), FALSE); list = kex->client_names->kex_algo; len += proxy_ssh_msg_write_string(&buf, &buflen, list); list = kex->client_names->server_hostkey_algo; len += proxy_ssh_msg_write_string(&buf, &buflen, list); list = kex->client_names->c2s_encrypt_algo; len += proxy_ssh_msg_write_string(&buf, &buflen, list); list = kex->client_names->s2c_encrypt_algo; len += proxy_ssh_msg_write_string(&buf, &buflen, list); list = kex->client_names->c2s_mac_algo; len += proxy_ssh_msg_write_string(&buf, &buflen, list); list = kex->client_names->s2c_mac_algo; len += proxy_ssh_msg_write_string(&buf, &buflen, list); list = kex->client_names->c2s_comp_algo; len += proxy_ssh_msg_write_string(&buf, &buflen, list); list = kex->client_names->s2c_comp_algo; len += proxy_ssh_msg_write_string(&buf, &buflen, list); /* XXX Need to support langs here. */ len += proxy_ssh_msg_write_string(&buf, &buflen, langs); len += proxy_ssh_msg_write_string(&buf, &buflen, langs); /* We don't try to optimistically guess what algorithms the client would * use and send a preemptive kex packet. */ len += proxy_ssh_msg_write_bool(&buf, &buflen, FALSE); len += proxy_ssh_msg_write_int(&buf, &buflen, 0); pkt->payload = ptr; pkt->payload_len = len; /* Make a copy of the payload for later. Skip past the first byte, which * is the KEXINIT identifier. */ kex->client_kexinit_payload_len = pkt->payload_len - 1; kex->client_kexinit_payload = palloc(kex->pool, pkt->payload_len - 1); memcpy(kex->client_kexinit_payload, pkt->payload + 1, pkt->payload_len - 1); return 0; } /* Only set the given environment variable/value IFF it is not already * present. */ static void set_env_var(pool *p, const char *k, const char *v) { const char *val; int have_val = FALSE; val = pr_env_get(p, k); if (val != NULL) { if (strcmp(val, v) == 0) { have_val = TRUE; } } if (have_val == FALSE) { k = pstrdup(p, k); v = pstrdup(p, v); pr_env_unset(p, k); pr_env_set(p, k, v); } } static int set_session_keys(struct proxy_ssh_kex *kex) { unsigned char *buf, *ptr; uint32_t buflen, bufsz, klen; int comp_read_flags, comp_write_flags; /* To date, the kex algo that has generated the largest K that I have * seen so far is "diffie-hellman-group18-sha512". */ bufsz = buflen = 2048; ptr = buf = palloc(kex_pool, bufsz); /* Need to use SSH2-style format of K for the key. */ klen = proxy_ssh_msg_write_mpint(&buf, &buflen, kex->k); if (proxy_ssh_cipher_set_read_key(kex_pool, kex->hash, ptr, klen, kex->h, kex->hlen, PROXY_SSH_ROLE_CLIENT) < 0) { pr_memscrub(ptr, bufsz); return -1; } if (proxy_ssh_cipher_set_write_key(kex_pool, kex->hash, ptr, klen, kex->h, kex->hlen, PROXY_SSH_ROLE_CLIENT) < 0) { pr_memscrub(ptr, bufsz); return -1; } if (proxy_ssh_mac_set_read_key(kex_pool, kex->hash, ptr, klen, kex->h, kex->hlen, PROXY_SSH_ROLE_CLIENT) < 0) { pr_memscrub(ptr, bufsz); return -1; } if (proxy_ssh_mac_set_write_key(kex_pool, kex->hash, ptr, klen, kex->h, kex->hlen, PROXY_SSH_ROLE_CLIENT) < 0) { pr_memscrub(ptr, bufsz); return -1; } comp_read_flags = comp_write_flags = PROXY_SSH_COMPRESS_FL_NEW_KEY; /* If we are rekeying, AND the existing compression is "delayed", then * we need to use slightly different compression flags. */ if (kex_rekey_kex != NULL) { const char *algo; algo = proxy_ssh_compress_get_read_algo(); if (strcmp(algo, "zlib@openssh.com") == 0) { comp_read_flags = PROXY_SSH_COMPRESS_FL_AUTHENTICATED; } algo = proxy_ssh_compress_get_write_algo(); if (strcmp(algo, "zlib@openssh.com") == 0) { comp_write_flags = PROXY_SSH_COMPRESS_FL_AUTHENTICATED; } } if (proxy_ssh_compress_init_read(comp_read_flags) < 0) { pr_memscrub(ptr, bufsz); return -1; } if (proxy_ssh_compress_init_write(comp_write_flags) < 0) { pr_memscrub(ptr, bufsz); return -1; } pr_memscrub(ptr, bufsz); set_env_var(session.pool, "PROXY_SSH_CLIENT_CIPHER_ALGO", proxy_ssh_cipher_get_write_algo()); set_env_var(session.pool, "PROXY_SSH_SERVER_CIPHER_ALGO", proxy_ssh_cipher_get_read_algo()); if (proxy_ssh_cipher_get_read_auth_size2() == 0) { set_env_var(session.pool, "PROXY_SSH_CLIENT_MAC_ALGO", proxy_ssh_mac_get_write_algo()); } else { set_env_var(session.pool, "PROXY_SSH_CLIENT_MAC_ALGO", "implicit"); } if (proxy_ssh_cipher_get_write_auth_size2() == 0) { set_env_var(session.pool, "PROXY_SSH_SERVER_MAC_ALGO", proxy_ssh_mac_get_read_algo()); } else { set_env_var(session.pool, "PROXY_SSH_SERVER_MAC_ALGO", "implicit"); } set_env_var(session.pool, "PROXY_SSH_CLIENT_COMPRESSION_ALGO", proxy_ssh_compress_get_write_algo()); set_env_var(session.pool, "PROXY_SSH_SERVER_COMPRESSION_ALGO", proxy_ssh_compress_get_read_algo()); set_env_var(session.pool, "PROXY_SSH_KEX_ALGO", kex->session_names->kex_algo); if (kex_rekey_kex != NULL) { pr_trace_msg(trace_channel, 3, "rekey KEX completed"); kex_rekey_kex = NULL; } return 0; } static int write_newkeys_reply(struct proxy_ssh_packet *pkt) { unsigned char *buf, *ptr; uint32_t bufsz, buflen, len = 0; /* Write out the NEWKEYS message. */ bufsz = buflen = 1; ptr = buf = palloc(pkt->pool, bufsz); len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_NEWKEYS); pkt->payload = ptr; pkt->payload_len = len; return 0; } static int handle_server_hostkey(pool *p, enum proxy_ssh_key_type_e hostkey_type, unsigned char *hostkey_data, uint32_t hostkey_datalen) { unsigned int vhost_id; const struct proxy_session *proxy_sess; const char *backend_uri, *hostkey_algo, *stored_hostkey_algo = NULL; const unsigned char *stored_hostkey_data = NULL; uint32_t stored_hostkey_datalen = 0; proxy_sess = pr_table_get(session.notes, "mod_proxy.proxy-session", NULL); if (proxy_sess == NULL) { /* Unlikely to occur. */ errno = EINVAL; return -1; } backend_uri = proxy_conn_get_uri(proxy_sess->dst_pconn); vhost_id = main_server->sid; hostkey_algo = proxy_ssh_keys_get_key_type_desc(hostkey_type); stored_hostkey_data = (kex_ds->hostkey_get)(p, kex_ds->dsh, vhost_id, backend_uri, &stored_hostkey_algo, &stored_hostkey_datalen); if (stored_hostkey_data == NULL) { if (errno != ENOENT) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error retrieving stored hostkey for vhost ID %u, URI '%s': %s", vhost_id, backend_uri, strerror(errno)); return 0; } pr_trace_msg(trace_channel, 18, "no existing hostkey stored for vhost ID %u, URI '%s', " "storing '%s' hostkey (%lu bytes)", vhost_id, backend_uri, hostkey_algo, (unsigned long) hostkey_datalen); if ((kex_ds->hostkey_add)(p, kex_ds->dsh, vhost_id, backend_uri, hostkey_algo, hostkey_data, hostkey_datalen) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error adding '%s' hostkey for vhost ID %u, URI '%s': %s", hostkey_algo, vhost_id, backend_uri, strerror(errno)); } } else { int verified = TRUE; pr_trace_msg(trace_channel, 12, "found stored '%s' hostkey (%lu bytes) for vhost ID %u, URI '%s'", stored_hostkey_algo, (unsigned long) stored_hostkey_datalen, vhost_id, backend_uri); if (strcmp(hostkey_algo, stored_hostkey_algo) != 0) { pr_trace_msg(trace_channel, 1, "stored hostkey for vhost ID %u, URI '%s' uses different algorithm: " "'%s' (stored), '%s' (current)", vhost_id, backend_uri, stored_hostkey_algo, hostkey_algo); verified = FALSE; } if (verified == TRUE && hostkey_datalen != stored_hostkey_datalen) { pr_trace_msg(trace_channel, 1, "stored hostkey for vhost ID %u, URI '%s' has different length: " "%lu bytes (stored), %lu bytes (current)", vhost_id, backend_uri, (unsigned long) stored_hostkey_datalen, (unsigned long) hostkey_datalen); verified = FALSE; } if (verified == TRUE && memcmp(hostkey_data, stored_hostkey_data, hostkey_datalen) != 0) { pr_trace_msg(trace_channel, 1, "stored hostkey for vhost ID %u, URI '%s' does not match current key", vhost_id, backend_uri); verified = FALSE; } if (verified == TRUE) { pr_trace_msg(trace_channel, 18, "stored hostkey matches current hostkey for vhost ID %u, URI '%s'", vhost_id, backend_uri); } else { if (kex_verify_hostkeys == TRUE) { /* TODO: This is where we would implement functionality similar to * OpenSSH's UpdateHostKeys, via hostkey rotation extensions, where * available. */ /* TODO: If we fail the KEX here, what recourse does the admin have? * We currently are not providing a way to update/remove the offending * stored hostkey in the SQLite database. * * For now, just loudly log the mismatch. */ (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "stored hostkey does not match current hostkey " "(vhost ID %u, URI '%s') and ProxySFTPVerifyServer is enabled", vhost_id, backend_uri); } else { /* Replace the stored hostkey. */ pr_trace_msg(trace_channel, 10, "stored hostkey does not match current " "hostkey (vhost ID %u, URI '%s') and ProxySFTPVerifyServer is " "disabled, updating stored hostkey", vhost_id, backend_uri); if ((kex_ds->hostkey_update)(p, kex_ds->dsh, vhost_id, backend_uri, hostkey_algo, hostkey_data, hostkey_datalen) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error updating '%s' hostkey for vhost ID %u, URI '%s': %s", hostkey_algo, vhost_id, backend_uri, strerror(errno)); } } } } return 0; } static struct proxy_ssh_packet *read_kex_packet(pool *p, struct proxy_ssh_kex *kex, conn_t *conn, int disconn_code, char *found_msg_type, unsigned int ntypes, ...) { register unsigned int i; va_list ap; struct proxy_ssh_packet *pkt = NULL; array_header *allowed_types; pr_trace_msg(trace_channel, 9, "waiting for a message of %d %s from server", ntypes, ntypes != 1 ? "types" : "type"); allowed_types = make_array(p, 1, sizeof(char)); va_start(ap, ntypes); while (ntypes-- > 0) { *((char *) push_array(allowed_types)) = va_arg(ap, int); } va_end(ap); /* Keep looping until we get the desired message, or we time out (hopefully * via TimeoutLogin or somesuch). */ while (pkt == NULL) { int found = FALSE, res; char msg_type; pr_signals_handle(); pkt = proxy_ssh_packet_create(p); res = proxy_ssh_packet_read(conn, pkt); if (res < 0) { int xerrno = errno; destroy_kex(kex); destroy_pool(pkt->pool); errno = xerrno; return NULL; } pr_response_clear(&resp_list); pr_response_clear(&resp_err_list); pr_response_set_pool(pkt->pool); /* Per RFC 4253, Section 11, DEBUG, DISCONNECT, IGNORE, and UNIMPLEMENTED * messages can occur at any time, even during KEX. We have to be prepared * for this, and Do The Right Thing(tm). * * However, due to the Terrapin attack, if we are using a "strict KEX" * mode, then only DISCONNECT messages can occur during KEX; DEBUG, * IGNORE, and UNIMPLEMENTED messages will lead to disconnecting. */ msg_type = proxy_ssh_packet_get_msg_type(pkt); for (i = 0; i < allowed_types->nelts; i++) { if (msg_type == ((unsigned char *) allowed_types->elts)[i]) { /* Exactly what we were looking for. Excellent. */ pr_trace_msg(trace_channel, 13, "received expected %s message", proxy_ssh_packet_get_msg_type_desc(msg_type)); if (found_msg_type != NULL) { /* The caller wants to know the type of message we're returning; * packet_get_msg_type() performs a destructive read. */ *found_msg_type = msg_type; } found = TRUE; break; } } if (found == TRUE) { break; } switch (msg_type) { /* DISCONNECT messages are always allowed. */ case PROXY_SSH_MSG_DISCONNECT: proxy_ssh_packet_handle_disconnect(pkt); pr_response_set_pool(NULL); pkt = NULL; break; case PROXY_SSH_MSG_DEBUG: if (use_strict_kex == FALSE) { proxy_ssh_packet_handle_debug(pkt); pr_response_set_pool(NULL); pkt = NULL; break; } case PROXY_SSH_MSG_IGNORE: if (use_strict_kex == FALSE) { proxy_ssh_packet_handle_ignore(pkt); pr_response_set_pool(NULL); pkt = NULL; break; } case PROXY_SSH_MSG_UNIMPLEMENTED: if (use_strict_kex == FALSE) { proxy_ssh_packet_handle_unimplemented(pkt); pr_response_set_pool(NULL); pkt = NULL; break; } default: /* For any other message type, it's considered a protocol error. */ (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "received %s (%d) unexpectedly%s, disconnecting", proxy_ssh_packet_get_msg_type_desc(msg_type), msg_type, use_strict_kex ? " during strict KEX" : ""); pr_response_set_pool(NULL); destroy_kex(kex); destroy_pool(pkt->pool); PROXY_SSH_DISCONNECT_CONN(conn, disconn_code, NULL); } } return pkt; } static int write_dh_init(struct proxy_ssh_packet *pkt, struct proxy_ssh_kex *kex) { unsigned char *buf, *ptr; uint32_t buflen, bufsz, len = 0; const BIGNUM *dh_pub_key; /* In our DH_INIT, send 'e', our client DH public key. */ #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) DH_get0_key(kex->dh, &dh_pub_key, NULL); #else dh_pub_key = kex->dh->pub_key; #endif /* prior to OpenSSL-1.1.0 */ bufsz = buflen = 2048; /* XXX Is this buffer large enough? Too large? */ ptr = buf = palloc(pkt->pool, bufsz); len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEX_DH_INIT); len += proxy_ssh_msg_write_mpint(&buf, &buflen, dh_pub_key); pkt->payload = ptr; pkt->payload_len = len; return 0; } static int read_dh_reply(struct proxy_ssh_packet *pkt, struct proxy_ssh_kex *kex) { const unsigned char *h; unsigned char *buf, *buf2, *server_hostkey_data = NULL, *sig = NULL; uint32_t buflen, server_hostkey_datalen = 0, siglen = 0, hlen = 0; const BIGNUM *server_pub_key = NULL, *k = NULL; size_t dh_len = 0; int res; buf = pkt->payload; buflen = pkt->payload_len; /* See RFC 4253, Section 8 "Diffie-Hellman Key Exchange" */ proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &server_hostkey_datalen); proxy_ssh_msg_read_data(pkt->pool, &buf, &buflen, server_hostkey_datalen, &server_hostkey_data); res = handle_server_hostkey(pkt->pool, kex->use_hostkey_type, server_hostkey_data, server_hostkey_datalen); if (res < 0) { int xerrno; xerrno = errno; DH_free(kex->dh); kex->dh = NULL; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error handling server host key: %s", strerror(xerrno)); errno = xerrno; return -1; } proxy_ssh_msg_read_mpint(pkt->pool, &buf, &buflen, &server_pub_key); if (have_good_dh(kex->dh, server_pub_key) < 0) { DH_free(kex->dh); kex->dh = NULL; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "invalid server public DH key"); return -1; } /* Compute the shared secret. */ dh_len = DH_size(kex->dh); buf2 = palloc(pkt->pool, dh_len); pr_trace_msg(trace_channel, 12, "computing DH key"); res = DH_compute_key(buf2, server_pub_key, kex->dh); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error computing DH shared secret: %s", proxy_ssh_crypto_get_errors()); DH_free(kex->dh); kex->dh = NULL; return -1; } k = BN_new(); if (BN_bin2bn(buf2, res, (BIGNUM *) k) == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error converting DH shared secret to BN: %s", proxy_ssh_crypto_get_errors()); DH_free(kex->dh); kex->dh = NULL; return -1; } kex->k = k; /* Calculate H */ h = calculate_h(pkt->pool, kex, server_hostkey_data, server_hostkey_datalen, server_pub_key, k, &hlen); if (h == NULL) { BN_clear_free((BIGNUM *) kex->k); kex->k = NULL; DH_free(kex->dh); kex->dh = NULL; return -1; } proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &siglen); proxy_ssh_msg_read_data(pkt->pool, &buf, &buflen, siglen, &sig); /* Verify H */ res = verify_h(pkt->pool, kex, server_hostkey_data, server_hostkey_datalen, sig, siglen, h, hlen); if (res < 0) { BN_clear_free((BIGNUM *) kex->k); kex->k = NULL; DH_free(kex->dh); kex->dh = NULL; return -1; } kex->h = palloc(kex->pool, hlen); kex->hlen = hlen; memcpy((char *) kex->h, h, kex->hlen); /* Save H as the session ID. */ proxy_ssh_session_set_id(session.pool, h, hlen); return 0; } static int handle_kex_dh(struct proxy_ssh_kex *kex, conn_t *conn) { int res; struct proxy_ssh_packet *pkt; pr_trace_msg(trace_channel, 9, "writing DH_INIT message to server"); pkt = proxy_ssh_packet_create(kex_pool); res = write_dh_init(pkt, kex); if (res < 0) { destroy_pool(pkt->pool); PROXY_SSH_DISCONNECT_CONN(conn, PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL); } res = proxy_ssh_packet_write(conn, pkt); if (res < 0) { destroy_pool(pkt->pool); PROXY_SSH_DISCONNECT_CONN(conn, PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL); } destroy_pool(pkt->pool); pr_trace_msg(trace_channel, 9, "reading DH_REPLY message from server"); pkt = read_kex_packet(kex_pool, kex, conn, PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL, 1, PROXY_SSH_MSG_KEX_DH_REPLY); res = read_dh_reply(pkt, kex); if (res < 0) { destroy_pool(pkt->pool); PROXY_SSH_DISCONNECT_CONN(conn, PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL); } destroy_pool(pkt->pool); return 0; } /* Values from NIST Special Publication 800-57: Recommendation for Key * Management Part 1 (rev 3) limited by the recommended maximum value from * RFC 4419 section 3. */ static uint32_t estimate_dh(int nbits) { if (nbits <= 112) { return PROXY_SSH_DH_MIN_LEN; } if (nbits <= 128) { return 3072; } if (nbits <= 192) { return 7680; } return PROXY_SSH_DH_MAX_LEN; } static int write_dh_gex_request(struct proxy_ssh_packet *pkt, struct proxy_ssh_kex *kex) { unsigned char *buf, *ptr; uint32_t buflen, bufsz, len = 0; int dh_nbits = 0; /* Estimate our desired DH size based on our negotiated ciphers. */ dh_nbits = get_dh_nbits(kex); kex->dh_gex_pref = estimate_dh(dh_nbits); if (kex->dh_gex_pref < PROXY_SSH_DH_MIN_LEN) { kex->dh_gex_pref = PROXY_SSH_DH_MIN_LEN; } if (kex->dh_gex_pref > PROXY_SSH_DH_MAX_LEN) { kex->dh_gex_pref = PROXY_SSH_DH_MAX_LEN; } /* XXX Is this buffer large enough? Too large? */ bufsz = buflen = 2048; ptr = buf = palloc(pkt->pool, bufsz); if (proxy_ssh_interop_supports_feature(PROXY_SSH_FEAT_DH_NEW_GEX) == TRUE) { kex->dh_gex_min = PROXY_SSH_DH_MIN_LEN; kex->dh_gex_max = PROXY_SSH_DH_MAX_LEN; len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEX_DH_GEX_REQUEST); len += proxy_ssh_msg_write_int(&buf, &buflen, kex->dh_gex_min); len += proxy_ssh_msg_write_int(&buf, &buflen, kex->dh_gex_pref); len += proxy_ssh_msg_write_int(&buf, &buflen, kex->dh_gex_max); } else { len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEX_DH_GEX_REQUEST_OLD); len += proxy_ssh_msg_write_int(&buf, &buflen, kex->dh_gex_pref); } pkt->payload = ptr; pkt->payload_len = len; return 0; } static int create_gex_dh(struct proxy_ssh_kex *kex, const BIGNUM *dh_p, const BIGNUM *dh_g) { unsigned int attempts = 0; int dh_nbits; DH *dh; if (kex->dh != NULL) { #if OPENSSL_VERSION_NUMBER < 0x10100000L if (kex->dh->p != NULL) { BN_clear_free(kex->dh->p); kex->dh->p = NULL; } if (kex->dh->g != NULL) { BN_clear_free(kex->dh->g); kex->dh->g = NULL; } if (kex->dh->priv_key != NULL) { BN_clear_free(kex->dh->priv_key); kex->dh->priv_key = NULL; } if (kex->dh->pub_key != NULL) { BN_clear_free(kex->dh->pub_key); kex->dh->pub_key = NULL; } #endif /* prior to OpenSSL-1.1.0 */ DH_free(kex->dh); kex->dh = NULL; } dh_nbits = get_dh_nbits(kex); /* We have 10 attempts to make a DH key which passes muster. */ while (attempts <= 10) { const BIGNUM *dh_pub_key = NULL, *dh_priv_key = NULL; pr_signals_handle(); attempts++; pr_trace_msg(trace_channel, 9, "attempt #%u to create a good DH key", attempts); dh = DH_new(); if (dh == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error creating DH: %s", proxy_ssh_crypto_get_errors()); return -1; } #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) DH_set0_pqg(dh, (BIGNUM *) dh_p, NULL, (BIGNUM *) dh_g); #else dh->p = dh_p; dh->g = dh_g; #endif /* prior to OpenSSL-1.1.0 */ dh_priv_key = BN_new(); /* Generate a random private exponent of the desired size, in bits. */ if (!BN_rand((BIGNUM *) dh_priv_key, dh_nbits, 0, 0)) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error generating DH random key (%d bits): %s", dh_nbits, proxy_ssh_crypto_get_errors()); BN_clear_free((BIGNUM *) dh_priv_key); DH_free(dh); return -1; } dh_pub_key = BN_new(); #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) DH_set0_key(dh, (BIGNUM *) dh_pub_key, (BIGNUM *) dh_priv_key); #else dh->pub_key = dh_pub_key; dh->priv_key = dh_priv_key; #endif /* prior to OpenSSL-1.1.0 */ pr_trace_msg(trace_channel, 12, "generating DH key"); if (DH_generate_key(dh) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error generating DH key: %s", proxy_ssh_crypto_get_errors()); DH_free(dh); return -1; } #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) DH_get0_key(dh, &dh_pub_key, NULL); #else dh_pub_key = dh->pub_key; #endif /* prior to OpenSSL-1.1.0 */ if (have_good_dh(dh, dh_pub_key) < 0) { DH_free(dh); continue; } kex->dh = dh; return 0; } errno = EPERM; return -1; } static int read_dh_gex_group(struct proxy_ssh_packet *pkt, struct proxy_ssh_kex *kex) { unsigned char *buf = NULL; uint32_t buflen = 0; const BIGNUM *dh_p, *dh_g; int dh_nbits; buf = pkt->payload; buflen = pkt->payload_len; proxy_ssh_msg_read_mpint(pkt->pool, &buf, &buflen, &dh_p); proxy_ssh_msg_read_mpint(pkt->pool, &buf, &buflen, &dh_g); dh_nbits = BN_num_bits(dh_p); if (kex->dh_gex_min > (uint32_t) dh_nbits || kex->dh_gex_max < (uint32_t) dh_nbits) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "server provided out-of-range DH size %d (requested %lu<%lu<%lu)", dh_nbits, (unsigned long) kex->dh_gex_min, (unsigned long) kex->dh_gex_pref, (unsigned long) kex->dh_gex_max); return -1; } if (create_gex_dh(kex, dh_p, dh_g) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error generating group-exchange DH: %s", strerror(errno)); return -1; } return 0; } static int write_dh_gex_init(struct proxy_ssh_packet *pkt, struct proxy_ssh_kex *kex) { unsigned char *buf, *ptr; uint32_t buflen, bufsz, len = 0; const BIGNUM *dh_pub_key; #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) DH_get0_key(kex->dh, &dh_pub_key, NULL); #else dh_pub_key = kex->dh->pub_key; #endif /* prior to OpenSSL-1.1.0 */ /* XXX Is this buffer large enough? Too large? */ bufsz = buflen = 2048; ptr = buf = palloc(pkt->pool, bufsz); len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEX_DH_GEX_INIT); len += proxy_ssh_msg_write_mpint(&buf, &buflen, dh_pub_key); pkt->payload = ptr; pkt->payload_len = len; return 0; } static int read_dh_gex_reply(struct proxy_ssh_packet *pkt, struct proxy_ssh_kex *kex) { const unsigned char *h; unsigned char *buf, *buf2, *server_hostkey_data = NULL, *sig = NULL; uint32_t buflen, server_hostkey_datalen = 0, siglen = 0, hlen = 0; const BIGNUM *server_pub_key = NULL, *k = NULL; size_t dh_len = 0; int res; buf = pkt->payload; buflen = pkt->payload_len; /* See RFC 4419, Section 3 "Diffie-Hellman Group and Key Exchange" */ proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &server_hostkey_datalen); proxy_ssh_msg_read_data(pkt->pool, &buf, &buflen, server_hostkey_datalen, &server_hostkey_data); res = handle_server_hostkey(pkt->pool, kex->use_hostkey_type, server_hostkey_data, server_hostkey_datalen); if (res < 0) { int xerrno; xerrno = errno; DH_free(kex->dh); kex->dh = NULL; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error handling server host key: %s", strerror(xerrno)); errno = xerrno; return -1; } proxy_ssh_msg_read_mpint(pkt->pool, &buf, &buflen, &server_pub_key); if (have_good_dh(kex->dh, server_pub_key) < 0) { DH_free(kex->dh); kex->dh = NULL; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "invalid server public DH key"); return -1; } /* Compute the shared secret. */ dh_len = DH_size(kex->dh); buf2 = palloc(pkt->pool, dh_len); pr_trace_msg(trace_channel, 12, "computing DH key"); res = DH_compute_key(buf2, server_pub_key, kex->dh); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error computing DH shared secret: %s", proxy_ssh_crypto_get_errors()); DH_free(kex->dh); kex->dh = NULL; return -1; } k = BN_new(); if (BN_bin2bn(buf2, res, (BIGNUM *) k) == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error converting DH shared secret to BN: %s", proxy_ssh_crypto_get_errors()); DH_free(kex->dh); kex->dh = NULL; return -1; } kex->k = k; /* Calculate H */ h = calculate_gex_h(pkt->pool, kex, server_hostkey_data, server_hostkey_datalen, server_pub_key, k, &hlen); if (h == NULL) { BN_clear_free((BIGNUM *) kex->k); kex->k = NULL; DH_free(kex->dh); kex->dh = NULL; return -1; } proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &siglen); proxy_ssh_msg_read_data(pkt->pool, &buf, &buflen, siglen, &sig); /* Verify H */ res = verify_h(pkt->pool, kex, server_hostkey_data, server_hostkey_datalen, sig, siglen, h, hlen); if (res < 0) { BN_clear_free((BIGNUM *) kex->k); kex->k = NULL; DH_free(kex->dh); kex->dh = NULL; return -1; } kex->h = palloc(kex->pool, hlen); kex->hlen = hlen; memcpy((char *) kex->h, h, kex->hlen); /* Save H as the session ID. */ proxy_ssh_session_set_id(session.pool, h, hlen); return 0; } static int handle_kex_dh_gex(struct proxy_ssh_kex *kex, conn_t *conn) { int res; struct proxy_ssh_packet *pkt; pr_trace_msg(trace_channel, 9, "writing DH_GEX_REQUEST message to server"); pkt = proxy_ssh_packet_create(kex_pool); res = write_dh_gex_request(pkt, kex); if (res < 0) { destroy_pool(pkt->pool); PROXY_SSH_DISCONNECT_CONN(conn, PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL); } res = proxy_ssh_packet_write(conn, pkt); if (res < 0) { destroy_pool(pkt->pool); PROXY_SSH_DISCONNECT_CONN(conn, PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL); } destroy_pool(pkt->pool); pr_trace_msg(trace_channel, 9, "reading DH_GEX_GROUP message from server"); pkt = read_kex_packet(kex_pool, kex, conn, PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL, 1, PROXY_SSH_MSG_KEX_DH_GEX_GROUP); res = read_dh_gex_group(pkt, kex); if (res < 0) { destroy_pool(pkt->pool); PROXY_SSH_DISCONNECT_CONN(conn, PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL); } destroy_pool(pkt->pool); pr_trace_msg(trace_channel, 9, "writing DH_GEX_INIT message to server"); pkt = proxy_ssh_packet_create(kex_pool); res = write_dh_gex_init(pkt, kex); if (res < 0) { destroy_pool(pkt->pool); PROXY_SSH_DISCONNECT_CONN(conn, PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL); } res = proxy_ssh_packet_write(conn, pkt); if (res < 0) { destroy_pool(pkt->pool); PROXY_SSH_DISCONNECT_CONN(conn, PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL); } destroy_pool(pkt->pool); pr_trace_msg(trace_channel, 9, "reading DH_GEX_REPLY message from server"); pkt = read_kex_packet(kex_pool, kex, conn, PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL, 1, PROXY_SSH_MSG_KEX_DH_GEX_REPLY); res = read_dh_gex_reply(pkt, kex); if (res < 0) { destroy_pool(pkt->pool); PROXY_SSH_DISCONNECT_CONN(conn, PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL); } destroy_pool(pkt->pool); return 0; } #if defined(PR_USE_OPENSSL_ECC) static int write_ecdh_init(struct proxy_ssh_packet *pkt, struct proxy_ssh_kex *kex) { unsigned char *buf, *ptr; uint32_t buflen, bufsz, len = 0; /* In our ECDH_INIT, send 'e', our client curve public key. */ /* XXX Is this buffer large enough? Too large? */ bufsz = buflen = 2048; ptr = buf = palloc(pkt->pool, bufsz); len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEX_ECDH_INIT); len += proxy_ssh_msg_write_ecpoint(&buf, &buflen, EC_KEY_get0_group(kex->ec), EC_KEY_get0_public_key(kex->ec)); pkt->payload = ptr; pkt->payload_len = len; return 0; } /* This is used to validate the ECDSA parameters we might receive e.g. from * a server. These checks come from Section 3.2.2.1 of 'Standards for * Efficient Cryptography Group, "Elliptic Curve Cryptography", SEC 1, * May 2009: * * http://www.secg.org/download/aid-780/sec1-v2.pdf * * as per RFC 5656 recommendation. */ static int validate_ecdsa_params(const EC_GROUP *group, const EC_POINT *point) { BN_CTX *bn_ctx; BIGNUM *ec_order, *x_coord, *y_coord, *bn_tmp; int coord_nbits, ec_order_nbits; EC_POINT *subgroup_order = NULL; if (EC_METHOD_get_field_type(EC_GROUP_method_of(group)) != NID_X9_62_prime_field) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "ECDSA group is not a prime field, rejecting"); errno = EACCES; return -1; } /* A Q of infinity is unacceptable. */ if (EC_POINT_is_at_infinity(group, point) != 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "ECDSA EC point has infinite value, rejecting"); errno = EACCES; return -1; } /* A BN_CTX is like our pools; we allocate one, use it to get any * number of BIGNUM variables, and only have free up the BN_CTX when * we're done, rather than all of the individual BIGNUMs. */ bn_ctx = BN_CTX_new(); if (bn_ctx == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error allocating BN_CTX: %s", proxy_ssh_crypto_get_errors()); return -1; } BN_CTX_start(bn_ctx); ec_order = BN_CTX_get(bn_ctx); if (ec_order == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error getting new BIGNUM from BN_CTX: %s", proxy_ssh_crypto_get_errors()); BN_CTX_free(bn_ctx); errno = EPERM; return -1; } if (EC_GROUP_get_order(group, ec_order, bn_ctx) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error getting EC group order: %s", proxy_ssh_crypto_get_errors()); BN_CTX_free(bn_ctx); errno = EPERM; return -1; } x_coord = BN_CTX_get(bn_ctx); if (x_coord == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error getting new BIGNUM from BN_CTX: %s", proxy_ssh_crypto_get_errors()); BN_CTX_free(bn_ctx); errno = EPERM; return -1; } y_coord = BN_CTX_get(bn_ctx); if (y_coord == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error getting new BIGNUM from BN_CTX: %s", proxy_ssh_crypto_get_errors()); BN_CTX_free(bn_ctx); errno = EPERM; return -1; } if (EC_POINT_get_affine_coordinates_GFp(group, point, x_coord, y_coord, bn_ctx) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error getting EC point affine coordinates: %s", proxy_ssh_crypto_get_errors()); BN_CTX_free(bn_ctx); errno = EPERM; return -1; } /* Ensure that the following are both true: * * log2(X coord) > log2(EC order)/2 * log2(Y coord) > log2(EC order)/2 */ coord_nbits = BN_num_bits(x_coord); ec_order_nbits = BN_num_bits(ec_order); if (coord_nbits <= (ec_order_nbits / 2)) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "EC public key X coordinate (%d bits) too small (<= %d bits), rejecting", coord_nbits, (ec_order_nbits / 2)); BN_CTX_free(bn_ctx); errno = EACCES; return -1; } coord_nbits = BN_num_bits(y_coord); if (coord_nbits <= (ec_order_nbits / 2)) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "EC public key Y coordinate (%d bits) too small (<= %d bits), rejecting", coord_nbits, (ec_order_nbits / 2)); BN_CTX_free(bn_ctx); errno = EACCES; return -1; } /* Ensure that the following is true: * * subgroup order == infinity */ subgroup_order = EC_POINT_new(group); if (subgroup_order == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error allocating new EC_POINT: %s", proxy_ssh_crypto_get_errors()); BN_CTX_free(bn_ctx); errno = EPERM; return -1; } if (EC_POINT_mul(group, subgroup_order, NULL, point, ec_order, bn_ctx) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error doing EC point multiplication: %s", proxy_ssh_crypto_get_errors()); EC_POINT_free(subgroup_order); BN_CTX_free(bn_ctx); errno = EPERM; return -1; } if (EC_POINT_is_at_infinity(group, subgroup_order) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "EC public key has finite subgroup order, rejecting"); EC_POINT_free(subgroup_order); BN_CTX_free(bn_ctx); errno = EACCES; return -1; } EC_POINT_free(subgroup_order); /* Ensure that the following are both true: * * X < order - 1 * Y < order - 1 */ bn_tmp = BN_CTX_get(bn_ctx); if (bn_tmp == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error getting new BIGNUM from BN_CTX: %s", proxy_ssh_crypto_get_errors()); BN_CTX_free(bn_ctx); errno = EPERM; return -1; } if (BN_sub(bn_tmp, ec_order, BN_value_one()) == 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error subtracting one from EC group order: %s", proxy_ssh_crypto_get_errors()); BN_CTX_free(bn_ctx); errno = EPERM; return -1; } if (BN_cmp(x_coord, bn_tmp) >= 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "EC public key X coordinate too large (>= EC group order - 1), " "rejecting"); BN_CTX_free(bn_ctx); errno = EACCES; return -1; } if (BN_cmp(y_coord, bn_tmp) >= 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "EC public key Y coordinate too large (>= EC group order - 1), " "rejecting"); BN_CTX_free(bn_ctx); errno = EACCES; return -1; } BN_CTX_free(bn_ctx); return 0; } static int read_ecdh_reply(struct proxy_ssh_packet *pkt, struct proxy_ssh_kex *kex) { const unsigned char *h; unsigned char *buf, *buf2, *server_hostkey_data = NULL, *sig = NULL; uint32_t buflen, server_hostkey_datalen = 0, siglen = 0, hlen = 0; const BIGNUM *k = NULL; const EC_GROUP *curve = NULL; EC_POINT *server_point = NULL; size_t ecdh_len = 0; int res; buf = pkt->payload; buflen = pkt->payload_len; /* See RFC 5656, Section 4 "ECDH Key Exchange" */ proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &server_hostkey_datalen); proxy_ssh_msg_read_data(pkt->pool, &buf, &buflen, server_hostkey_datalen, &server_hostkey_data); res = handle_server_hostkey(pkt->pool, kex->use_hostkey_type, server_hostkey_data, server_hostkey_datalen); if (res < 0) { int xerrno; xerrno = errno; EC_KEY_free(kex->ec); kex->ec = NULL; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error handling server host key: %s", strerror(xerrno)); errno = xerrno; return -1; } curve = EC_KEY_get0_group(kex->ec); server_point = EC_POINT_new(curve); if (server_point == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error allocating EC_POINT: %s", proxy_ssh_crypto_get_errors()); EC_KEY_free(kex->ec); kex->ec = NULL; return -1; } proxy_ssh_msg_read_ecpoint(pkt->pool, &buf, &buflen, curve, &server_point); if (server_point == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error reading ECDH_REPLY: %s", strerror(errno)); EC_KEY_free(kex->ec); kex->ec = NULL; return -1; } kex->server_point = server_point; if (validate_ecdsa_params(curve, kex->server_point) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "invalid server ECDH public key (EC point): %s", strerror(errno)); EC_POINT_clear_free(kex->server_point); kex->server_point = NULL; return -1; } /* Compute the shared secret */ ecdh_len = ((EC_GROUP_get_degree(EC_KEY_get0_group(kex->ec)) + 7) / 8); buf2 = palloc(kex_pool, ecdh_len); pr_trace_msg(trace_channel, 12, "computing ECDH key"); res = ECDH_compute_key((unsigned char *) buf2, ecdh_len, kex->server_point, kex->ec, NULL); if (res <= 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error computing ECDH shared secret: %s", proxy_ssh_crypto_get_errors()); pr_memscrub(buf2, ecdh_len); return -1; } if ((size_t) res != ecdh_len) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "computed ECDH shared secret length (%d) does not match needed length " "(%lu), rejecting", res, (unsigned long) ecdh_len); pr_memscrub(buf2, ecdh_len); return -1; } k = BN_new(); if (k == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error allocating new BIGNUM: %s", proxy_ssh_crypto_get_errors()); pr_memscrub(buf2, ecdh_len); return -1; } if (BN_bin2bn(buf2, res, (BIGNUM *) k) == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error converting ECDH shared secret to BN: %s", proxy_ssh_crypto_get_errors()); EC_KEY_free(kex->ec); kex->ec = NULL; pr_memscrub(buf2, ecdh_len); return -1; } kex->k = k; pr_memscrub(buf2, ecdh_len); /* Calculate H */ h = calculate_ecdh_h(pkt->pool, kex, server_hostkey_data, server_hostkey_datalen, k, &hlen); if (h == NULL) { BN_clear_free((BIGNUM *) kex->k); kex->k = NULL; EC_KEY_free(kex->ec); kex->ec = NULL; return -1; } proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &siglen); proxy_ssh_msg_read_data(pkt->pool, &buf, &buflen, siglen, &sig); /* Verify H */ res = verify_h(pkt->pool, kex, server_hostkey_data, server_hostkey_datalen, sig, siglen, h, hlen); if (res < 0) { BN_clear_free((BIGNUM *) kex->k); kex->k = NULL; EC_KEY_free(kex->ec); kex->ec = NULL; return -1; } kex->h = palloc(kex->pool, hlen); kex->hlen = hlen; memcpy((char *) kex->h, h, kex->hlen); /* Save H as the session ID. */ proxy_ssh_session_set_id(session.pool, h, hlen); return 0; } static int handle_kex_ecdh(struct proxy_ssh_kex *kex, conn_t *conn) { int res; struct proxy_ssh_packet *pkt; pr_trace_msg(trace_channel, 9, "writing ECDH_INIT message to server"); pkt = proxy_ssh_packet_create(kex_pool); res = write_ecdh_init(pkt, kex); if (res < 0) { destroy_pool(pkt->pool); PROXY_SSH_DISCONNECT_CONN(conn, PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL); } res = proxy_ssh_packet_write(conn, pkt); if (res < 0) { destroy_pool(pkt->pool); PROXY_SSH_DISCONNECT_CONN(conn, PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL); } destroy_pool(pkt->pool); pr_trace_msg(trace_channel, 9, "reading ECDH_REPLY message from server"); pkt = read_kex_packet(kex_pool, kex, conn, PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL, 1, PROXY_SSH_MSG_KEX_ECDH_REPLY); res = read_ecdh_reply(pkt, kex); if (res < 0) { destroy_pool(pkt->pool); PROXY_SSH_DISCONNECT_CONN(conn, PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL); } destroy_pool(pkt->pool); return 0; } #endif /* PR_USE_OPENSSL_ECC */ static int read_kexrsa_pubkey(struct proxy_ssh_packet *pkt, struct proxy_ssh_kex *kex, pool *hostkey_pool, unsigned char **hostkey_data, uint32_t *hostkey_datalen) { char *key_type = NULL; unsigned char *buf, *server_hostkey_data = NULL, *rsa_pubkey_data = NULL; uint32_t buflen, server_hostkey_datalen = 0, rsa_pubkey_datalen = 0; const BIGNUM *rsa_n = NULL, *rsa_e = NULL; int res; buf = pkt->payload; buflen = pkt->payload_len; /* See RFC 4432 "SSH RSA Key Exchange" */ proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &server_hostkey_datalen); proxy_ssh_msg_read_data(pkt->pool, &buf, &buflen, server_hostkey_datalen, &server_hostkey_data); res = handle_server_hostkey(pkt->pool, kex->use_hostkey_type, server_hostkey_data, server_hostkey_datalen); if (res < 0) { int xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error handling server host key: %s", strerror(xerrno)); errno = xerrno; return -1; } *hostkey_datalen = server_hostkey_datalen; *hostkey_data = palloc(hostkey_pool, server_hostkey_datalen); memcpy(*hostkey_data, server_hostkey_data, server_hostkey_datalen); proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &rsa_pubkey_datalen); proxy_ssh_msg_read_data(pkt->pool, &buf, &buflen, rsa_pubkey_datalen, &rsa_pubkey_data); kex->rsa = RSA_new(); if (kex->rsa == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error allocating new RSA: %s", proxy_ssh_crypto_get_errors()); pr_memscrub(rsa_pubkey_data, rsa_pubkey_datalen); return -1; } buf = rsa_pubkey_data; buflen = rsa_pubkey_datalen; proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &key_type); if (key_type == NULL || strcmp(key_type, "ssh-rsa") != 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unsupported key type received: %s", key_type != NULL ? key_type : "(nil)"); return -1; } proxy_ssh_msg_read_mpint(pkt->pool, &buf, &buflen, &rsa_e); proxy_ssh_msg_read_mpint(pkt->pool, &buf, &buflen, &rsa_n); #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) RSA_set0_key(kex->rsa, (BIGNUM *) rsa_n, (BIGNUM *) rsa_e, NULL); #else kex->rsa->e = rsa_e; kex->rsa->n = rsa_n; #endif /* prior to OpenSSL-1.1.0 */ return 0; } static int write_kexrsa_secret(struct proxy_ssh_packet *pkt, struct proxy_ssh_kex *kex) { BN_CTX *ctx; BIGNUM *k = NULL, *range = NULL, *two = NULL, *bits = NULL; int res, klen = 0, hlen = 0, nbits = 0, plaintext_len = 0, encrypted_len = 0; unsigned char *buf, *ptr, *plaintext = NULL, *encrypted = NULL; uint32_t bufsz, buflen, len = 0; klen = RSA_size(kex->rsa) * 8; hlen = EVP_MD_size(kex->hash) * 8; nbits = klen - (2 * hlen) - 49; two = BN_new(); if (two == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error allocating new BIGNUM: %s", proxy_ssh_crypto_get_errors()); return -1; } res = BN_set_word(two, (BN_ULONG) 2); if (res != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error allocating setting BIGNUM value: %s", proxy_ssh_crypto_get_errors()); BN_free(two); return -1; } bits = BN_new(); if (bits == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error allocating new BIGNUM: %s", proxy_ssh_crypto_get_errors()); BN_free(two); return -1; } res = BN_set_word(bits, (BN_ULONG) nbits); if (res != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error allocating setting BIGNUM value: %s", proxy_ssh_crypto_get_errors()); BN_free(two); BN_free(bits); return -1; } range = BN_new(); if (range == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error allocating new BIGNUM: %s", proxy_ssh_crypto_get_errors()); BN_free(two); BN_free(bits); return -1; } ctx = BN_CTX_new(); if (ctx == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error allocating new BN_CTX: %s", proxy_ssh_crypto_get_errors()); BN_free(two); BN_free(bits); BN_free(range); return -1; } res = BN_exp(range, two, bits, ctx); if (res != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error expontentiating BIGNUM: %s", proxy_ssh_crypto_get_errors()); BN_free(two); BN_free(bits); BN_free(range); BN_CTX_free(ctx); return -1; } k = BN_new(); if (k == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error allocating new BIGNUM: %s", proxy_ssh_crypto_get_errors()); BN_free(two); BN_free(bits); BN_free(range); BN_CTX_free(ctx); return -1; } res = BN_rand_range(k, range); if (res != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error generating random BIGNUM: %s", proxy_ssh_crypto_get_errors()); BN_free(k); BN_free(two); BN_free(bits); BN_free(range); BN_CTX_free(ctx); return -1; } BN_free(two); BN_free(bits); BN_free(range); BN_CTX_free(ctx); kex->k = k; plaintext_len = BN_bn2mpi(kex->k, NULL); plaintext = palloc(pkt->pool, plaintext_len); res = BN_bn2mpi(kex->k, plaintext); if (res != plaintext_len) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error converting RSA shared secret from BN: %s", proxy_ssh_crypto_get_errors()); BN_clear_free((BIGNUM *) kex->k); kex->k = NULL; return -1; } pr_trace_msg(trace_channel, 12, "encrypting RSA shared secret"); encrypted_len = RSA_size(kex->rsa); encrypted = palloc(pkt->pool, encrypted_len); res = RSA_public_encrypt(plaintext_len, plaintext, encrypted, kex->rsa, RSA_PKCS1_OAEP_PADDING); if (res == -1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error encrypting RSA shared secret: %s", proxy_ssh_crypto_get_errors()); pr_memscrub(plaintext, plaintext_len); BN_clear_free((BIGNUM *) kex->k); kex->k = NULL; return -1; } pr_memscrub(plaintext, plaintext_len); /* Store the encrypted RSA for calculating H later. */ kex->rsa_encrypted_len = encrypted_len; kex->rsa_encrypted = palloc(kex_pool, encrypted_len); memcpy(kex->rsa_encrypted, encrypted, encrypted_len); bufsz = buflen = 2048; /* XXX Is this buffer large enough? Too large? */ ptr = buf = palloc(pkt->pool, bufsz); len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEXRSA_SECRET); len += proxy_ssh_msg_write_data(&buf, &buflen, encrypted, encrypted_len, TRUE); pkt->payload = ptr; pkt->payload_len = len; return 0; } static int read_kexrsa_done(struct proxy_ssh_packet *pkt, struct proxy_ssh_kex *kex, unsigned char *server_hostkey_data, uint32_t server_hostkey_datalen) { const unsigned char *h; unsigned char *buf, *sig = NULL; uint32_t buflen, siglen = 0, hlen = 0; int res; buf = pkt->payload; buflen = pkt->payload_len; proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &siglen); proxy_ssh_msg_read_data(pkt->pool, &buf, &buflen, siglen, &sig); /* Calculate H */ h = calculate_kexrsa_h(pkt->pool, kex, server_hostkey_data, server_hostkey_datalen, kex->k, &hlen); if (h == NULL) { BN_clear_free((BIGNUM *) kex->k); kex->k = NULL; return -1; } /* Verify H */ res = verify_h(pkt->pool, kex, server_hostkey_data, server_hostkey_datalen, sig, siglen, h, hlen); if (res < 0) { BN_clear_free((BIGNUM *) kex->k); kex->k = NULL; return -1; } kex->h = palloc(kex->pool, hlen); kex->hlen = hlen; memcpy((char *) kex->h, h, kex->hlen); /* Save H as the session ID. */ proxy_ssh_session_set_id(session.pool, h, hlen); return 0; } static int handle_kex_rsa(struct proxy_ssh_kex *kex, conn_t *conn) { pool *tmp_pool; int res; struct proxy_ssh_packet *pkt; unsigned char *server_hostkey_data = NULL; uint32_t server_hostkey_datalen = 0; tmp_pool = make_sub_pool(session.pool); pr_pool_tag(tmp_pool, "KEXRSA pool"); pr_trace_msg(trace_channel, 9, "reading KEXRSA_PUBKEY message from server"); pkt = read_kex_packet(kex_pool, kex, conn, PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL, 1, PROXY_SSH_MSG_KEXRSA_PUBKEY); res = read_kexrsa_pubkey(pkt, kex, tmp_pool, &server_hostkey_data, &server_hostkey_datalen); if (res < 0) { destroy_pool(pkt->pool); PROXY_SSH_DISCONNECT_CONN(conn, PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL); } destroy_pool(pkt->pool); pr_trace_msg(trace_channel, 9, "writing KEXRSA_SECRET message to server"); pkt = proxy_ssh_packet_create(kex_pool); res = write_kexrsa_secret(pkt, kex); if (res < 0) { destroy_pool(pkt->pool); PROXY_SSH_DISCONNECT_CONN(conn, PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL); } pr_trace_msg(trace_channel, 9, "sending KEXRSA_SECRET message to server"); res = proxy_ssh_packet_write(conn, pkt); if (res < 0) { destroy_pool(pkt->pool); PROXY_SSH_DISCONNECT_CONN(conn, PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL); } destroy_pool(pkt->pool); pr_trace_msg(trace_channel, 9, "reading KEXRSA_DONE message from server"); pkt = read_kex_packet(kex_pool, kex, conn, PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL, 1, PROXY_SSH_MSG_KEXRSA_DONE); res = read_kexrsa_done(pkt, kex, server_hostkey_data, server_hostkey_datalen); if (res < 0) { destroy_pool(pkt->pool); PROXY_SSH_DISCONNECT_CONN(conn, PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL); } destroy_pool(pkt->pool); destroy_pool(tmp_pool); return 0; } #if defined(PR_USE_SODIUM) && defined(HAVE_SHA256_OPENSSL) static int write_curve25519_init(struct proxy_ssh_packet *pkt, struct proxy_ssh_kex *kex) { unsigned char *buf, *ptr; uint32_t buflen, bufsz, len = 0; /* In our Curve25519 ECDH_INIT, send 'e', our client curve25519 public key. */ /* XXX Is this buffer large enough? Too large? */ bufsz = buflen = 2048; ptr = buf = palloc(pkt->pool, bufsz); len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEX_ECDH_INIT); len += proxy_ssh_msg_write_data(&buf, &buflen, kex->client_curve25519_pub_key, CURVE25519_SIZE, TRUE); pkt->payload = ptr; pkt->payload_len = len; return 0; } static int read_curve25519_reply(struct proxy_ssh_packet *pkt, struct proxy_ssh_kex *kex) { const unsigned char *h; unsigned char zero_curve25519[CURVE25519_SIZE]; unsigned char *buf, *buf2, *server_hostkey_data = NULL, *sig = NULL; uint32_t buflen, pub_keylen, server_hostkey_datalen = 0, siglen = 0, hlen = 0; const BIGNUM *k = NULL; int res; buf = pkt->payload; buflen = pkt->payload_len; /* See RFC 5656, Section 4 "ECDH Key Exchange", modified by RFC 8731. */ proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &server_hostkey_datalen); proxy_ssh_msg_read_data(pkt->pool, &buf, &buflen, server_hostkey_datalen, &server_hostkey_data); res = handle_server_hostkey(pkt->pool, kex->use_hostkey_type, server_hostkey_data, server_hostkey_datalen); if (res < 0) { int xerrno; xerrno = errno; pr_memscrub(kex->client_curve25519_priv_key, CURVE25519_SIZE); (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error handling server host key: %s", strerror(xerrno)); errno = xerrno; return -1; } proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &pub_keylen); if (pub_keylen != CURVE25519_SIZE) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "rejecting invalid length (%lu %s, wanted %d) of server Curve25519 key", (unsigned long) pub_keylen, pub_keylen != 1 ? "bytes" : "byte", CURVE25519_SIZE); pr_memscrub(kex->client_curve25519_priv_key, CURVE25519_SIZE); return -1; } proxy_ssh_msg_read_data(kex->pool, &buf, &buflen, pub_keylen, &(kex->server_curve25519_pub_key)); if (kex->server_curve25519_pub_key == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error reading ECDH_REPLY: %s", strerror(errno)); pr_memscrub(kex->client_curve25519_priv_key, CURVE25519_SIZE); return -1; } /* Watch for all-zero public keys, and reject them. */ sodium_memzero(zero_curve25519, CURVE25519_SIZE); if (sodium_memcmp(kex->server_curve25519_pub_key, zero_curve25519, CURVE25519_SIZE) == 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "rejecting invalid (all-zero) server Curve25519 key"); pr_memscrub(kex->client_curve25519_priv_key, CURVE25519_SIZE); return -1; } /* Compute the shared secret */ buf2 = palloc(kex->pool, CURVE25519_SIZE); pr_trace_msg(trace_channel, 12, "computing Curve25519 key"); res = get_curve25519_shared_key((unsigned char *) buf2, kex->server_curve25519_pub_key, kex->client_curve25519_priv_key); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error computing Curve25519 shared secret: %s", strerror(errno)); pr_memscrub(buf2, CURVE25519_SIZE); pr_memscrub(kex->client_curve25519_priv_key, CURVE25519_SIZE); return -1; } k = BN_new(); if (k == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error allocating new BIGNUM: %s", proxy_ssh_crypto_get_errors()); pr_memscrub(buf2, CURVE25519_SIZE); pr_memscrub(kex->client_curve25519_priv_key, CURVE25519_SIZE); return -1; } if (BN_bin2bn(buf2, res, (BIGNUM *) k) == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error converting Curve25519 shared secret to BN: %s", proxy_ssh_crypto_get_errors()); pr_memscrub(buf2, CURVE25519_SIZE); pr_memscrub(kex->client_curve25519_priv_key, CURVE25519_SIZE); return -1; } kex->k = k; pr_memscrub(buf2, CURVE25519_SIZE); /* Calculate H */ h = calculate_curve25519_h(pkt->pool, kex, server_hostkey_data, server_hostkey_datalen, k, &hlen); if (h == NULL) { BN_clear_free((BIGNUM *) kex->k); kex->k = NULL; pr_memscrub(kex->client_curve25519_priv_key, CURVE25519_SIZE); return -1; } proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &siglen); proxy_ssh_msg_read_data(pkt->pool, &buf, &buflen, siglen, &sig); /* Verify H */ res = verify_h(pkt->pool, kex, server_hostkey_data, server_hostkey_datalen, sig, siglen, h, hlen); if (res < 0) { BN_clear_free((BIGNUM *) kex->k); kex->k = NULL; pr_memscrub(kex->client_curve25519_priv_key, CURVE25519_SIZE); return -1; } kex->h = palloc(kex->pool, hlen); kex->hlen = hlen; memcpy((char *) kex->h, h, kex->hlen); /* Save H as the session ID. */ proxy_ssh_session_set_id(session.pool, h, hlen); /* We no longer need the private key. */ pr_memscrub(kex->client_curve25519_priv_key, CURVE25519_SIZE); return 0; } static int handle_kex_curve25519(struct proxy_ssh_kex *kex, conn_t *conn) { int res; struct proxy_ssh_packet *pkt; pr_trace_msg(trace_channel, 9, "writing ECDH_INIT message to server"); pkt = proxy_ssh_packet_create(kex_pool); res = write_curve25519_init(pkt, kex); if (res < 0) { destroy_pool(pkt->pool); PROXY_SSH_DISCONNECT_CONN(conn, PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL); } res = proxy_ssh_packet_write(conn, pkt); if (res < 0) { destroy_pool(pkt->pool); PROXY_SSH_DISCONNECT_CONN(conn, PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL); } destroy_pool(pkt->pool); pr_trace_msg(trace_channel, 9, "reading ECDH_REPLY message from server"); pkt = read_kex_packet(kex_pool, kex, conn, PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL, 1, PROXY_SSH_MSG_KEX_ECDH_REPLY); res = read_curve25519_reply(pkt, kex); if (res < 0) { destroy_pool(pkt->pool); PROXY_SSH_DISCONNECT_CONN(conn, PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL); } destroy_pool(pkt->pool); return 0; } #endif /* PR_USE_SODIUM and HAVE_SHA256_OPENSSL */ #if defined(HAVE_X448_OPENSSL) && defined(HAVE_SHA512_OPENSSL) static int write_curve448_init(struct proxy_ssh_packet *pkt, struct proxy_ssh_kex *kex) { unsigned char *buf, *ptr; uint32_t buflen, bufsz, len = 0; /* In our Curve448 ECDH_INIT, send 'e', our client curve448 public key. */ /* XXX Is this buffer large enough? Too large? */ bufsz = buflen = 2048; ptr = buf = palloc(pkt->pool, bufsz); len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEX_ECDH_INIT); len += proxy_ssh_msg_write_data(&buf, &buflen, kex->client_curve448_pub_key, CURVE448_SIZE, TRUE); pkt->payload = ptr; pkt->payload_len = len; return 0; } static int read_curve448_reply(struct proxy_ssh_packet *pkt, struct proxy_ssh_kex *kex) { const unsigned char *h; unsigned char zero_curve448[CURVE448_SIZE]; unsigned char *buf, *buf2, *server_hostkey_data = NULL, *sig = NULL; uint32_t buflen, pub_keylen, server_hostkey_datalen = 0, siglen = 0, hlen = 0; const BIGNUM *k = NULL; int res; buf = pkt->payload; buflen = pkt->payload_len; /* See RFC 5656, Section 4 "ECDH Key Exchange", modified by RFC 8731. */ proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &server_hostkey_datalen); proxy_ssh_msg_read_data(pkt->pool, &buf, &buflen, server_hostkey_datalen, &server_hostkey_data); res = handle_server_hostkey(pkt->pool, kex->use_hostkey_type, server_hostkey_data, server_hostkey_datalen); if (res < 0) { int xerrno; xerrno = errno; pr_memscrub(kex->client_curve448_priv_key, CURVE448_SIZE); (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error handling server host key: %s", strerror(xerrno)); errno = xerrno; return -1; } proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &pub_keylen); if (pub_keylen != CURVE448_SIZE) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "rejecting invalid length (%lu %s, wanted %d) of server Curve448 key", (unsigned long) pub_keylen, pub_keylen != 1 ? "bytes" : "byte", CURVE448_SIZE); pr_memscrub(kex->client_curve448_priv_key, CURVE448_SIZE); return -1; } /* Note that we use `kex->pool` here, since we are storing the key in the * `kex` structure. */ proxy_ssh_msg_read_data(kex->pool, &buf, &buflen, pub_keylen, &(kex->server_curve448_pub_key)); if (kex->server_curve448_pub_key == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error reading ECDH_REPLY: %s", strerror(errno)); pr_memscrub(kex->client_curve448_priv_key, CURVE448_SIZE); return -1; } /* Watch for all-zero public keys, and reject them. */ memset(zero_curve448, '\0', sizeof(zero_curve448)); if (memcmp(kex->server_curve448_pub_key, zero_curve448, CURVE448_SIZE) == 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "rejecting invalid (all-zero) server Curve448 key"); pr_memscrub(kex->client_curve448_priv_key, CURVE448_SIZE); return -1; } /* Compute the shared secret */ buf2 = palloc(kex->pool, CURVE448_SIZE); pr_trace_msg(trace_channel, 12, "computing Curve448 key"); res = get_curve448_shared_key((unsigned char *) buf2, kex->server_curve448_pub_key, kex->client_curve448_priv_key); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error computing Curve448 shared secret: %s", strerror(errno)); pr_memscrub(buf2, CURVE448_SIZE); pr_memscrub(kex->client_curve448_priv_key, CURVE448_SIZE); return -1; } k = BN_new(); if (k == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error allocating new BIGNUM: %s", proxy_ssh_crypto_get_errors()); pr_memscrub(buf2, CURVE448_SIZE); pr_memscrub(kex->client_curve448_priv_key, CURVE448_SIZE); return -1; } if (BN_bin2bn(buf2, res, (BIGNUM *) k) == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error converting Curve448 shared secret to BN: %s", proxy_ssh_crypto_get_errors()); pr_memscrub(buf2, CURVE448_SIZE); pr_memscrub(kex->client_curve448_priv_key, CURVE448_SIZE); return -1; } kex->k = k; pr_memscrub(buf2, CURVE448_SIZE); /* Calculate H */ h = calculate_curve448_h(pkt->pool, kex, server_hostkey_data, server_hostkey_datalen, k, &hlen); if (h == NULL) { BN_clear_free((BIGNUM *) kex->k); kex->k = NULL; pr_memscrub(kex->client_curve448_priv_key, CURVE448_SIZE); return -1; } proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &siglen); proxy_ssh_msg_read_data(pkt->pool, &buf, &buflen, siglen, &sig); /* Verify H */ res = verify_h(pkt->pool, kex, server_hostkey_data, server_hostkey_datalen, sig, siglen, h, hlen); if (res < 0) { BN_clear_free((BIGNUM *) kex->k); kex->k = NULL; pr_memscrub(kex->client_curve448_priv_key, CURVE448_SIZE); return -1; } kex->h = palloc(kex->pool, hlen); kex->hlen = hlen; memcpy((char *) kex->h, h, kex->hlen); /* Save H as the session ID. */ proxy_ssh_session_set_id(session.pool, h, hlen); /* We no longer need the private key. */ pr_memscrub(kex->client_curve448_priv_key, CURVE448_SIZE); return 0; } static int handle_kex_curve448(struct proxy_ssh_kex *kex, conn_t *conn) { int res; struct proxy_ssh_packet *pkt; pr_trace_msg(trace_channel, 9, "writing ECDH_INIT message to server"); pkt = proxy_ssh_packet_create(kex_pool); res = write_curve448_init(pkt, kex); if (res < 0) { destroy_pool(pkt->pool); PROXY_SSH_DISCONNECT_CONN(conn, PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL); } res = proxy_ssh_packet_write(conn, pkt); if (res < 0) { destroy_pool(pkt->pool); PROXY_SSH_DISCONNECT_CONN(conn, PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL); } destroy_pool(pkt->pool); pr_trace_msg(trace_channel, 9, "reading ECDH_REPLY message from server"); pkt = read_kex_packet(kex_pool, kex, conn, PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL, 1, PROXY_SSH_MSG_KEX_ECDH_REPLY); res = read_curve448_reply(pkt, kex); if (res < 0) { destroy_pool(pkt->pool); PROXY_SSH_DISCONNECT_CONN(conn, PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL); } destroy_pool(pkt->pool); return 0; } #endif /* HAVE_X448_OPENSSL and HAVE_SHA512_OPENSSL */ static int run_kex(struct proxy_ssh_kex *kex, conn_t *conn) { const char *algo; algo = kex->session_names->kex_algo; if (strcmp(algo, "diffie-hellman-group1-sha1") == 0 || strcmp(algo, "diffie-hellman-group14-sha1") == 0 || strcmp(algo, "diffie-hellman-group14-sha256") == 0 || strcmp(algo, "diffie-hellman-group16-sha512") == 0 || strcmp(algo, "diffie-hellman-group18-sha512") == 0) { return handle_kex_dh(kex, conn); } else if (strcmp(algo, "diffie-hellman-group-exchange-sha1") == 0) { return handle_kex_dh_gex(kex, conn); } else if (strcmp(algo, "rsa1024-sha1") == 0) { return handle_kex_rsa(kex, conn); #if ((OPENSSL_VERSION_NUMBER > 0x000907000L && defined(OPENSSL_FIPS)) || \ (OPENSSL_VERSION_NUMBER > 0x000908000L)) && \ defined(HAVE_SHA256_OPENSSL) } else if (strcmp(algo, "diffie-hellman-group-exchange-sha256") == 0) { return handle_kex_dh_gex(kex, conn); } else if (strcmp(algo, "rsa2048-sha256") == 0) { return handle_kex_rsa(kex, conn); #endif #if defined(PR_USE_OPENSSL_ECC) } else if (strcmp(algo, "ecdh-sha2-nistp256") == 0 || strcmp(algo, "ecdh-sha2-nistp384") == 0 || strcmp(algo, "ecdh-sha2-nistp521") == 0) { return handle_kex_ecdh(kex, conn); #endif /* PR_USE_OPENSSL_ECC */ #if defined(PR_USE_SODIUM) && defined(HAVE_SHA256_OPENSSL) } else if (strcmp(algo, "curve25519-sha256") == 0 || strcmp(algo, "curve25519-sha256@libssh.org") == 0) { return handle_kex_curve25519(kex, conn); #endif /* PR_USE_SODIUM and HAVE_SHA256_OPENSSL */ #if defined(HAVE_X448_OPENSSL) && defined(HAVE_SHA512_OPENSSL) } else if (strcmp(algo, "curve448-sha512") == 0) { return handle_kex_curve448(kex, conn); #endif /* HAVE_X448_OPENSSL and HAVE_SHA512_OPENSSL */ } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unsupported key exchange algorithm '%s'", algo); errno = EINVAL; return -1; } int proxy_ssh_kex_handle(struct proxy_ssh_packet *pkt, const struct proxy_session *proxy_sess) { int correct_guess = TRUE, res, sent_newkeys = FALSE; char msg_type; struct proxy_ssh_kex *kex; /* We may already have a kex structure, either from the client * initial connect (kex_first_kex not null), or because we * are in a server-initiated rekeying (kex_rekey_kex not null). */ if (kex_first_kex != NULL) { kex = kex_first_kex; /* We need to assign the client/server versions, which this struct * will not have. */ kex->client_version = kex_client_version; kex->server_version = kex_server_version; } else if (kex_rekey_kex != NULL) { kex = kex_rekey_kex; } else { kex = create_kex(kex_pool); } /* The packet we are given is guaranteed to be a KEXINIT packet. */ pr_trace_msg(trace_channel, 9, "reading KEXINIT message from server"); res = read_kexinit(pkt, kex); if (res < 0) { destroy_kex(kex); destroy_pool(pkt->pool); return -1; } destroy_pool(pkt->pool); pr_trace_msg(trace_channel, 9, "determining shared algorithms for SSH session"); if (get_session_names(kex, &correct_guess) < 0) { destroy_kex(kex); return -1; } if (use_strict_kex == TRUE && kex_done_first_kex == FALSE) { uint32_t server_seqno; server_seqno = proxy_ssh_packet_get_server_seqno(); if (server_seqno != 1) { /* Receiving any messages other than a KEXINIT as the first server * message indicates the possibility of the Terrapin attack being * conducted (Issue 257). Thus we disconnect the server in such * cases. */ (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "'strict KEX' violation, as KEXINIT was not the first message; disconnecting"); destroy_kex(kex); PROXY_SSH_DISCONNECT_CONN(proxy_sess->backend_ctrl_conn, PROXY_SSH_DISCONNECT_BY_APPLICATION, NULL); } } /* Once we have received the server KEXINIT message, we can compare what we * want to send against what we already received from the server. * * If the server said that it was going to send a "guess" KEX packet, * and we determine that its key exchange guess matches what we would have * sent in our KEXINIT, then we proceed on with reading and handling that * guess packet. If not, we ignore that packet, and proceed. */ if (kex->first_kex_follows == FALSE) { /* No guess packet sent; send our KEXINIT as normal (as long as we are * not in a server-initiated rekeying). */ if (kex_sent_kexinit == FALSE) { pkt = proxy_ssh_packet_create(kex_pool); res = write_kexinit(pkt, kex); if (res < 0) { destroy_kex(kex); destroy_pool(pkt->pool); return -1; } pr_trace_msg(trace_channel, 9, "sending KEXINIT message to server"); res = proxy_ssh_packet_write(proxy_sess->backend_ctrl_conn, pkt); if (res < 0) { destroy_kex(kex); destroy_pool(pkt->pool); return res; } kex_sent_kexinit = TRUE; destroy_pool(pkt->pool); } } else { /* If the server sent a guess kex packet, but that guess was incorrect, * then we need to consume and silently ignore that packet, and proceed * as normal. */ if (correct_guess == FALSE) { pr_trace_msg(trace_channel, 3, "server sent incorrect key exchange " "guess, ignoring guess packet"); pkt = read_kex_packet(kex_pool, kex, proxy_sess->backend_ctrl_conn, PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, &msg_type, 3, PROXY_SSH_MSG_KEX_DH_INIT, PROXY_SSH_MSG_KEX_DH_GEX_INIT, PROXY_SSH_MSG_KEX_ECDH_INIT); pr_trace_msg(trace_channel, 3, "ignored %s (%d) guess message sent by server", proxy_ssh_packet_get_msg_type_desc(msg_type), msg_type); destroy_pool(pkt->pool); if (kex_sent_kexinit == FALSE) { pkt = proxy_ssh_packet_create(kex_pool); res = write_kexinit(pkt, kex); if (res < 0) { destroy_kex(kex); destroy_pool(pkt->pool); return -1; } pr_trace_msg(trace_channel, 9, "sending KEXINIT message to server"); res = proxy_ssh_packet_write(proxy_sess->backend_ctrl_conn, pkt); if (res < 0) { destroy_kex(kex); destroy_pool(pkt->pool); return res; } kex_sent_kexinit = TRUE; destroy_pool(pkt->pool); } } if (kex_sent_kexinit == FALSE) { pkt = proxy_ssh_packet_create(kex_pool); res = write_kexinit(pkt, kex); if (res < 0) { destroy_kex(kex); destroy_pool(pkt->pool); return -1; } pr_trace_msg(trace_channel, 9, "sending KEXINIT message to server"); res = proxy_ssh_packet_write(proxy_sess->backend_ctrl_conn, pkt); if (res < 0) { destroy_kex(kex); destroy_pool(pkt->pool); return res; } kex_sent_kexinit = TRUE; destroy_pool(pkt->pool); } } if (run_kex(kex, proxy_sess->backend_ctrl_conn) < 0) { destroy_kex(kex); return -1; } if (!proxy_ssh_interop_supports_feature(PROXY_SSH_FEAT_PESSIMISTIC_NEWKEYS)) { pr_trace_msg(trace_channel, 9, "sending NEWKEYS message to server"); /* Send our NEWKEYS reply. */ pkt = proxy_ssh_packet_create(kex_pool); res = write_newkeys_reply(pkt); if (res < 0) { destroy_kex(kex); destroy_pool(pkt->pool); return -1; } res = proxy_ssh_packet_write(proxy_sess->backend_ctrl_conn, pkt); if (res < 0) { destroy_kex(kex); destroy_pool(pkt->pool); return -1; } destroy_pool(pkt->pool); sent_newkeys = TRUE; } pkt = read_kex_packet(kex_pool, kex, proxy_sess->backend_ctrl_conn, PROXY_SSH_DISCONNECT_PROTOCOL_ERROR, NULL, 1, PROXY_SSH_MSG_NEWKEYS); /* If we didn't send our NEWKEYS message earlier, do it now. */ if (sent_newkeys == FALSE) { struct proxy_ssh_packet *pkt2; pr_trace_msg(trace_channel, 9, "sending NEWKEYS message to server"); /* Send our NEWKEYS reply. */ pkt2 = proxy_ssh_packet_create(kex_pool); res = write_newkeys_reply(pkt2); if (res < 0) { destroy_kex(kex); destroy_pool(pkt2->pool); return -1; } res = proxy_ssh_packet_write(proxy_sess->backend_ctrl_conn, pkt2); if (res < 0) { destroy_kex(kex); destroy_pool(pkt2->pool); return -1; } destroy_pool(pkt2->pool); sent_newkeys = TRUE; } if (use_strict_kex == TRUE) { proxy_ssh_packet_reset_client_seqno(); proxy_ssh_packet_reset_server_seqno(); } /* Last but certainly not least, set up the keys for encryption and * authentication, based on H and K. */ pr_trace_msg(trace_channel, 9, "setting session keys"); if (set_session_keys(kex) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error setting session keys, disconnecting"); destroy_kex(kex); PROXY_SSH_DISCONNECT_CONN(proxy_sess->backend_ctrl_conn, PROXY_SSH_DISCONNECT_BY_APPLICATION, NULL); } /* We've now completed our KEX, possibly our first. */ kex_done_first_kex = TRUE; destroy_pool(pkt->pool); destroy_kex(kex); return 0; } int proxy_ssh_kex_sess_free(void) { kex_ds = NULL; kex_verify_hostkeys = FALSE; return 0; } int proxy_ssh_kex_sess_init(pool *p, struct proxy_ssh_datastore *ds, int verify_hostkeys) { (void) p; kex_ds = ds; kex_verify_hostkeys = verify_hostkeys; return 0; } int proxy_ssh_kex_free(void) { struct proxy_ssh_kex *first_kex, *rekey_kex; /* destroy_kex() will set the kex_first_kex AND kex_rekey_kex pointers to * null, so we need to keep our own copies of those pointers here. */ first_kex = kex_first_kex; rekey_kex = kex_rekey_kex; if (first_kex != NULL) { destroy_kex(first_kex); } if (rekey_kex != NULL) { destroy_kex(rekey_kex); } if (kex_pool != NULL) { destroy_pool(kex_pool); kex_pool = NULL; } return 0; } int proxy_ssh_kex_init(pool *p, const char *client_version, const char *server_version) { /* If we are called with client_version and server_version both NULL, * then we're setting up for a rekey. We can destroy/create the Kex * pool in that case. But not otherwise. */ if (client_version == NULL && server_version == NULL) { if (kex_pool != NULL) { destroy_pool(kex_pool); kex_pool = NULL; } } if (kex_pool == NULL) { kex_pool = make_sub_pool(p); pr_pool_tag(kex_pool, "Proxy SSH Kex Pool"); } /* Save the client and server versions, the first time through. They * will be used for any future rekey KEXINIT exchanges. */ if (client_version != NULL && kex_client_version == NULL) { kex_client_version = pstrdup(proxy_pool, client_version); } if (server_version != NULL && kex_server_version == NULL) { kex_server_version = pstrdup(proxy_pool, server_version); } if (client_version == NULL && server_version == NULL) { pr_trace_msg(trace_channel, 19, "preparing for rekey"); kex_rekey_kex = create_kex(kex_pool); kex_sent_kexinit = FALSE; } return 0; } int proxy_ssh_kex_send_first_kexinit(pool *p, const struct proxy_session *proxy_sess) { struct proxy_ssh_packet *pkt; int res; if (kex_pool == NULL) { kex_pool = make_sub_pool(p); pr_pool_tag(kex_pool, "Proxy SSH Kex Pool"); } /* We have just connected to the server. We want to send our version * ID string _and_ the KEXINIT in the same TCP packet, and save a * TCP round trip (one TCP ACK for both messages, rather than one ACK * per message). The packet API will automatically send the version * ID string along with the first packet we send; we just have to * send a packet, and the KEXINIT is the first one in the protocol. */ kex_first_kex = create_kex(kex_pool); pkt = proxy_ssh_packet_create(kex_pool); res = write_kexinit(pkt, kex_first_kex); if (res < 0) { destroy_kex(kex_first_kex); destroy_pool(pkt->pool); return -1; } pr_trace_msg(trace_channel, 9, "sending KEXINIT message to server"); res = proxy_ssh_packet_write(proxy_sess->backend_ctrl_conn, pkt); if (res < 0) { destroy_kex(kex_first_kex); destroy_pool(pkt->pool); return -1; } kex_sent_kexinit = TRUE; destroy_pool(pkt->pool); return 0; } #endif /* PR_USE_OPENSSL */ proftpd-mod_proxy-0.9.5/lib/proxy/ssh/keys.c000066400000000000000000004735211475737016700211210ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH key mgmt (keys) * Copyright (c) 2021-2023 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "proxy/ssh/msg.h" #include "proxy/ssh/packet.h" #include "proxy/ssh/crypto.h" #include "proxy/ssh/keys.h" #include "proxy/ssh/agent.h" #include "proxy/ssh/interop.h" #include "proxy/ssh/bcrypt.h" #if defined(PR_USE_OPENSSL) #if defined(PR_USE_SODIUM) # include #endif /* PR_USE_SODIUM */ #include #include #include #if defined(HAVE_X448_OPENSSL) && defined(HAVE_SHA512_OPENSSL) # define CURVE448_SIZE 56 #endif /* HAVE_X448_OPENSSL and HAVE_SHA512_OPENSSL */ extern xaset_t *server_list; /* Note: Should this size be made bigger, in light of larger hostkeys? */ #define PROXY_SSH_DEFAULT_HOSTKEY_SZ 4096 #define PROXY_SSH_MAX_SIG_SZ 4096 struct proxy_ssh_hostkey { enum proxy_ssh_key_type_e key_type; EVP_PKEY *pkey; /* Non-OpenSSL keys */ unsigned char *ed25519_public_key; unsigned long long ed25519_public_keylen; unsigned char *ed25519_secret_key; unsigned long long ed25519_secret_keylen; unsigned char *ed448_public_key; unsigned long long ed448_public_keylen; unsigned char *ed448_secret_key; unsigned long long ed448_secret_keylen; const unsigned char *key_data; uint32_t key_datalen; /* This will usually not be null; if the key was obtained from a local * file, this will point to that file. */ const char *file_path; /* This will usually be null; if the key was obtained from an agent, * this point will point to the Unix domain socket to use for talking * to that agent, e.g. for data signing requests. */ const char *agent_path; }; static struct proxy_ssh_hostkey *dsa_hostkey = NULL; static struct proxy_ssh_hostkey *rsa_hostkey = NULL; #if defined(PR_USE_OPENSSL_ECC) static struct proxy_ssh_hostkey *ecdsa256_hostkey = NULL; static struct proxy_ssh_hostkey *ecdsa384_hostkey = NULL; static struct proxy_ssh_hostkey *ecdsa521_hostkey = NULL; #endif /* PR_USE_OPENSSL_ECC */ #if defined(PR_USE_SODIUM) static struct proxy_ssh_hostkey *ed25519_hostkey = NULL; #endif /* PR_USE_SODIUM */ #if defined(HAVE_X448_OPENSSL) static struct proxy_ssh_hostkey *ed448_hostkey = NULL; #endif /* HAVE_X448_OPENSSL */ static const char *passphrase_provider = NULL; struct proxy_ssh_pkey { struct proxy_ssh_pkey *next; size_t pkeysz; char *client_pkey; void *client_pkey_ptr; server_rec *server; }; #define PROXY_SSH_PASSPHRASE_TIMEOUT 10 static struct proxy_ssh_pkey *pkey_list = NULL; static unsigned int npkeys = 0; static struct proxy_ssh_pkey *client_pkey = NULL; struct proxy_ssh_pkey_data { server_rec *s; const char *path; char *buf; size_t buflen, bufsz; const char *prompt; }; /* Public key files start with "BEGIN ... PUBLIC KEY" and "END ... PUBLIC KEY" * lines. Note that the "..." can be multiple different values ("RSA", "SSH2", * etc). */ #define PROXY_SSH_PUBLICKEY_BEGIN "BEGIN PUBLIC KEY" #define PROXY_SSH_PUBLICKEY_BEGIN_LEN (sizeof(PROXY_SSH_PUBLICKEY_BEGIN) - 1) #define PROXY_SSH_PUBLICKEY_END "END PUBLIC KEY" #define PROXY_SSH_PUBLICKEY_END_LEN (sizeof(PROXY_SSH_PUBLICKEY_BEGIN) - 1) /* OpenSSH's homegrown private key file format. * * See the PROTOCOL.key file in the OpenSSH source distribution for details * on their homegrown private key format. See also the implementations in * sskey.c#sshkey_private_to_blob2 (for writing private keys) and * sshkey.c#sshkey_parse_private2 (for reading private keys). The values * for different encryption ciphers are in the `ciphers[]` table in cipher.c. */ #define PROXY_SSH_OPENSSH_BEGIN "-----BEGIN OPENSSH PRIVATE KEY-----\n" #define PROXY_SSH_OPENSSH_END "-----END OPENSSH PRIVATE KEY-----\n" #define PROXY_SSH_OPENSSH_BEGIN_LEN (sizeof(PROXY_SSH_OPENSSH_BEGIN) - 1) #define PROXY_SSH_OPENSSH_END_LEN (sizeof(PROXY_SSH_OPENSSH_END) - 1) #define PROXY_SSH_OPENSSH_KDFNAME "bcrypt" #define PROXY_SSH_OPENSSH_MAGIC "openssh-key-v1" /* Encryption cipher info. */ struct openssh_cipher { const char *algo; uint32_t blocksz; uint32_t key_len; uint32_t iv_len; uint32_t auth_len; const EVP_CIPHER *cipher; const EVP_CIPHER *(*get_cipher)(void); }; static struct openssh_cipher ciphers[] = { { "none", 8, 0, 0, 0, NULL, EVP_enc_null }, { "aes256-cbc", 16, 32, 16, 0, NULL, EVP_aes_256_cbc }, #if defined(HAVE_EVP_AES_256_CTR_OPENSSL) { "aes256-ctr", 16, 32, 16, 0, NULL, EVP_aes_256_ctr }, #else { "aes256-ctr", 16, 32, 16, 0, NULL, NULL }, #endif /* HAVE_EVP_AES_256_CTR_OPENSSL */ { NULL, 0, 0, 0, 0, NULL, NULL } }; static int handle_ed448_hostkey(pool *p, const unsigned char *key_data, uint32_t key_datalen, const char *file_path); static int read_openssh_private_key(pool *p, const char *path, int fd, const char *passphrase, enum proxy_ssh_key_type_e *key_type, EVP_PKEY **pkey, unsigned char **key, uint32_t *keylen); static const char *trace_channel = "proxy.ssh.keys"; static void prepare_provider_fds(int stdout_fd, int stderr_fd) { long nfiles = 0; register unsigned int i = 0; struct rlimit rlim; if (stdout_fd != STDOUT_FILENO) { if (dup2(stdout_fd, STDOUT_FILENO) < 0) { pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": error duping fd %d to stdout: %s", stdout_fd, strerror(errno)); } (void) close(stdout_fd); } if (stderr_fd != STDERR_FILENO) { if (dup2(stderr_fd, STDERR_FILENO) < 0) { pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": error duping fd %d to stderr: %s", stderr_fd, strerror(errno)); } (void) close(stderr_fd); } /* Make sure not to pass on open file descriptors. For stdout and stderr, * we dup some pipes, so that we can capture what the command may write * to stdout or stderr. The stderr output will be logged to the SFTPLog. * * First, use getrlimit() to obtain the maximum number of open files * for this process -- then close that number. */ #if defined(RLIMIT_NOFILE) || defined(RLIMIT_OFILE) # if defined(RLIMIT_NOFILE) if (getrlimit(RLIMIT_NOFILE, &rlim) < 0) { # elif defined(RLIMIT_OFILE) if (getrlimit(RLIMIT_OFILE, &rlim) < 0) { # endif /* Ignore ENOSYS (and EPERM, since some libc's use this as ENOSYS). */ if (errno != ENOSYS && errno != EPERM) { pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": getrlimit error: %s", strerror(errno)); } /* Pick some arbitrary high number. */ nfiles = 255; } else { nfiles = (unsigned long) rlim.rlim_max; } #else /* no RLIMIT_NOFILE or RLIMIT_OFILE */ nfiles = 255; #endif /* Appears that on some platforms (e.g. Solaris, Mac OSX), having too * high of an fd value can lead to undesirable behavior for some reason. * Need to track down why; the behavior I saw was the inability of * select() to work properly on the stdout/stderr fds attached to the * exec'd script. */ if (nfiles > 255) { nfiles = 255; } if (nfiles < 0) { /* Yes, using a long for the nfiles variable is not quite kosher; it should * be an unsigned type, otherwise a large limit (say, RLIMIT_INFINITY) * might overflow the data type. In that case, though, we want to know * about it -- and using a signed type, we will know if the overflowed * value is a negative number. Chances are we do NOT want to be closing * fds whose value is as high as they can possibly get; that's too many * fds to iterate over. Long story short, using a long int is just fine. */ nfiles = 255; } /* Close the "non-standard" file descriptors. */ for (i = 3; i < nfiles; i++) { pr_signals_handle(); (void) close(i); } return; } static void prepare_provider_pipes(int *stdout_pipe, int *stderr_pipe) { if (pipe(stdout_pipe) < 0) { pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": error opening stdout pipe: %s", strerror(errno)); stdout_pipe[0] = -1; stdout_pipe[1] = STDOUT_FILENO; } else { if (fcntl(stdout_pipe[0], F_SETFD, FD_CLOEXEC) < 0) { pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": error setting close-on-exec flag on stdout pipe read fd: %s", strerror(errno)); } if (fcntl(stdout_pipe[1], F_SETFD, 0) < 0) { pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": error setting close-on-exec flag on stdout pipe write fd: %s", strerror(errno)); } } if (pipe(stderr_pipe) < 0) { pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": error opening stderr pipe: %s", strerror(errno)); stderr_pipe[0] = -1; stderr_pipe[1] = STDERR_FILENO; } else { if (fcntl(stderr_pipe[0], F_SETFD, FD_CLOEXEC) < 0) { pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": error setting close-on-exec flag on stderr pipe read fd: %s", strerror(errno)); } if (fcntl(stderr_pipe[1], F_SETFD, 0) < 0) { pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": error setting close-on-exec flag on stderr pipe write fd: %s", strerror(errno)); } } } static int exec_passphrase_provider(server_rec *s, char *buf, int buflen, const char *path) { pid_t pid; int status; int stdout_pipe[2], stderr_pipe[2]; struct sigaction sa_ignore, sa_intr, sa_quit; sigset_t set_chldmask, set_save; /* Prepare signal dispositions. */ sa_ignore.sa_handler = SIG_IGN; sigemptyset(&sa_ignore.sa_mask); sa_ignore.sa_flags = 0; if (sigaction(SIGINT, &sa_ignore, &sa_intr) < 0) { return -1; } if (sigaction(SIGQUIT, &sa_ignore, &sa_quit) < 0) { return -1; } sigemptyset(&set_chldmask); sigaddset(&set_chldmask, SIGCHLD); if (sigprocmask(SIG_BLOCK, &set_chldmask, &set_save) < 0) { return -1; } prepare_provider_pipes(stdout_pipe, stderr_pipe); pid = fork(); if (pid < 0) { int xerrno = errno; pr_log_pri(PR_LOG_ALERT, MOD_PROXY_VERSION ": error: unable to fork: %s", strerror(xerrno)); errno = xerrno; status = -1; } else if (pid == 0) { char nbuf[32]; pool *tmp_pool; char *stdin_argv[4]; /* Child process */ session.pid = getpid(); /* Note: there is no need to clean up this temporary pool, as we've * forked. If the exec call succeeds, this child process will exit * normally, and its process space recovered by the OS. If the exec * call fails, we still exit, and the process space is recovered by * the OS. Either way, the memory will be cleaned up without need for * us to do it explicitly (unless one wanted to be pedantic about it, * of course). */ tmp_pool = make_sub_pool(s->pool); /* Restore previous signal actions. */ sigaction(SIGINT, &sa_intr, NULL); sigaction(SIGQUIT, &sa_quit, NULL); sigprocmask(SIG_SETMASK, &set_save, NULL); stdin_argv[0] = pstrdup(tmp_pool, passphrase_provider); memset(nbuf, '\0', sizeof(nbuf)); pr_snprintf(nbuf, sizeof(nbuf)-1, "%u", (unsigned int) s->ServerPort); nbuf[sizeof(nbuf)-1] = '\0'; stdin_argv[1] = pstrcat(tmp_pool, s->ServerName, ":", nbuf, NULL); stdin_argv[2] = pstrdup(tmp_pool, path); stdin_argv[3] = NULL; PRIVS_ROOT pr_log_debug(DEBUG6, MOD_PROXY_VERSION ": executing '%s' with uid %lu (euid %lu), gid %lu (egid %lu)", passphrase_provider, (unsigned long) getuid(), (unsigned long) geteuid(), (unsigned long) getgid(), (unsigned long) getegid()); /* Prepare the file descriptors that the process will inherit. */ prepare_provider_fds(stdout_pipe[1], stderr_pipe[1]); errno = 0; execv(passphrase_provider, stdin_argv); /* Since all previous file descriptors (including those for log files) * have been closed, and root privs have been revoked, there's little * chance of directing a message of execv() failure to proftpd's log * files. execv() only returns if there's an error; the only way we * can signal this to the waiting parent process is to exit with a * non-zero value (the value of errno will do nicely). */ exit(errno); } else { int res; int maxfd = -1, fds, send_sigterm = 1; fd_set readfds; time_t start_time = time(NULL); struct timeval tv; /* Parent process */ close(stdout_pipe[1]); stdout_pipe[1] = -1; close(stderr_pipe[1]); stderr_pipe[1] = -1; if (stdout_pipe[0] > maxfd) { maxfd = stdout_pipe[0]; } if (stderr_pipe[0] > maxfd) { maxfd = stderr_pipe[0]; } res = waitpid(pid, &status, WNOHANG); while (res <= 0) { if (res < 0) { if (errno != EINTR) { pr_log_debug(DEBUG2, MOD_PROXY_VERSION ": passphrase provider error: unable to wait for pid %u: %s", (unsigned int) pid, strerror(errno)); status = -1; break; } else { pr_signals_handle(); continue; } } /* Check the time elapsed since we started. */ if ((time(NULL) - start_time) > PROXY_SSH_PASSPHRASE_TIMEOUT) { /* Send TERM, the first time, to be polite. */ if (send_sigterm) { send_sigterm = 0; pr_log_debug(DEBUG6, MOD_PROXY_VERSION ": '%s' has exceeded the timeout (%lu seconds), sending " "SIGTERM (signal %d)", passphrase_provider, (unsigned long) PROXY_SSH_PASSPHRASE_TIMEOUT, SIGTERM); kill(pid, SIGTERM); } else { /* The child is still around? Terminate with extreme prejudice. */ pr_log_debug(DEBUG6, MOD_PROXY_VERSION ": '%s' has exceeded the timeout (%lu seconds), sending " "SIGKILL (signal %d)", passphrase_provider, (unsigned long) PROXY_SSH_PASSPHRASE_TIMEOUT, SIGKILL); kill(pid, SIGKILL); } } /* Select on the pipe read fds, to see if the child has anything * to tell us. */ FD_ZERO(&readfds); FD_SET(stdout_pipe[0], &readfds); FD_SET(stderr_pipe[0], &readfds); /* Note: this delay should be configurable somehow. */ tv.tv_sec = 2L; tv.tv_usec = 0L; fds = select(maxfd + 1, &readfds, NULL, NULL, &tv); if (fds == -1 && errno == EINTR) { pr_signals_handle(); } if (fds > 0) { /* The child sent us something. How thoughtful. */ if (FD_ISSET(stdout_pipe[0], &readfds)) { res = read(stdout_pipe[0], buf, buflen); if (res > 0) { buf[buflen-1] = '\0'; while (res && (buf[res-1] == '\r' || buf[res-1] == '\n')) { pr_signals_handle(); res--; } buf[res] = '\0'; } else if (res < 0) { pr_log_debug(DEBUG2, MOD_PROXY_VERSION ": error reading stdout from '%s': %s", passphrase_provider, strerror(errno)); } } if (FD_ISSET(stderr_pipe[0], &readfds)) { long stderrlen, stderrsz; char *stderrbuf; pool *tmp_pool = make_sub_pool(s->pool); stderrbuf = pr_fsio_getpipebuf(tmp_pool, stderr_pipe[0], &stderrsz); memset(stderrbuf, '\0', stderrsz); stderrlen = read(stderr_pipe[0], stderrbuf, stderrsz-1); if (stderrlen > 0) { while (stderrlen && (stderrbuf[stderrlen-1] == '\r' || stderrbuf[stderrlen-1] == '\n')) { stderrlen--; } stderrbuf[stderrlen] = '\0'; pr_log_debug(DEBUG5, MOD_PROXY_VERSION ": stderr from '%s': %s", passphrase_provider, stderrbuf); } else if (res < 0) { pr_log_debug(DEBUG2, MOD_PROXY_VERSION ": error reading stderr from '%s': %s", passphrase_provider, strerror(errno)); } destroy_pool(tmp_pool); tmp_pool = NULL; } } res = waitpid(pid, &status, WNOHANG); } } /* Restore the previous signal actions. */ if (sigaction(SIGINT, &sa_intr, NULL) < 0) { return -1; } if (sigaction(SIGQUIT, &sa_quit, NULL) < 0) { return -1; } if (sigprocmask(SIG_SETMASK, &set_save, NULL) < 0) { return -1; } if (WIFSIGNALED(status)) { pr_log_debug(DEBUG2, MOD_PROXY_VERSION ": '%s' died from signal %d", passphrase_provider, WTERMSIG(status)); errno = EPERM; return -1; } return 0; } /* Return the size of a page on this architecture. */ static size_t get_pagesz(void) { long pagesz; #if defined(_SC_PAGESIZE) pagesz = sysconf(_SC_PAGESIZE); #elif defined(_SC_PAGE_SIZE) pagesz = sysconf(_SC_PAGE_SIZE); #else /* Default to using OpenSSL's defined buffer size for PEM files. */ pagesz = PEM_BUFSIZE; #endif /* !_SC_PAGESIZE and !_SC_PAGE_SIZE */ return pagesz; } /* Return a page-aligned pointer to memory of at least the given size. */ static char *get_page(size_t sz, void **ptr) { void *d; long pagesz = get_pagesz(), p; d = calloc(1, sz + (pagesz-1)); if (d == NULL) { pr_log_pri(PR_LOG_ALERT, MOD_PROXY_VERSION ": Out of memory!"); exit(1); } *ptr = d; p = ((long) d + (pagesz-1)) &~ (pagesz-1); return ((char *) p); } static unsigned char *decode_base64(pool *p, unsigned char *text, size_t text_len, size_t *data_len) { unsigned char *data = NULL; int have_padding = FALSE, res; /* Due to Base64's padding, we need to detect if the last block was padded * with zeros; we do this by looking for '=' characters at the end of the * text being decoded. If we see these characters, then we will "trim" off * any trailing zero values in the decoded data, on the ASSUMPTION that they * are the auto-added padding bytes. */ if (text[text_len-1] == '=') { have_padding = TRUE; } data = pcalloc(p, text_len); res = EVP_DecodeBlock((unsigned char *) data, (unsigned char *) text, (int) text_len); if (res <= 0) { /* Base64-decoding error. */ errno = EINVAL; return NULL; } if (have_padding == TRUE) { /* Assume that only one or two zero bytes of padding were added. */ if (data[res-1] == '\0') { res -= 1; if (data[res-1] == '\0') { res -= 1; } } } *data_len = (size_t) res; return data; } static int is_public_key(int fd) { struct stat st; char begin_buf[PROXY_SSH_PUBLICKEY_BEGIN_LEN+20]; ssize_t len; off_t minsz; if (fstat(fd, &st) < 0) { return -1; } minsz = PROXY_SSH_PUBLICKEY_BEGIN_LEN + PROXY_SSH_PUBLICKEY_END_LEN; if (st.st_size < minsz) { return FALSE; } len = pread(fd, begin_buf, sizeof(begin_buf), 0); if (len != sizeof(begin_buf)) { return FALSE; } begin_buf[len-1] = '\0'; if (strstr(begin_buf, "PUBLIC KEY") == NULL) { return FALSE; } if (strstr(begin_buf, "BEGIN") == NULL) { return FALSE; } return TRUE; } static int is_openssh_private_key(int fd) { struct stat st; char begin_buf[PROXY_SSH_OPENSSH_BEGIN_LEN], end_buf[PROXY_SSH_OPENSSH_END_LEN]; ssize_t len; off_t minsz; if (fstat(fd, &st) < 0) { return -1; } minsz = PROXY_SSH_OPENSSH_BEGIN_LEN + PROXY_SSH_OPENSSH_END_LEN; if (st.st_size < minsz) { return FALSE; } len = pread(fd, begin_buf, sizeof(begin_buf), 0); if (len != sizeof(begin_buf)) { return FALSE; } if (memcmp(begin_buf, PROXY_SSH_OPENSSH_BEGIN, PROXY_SSH_OPENSSH_BEGIN_LEN) != 0) { return FALSE; } len = pread(fd, end_buf, sizeof(end_buf), st.st_size - PROXY_SSH_OPENSSH_END_LEN); if (len != sizeof(end_buf)) { return FALSE; } if (memcmp(end_buf, PROXY_SSH_OPENSSH_END, PROXY_SSH_OPENSSH_END_LEN) != 0) { return FALSE; } return TRUE; } static int get_passphrase_cb(char *buf, int buflen, int rwflag, void *d) { static int need_banner = TRUE; struct proxy_ssh_pkey_data *pdata = d; if (passphrase_provider == NULL) { register unsigned int attempt; size_t pwlen = 0; pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": requesting passphrase from admin"); if (need_banner) { fprintf(stderr, "\nPlease provide passphrase for the encrypted host key:\n"); need_banner = FALSE; } /* You get three attempts at entering the passphrase correctly. */ for (attempt = 0; attempt < 3; attempt++) { int res; /* Always handle signals in a loop. */ pr_signals_handle(); res = EVP_read_pw_string(buf, buflen, pdata->prompt, TRUE); /* A return value of zero from EVP_read_pw_string() means success; -1 * means a system error occurred, and 1 means user interaction problems. */ if (res != 0) { fprintf(stderr, "\nPassphrases do not match. Please try again.\n"); continue; } /* Ensure that the buffer is NUL-terminated. */ buf[buflen-1] = '\0'; pwlen = strlen(buf); if (pwlen < 1) { fprintf(stderr, "Error: passphrase must be at least one character\n"); } else { sstrncpy(pdata->buf, buf, pdata->bufsz); pdata->buflen = pwlen; return pwlen; } } } else { pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": requesting passphrase from '%s'", passphrase_provider); if (exec_passphrase_provider(pdata->s, buf, buflen, pdata->path) < 0) { pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": error obtaining passphrase from '%s': %s", passphrase_provider, strerror(errno)); } else { size_t pwlen; /* Ensure that the buffer is NUL-terminated. */ buf[buflen-1] = '\0'; pwlen = strlen(buf); sstrncpy(pdata->buf, buf, pdata->bufsz); pdata->buflen = pwlen; return pwlen; } } #if OPENSSL_VERSION_NUMBER < 0x00908001 PEMerr(PEM_F_DEF_CALLBACK, PEM_R_PROBLEMS_GETTING_PASSWORD); #else PEMerr(PEM_F_PEM_DEF_CALLBACK, PEM_R_PROBLEMS_GETTING_PASSWORD); #endif pr_memscrub(buf, buflen); return -1; } static void free_hostkey_bio(BIO *bio) { char *data = NULL; long datalen = 0; /* "Rewind" the pointer to the start of the buffer, for scrubbing. */ BIO_reset(bio); datalen = BIO_get_mem_data(bio, &data); if (data != NULL && datalen > 0) { pr_memscrub(data, datalen); } BIO_free(bio); } static BIO *load_hostkey_bio(pool *p, int fd) { int res, xerrno; BIO *bio = NULL, *readonly_bio = NULL; struct stat st; unsigned char *buf = NULL; size_t bufsz; char *data = NULL, *ptr = NULL; long datalen = 0; memset(&st, 0, sizeof(st)); res = fstat(fd, &st); if (res < 0) { return NULL; } bufsz = st.st_blksize; buf = palloc(p, bufsz); #if defined(PR_USE_OPENSSL_BIO_SECMEM) bio = BIO_new(BIO_s_secmem()); #else bio = BIO_new(BIO_s_mem()); #endif /* PR_USE_OPENSSL_BIO_SECMEM */ res = read(fd, buf, bufsz); xerrno = errno; if (res < 0) { BIO_free(bio); errno = xerrno; return NULL; } while (res > 0) { pr_signals_handle(); BIO_write(bio, buf, res); pr_memscrub(buf, res); res = read(fd, buf, bufsz); xerrno = errno; if (res < 0) { BIO_free(bio); errno = xerrno; return NULL; } } /* Now we create a read-only memory BIO for this hostkey data. This is * specifically for the on-startup use case, where the admin might mistype * the passphrase for a passphrase-protected hostkey, and need to retry * the decryption process. The data in a normal read/write memory BIO is * consumed from the BIO on read, meaning that that BIO would not be usable * for a subsequent decryption attempt. */ datalen = BIO_get_mem_data(bio, &ptr); if (ptr == NULL || datalen == 0) { BIO_free(bio); errno = EIO; return NULL; } /* Make a copy of the data, so that we can destroy the original BIO without * losing this data. */ data = palloc(p, datalen); memcpy(data, ptr, datalen); BIO_free(bio); readonly_bio = BIO_new_mem_buf(data, datalen); return readonly_bio; } static int get_passphrase(struct proxy_ssh_pkey *k, const char *path) { pool *tmp_pool = NULL; char prompt[256]; BIO *bio = NULL; EVP_PKEY *pkey = NULL; unsigned char *key_data = NULL; uint32_t key_datalen = 0; int fd, prompt_fd = -1, res, xerrno, openssh_format = FALSE, public_key_format = FALSE; struct proxy_ssh_pkey_data pdata; register unsigned int attempt; memset(prompt, '\0', sizeof(prompt)); res = pr_snprintf(prompt, sizeof(prompt)-1, "Host key for the %s#%d (%s) server: ", pr_netaddr_get_ipstr(k->server->addr), k->server->ServerPort, k->server->ServerName); prompt[res] = '\0'; prompt[sizeof(prompt)-1] = '\0'; PRIVS_ROOT fd = open(path, O_RDONLY); xerrno = errno; PRIVS_RELINQUISH if (fd < 0) { SYSerr(SYS_F_FOPEN, xerrno); errno = xerrno; return -1; } /* Make sure the fd isn't one of the big three. */ if (fd <= STDERR_FILENO) { res = pr_fs_get_usable_fd(fd); if (res >= 0) { (void) close(fd); fd = res; } } tmp_pool = make_sub_pool(proxy_pool); pr_pool_tag(tmp_pool, "Proxy SFTP Passphrase pool"); public_key_format = is_public_key(fd); if (public_key_format == TRUE) { pr_trace_msg(trace_channel, 3, "hostkey file '%s' uses a public key format", path); (void) pr_log_pri(PR_LOG_WARNING, MOD_PROXY_VERSION ": unable to use public key '%s' for SFTPHostKey", path); (void) close(fd); destroy_pool(tmp_pool); errno = EINVAL; return -1; } openssh_format = is_openssh_private_key(fd); if (openssh_format != TRUE) { /* Rather than using OpenSSL's PEM_read_PrivateKey and the underlying C * library's FILE routines, dealing with unbuffered file handles and * byte-by-byte reads from OpenSSL, we instead provision the file data * into a memory BIO, and let OpenSSL read from that. * * This allows OpenSSL to maintain its byte-by-byte reads, while we read * the file data using filesystem block-sized reads. */ bio = load_hostkey_bio(tmp_pool, fd); if (bio == NULL) { xerrno = errno; (void) close(fd); destroy_pool(tmp_pool); SYSerr(SYS_F_FOPEN, xerrno); errno = xerrno; return -1; } } else { pr_trace_msg(trace_channel, 9, "handling host key '%s' as an OpenSSH-formatted private key", path); } k->client_pkey = get_page(PEM_BUFSIZE, &k->client_pkey_ptr); if (k->client_pkey == NULL) { pr_log_pri(PR_LOG_ALERT, MOD_PROXY_VERSION ": Out of memory!"); exit(1); } pdata.s = k->server; pdata.buf = k->client_pkey; pdata.buflen = 0; pdata.bufsz = k->pkeysz; pdata.path = path; pdata.prompt = prompt; /* Reconnect stderr to the term because proftpd connects stderr, earlier, * to the general stderr logfile. */ prompt_fd = open("/dev/null", O_WRONLY); if (prompt_fd == -1) { /* This is an arbitrary, meaningless placeholder number. */ prompt_fd = 76; } dup2(STDERR_FILENO, prompt_fd); dup2(STDOUT_FILENO, STDERR_FILENO); /* The user gets three tries to enter the correct passphrase. */ for (attempt = 0; attempt < 3; attempt++) { /* Always handle signals in a loop. */ pr_signals_handle(); if (openssh_format == FALSE) { pkey = PEM_read_bio_PrivateKey(bio, NULL, get_passphrase_cb, &pdata); if (pkey != NULL) { break; } if (BIO_reset(bio) < 0) { pr_trace_msg(trace_channel, 3, "error resetting BIO for '%s': %s", path, strerror(errno)); } } else { char buf[PEM_BUFSIZE]; const char *passphrase; enum proxy_ssh_key_type_e key_type = PROXY_SSH_KEY_UNKNOWN; /* First we try with no passphrase. Failing that, we have to invoke the * get_passphase_cb() callback ourselves for OpenSSH keys. */ if (attempt == 0) { passphrase = pstrdup(tmp_pool, ""); res = read_openssh_private_key(tmp_pool, path, fd, passphrase, &key_type, &pkey, &key_data, &key_datalen); if (lseek(fd, 0, SEEK_SET) < 0) { pr_trace_msg(trace_channel, 3, "error rewinding fd %d for '%s': %s", fd, path, strerror(errno)); } if (res == 0) { break; } } res = get_passphrase_cb(buf, PEM_BUFSIZE, 0, &pdata); if (res > 0) { passphrase = pdata.buf; res = read_openssh_private_key(tmp_pool, path, fd, passphrase, &key_type, &pkey, &key_data, &key_datalen); if (res == 0) { break; } if (lseek(fd, 0, SEEK_SET) < 0) { pr_trace_msg(trace_channel, 3, "error rewinding fd %d for '%s': %s", fd, path, strerror(errno)); } } else { pr_trace_msg(trace_channel, 2, "error reading passphrase for OpenSSH key: %s", proxy_ssh_crypto_get_errors()); } } ERR_clear_error(); fprintf(stderr, "\nWrong passphrase for this key. Please try again.\n"); } if (bio != NULL) { free_hostkey_bio(bio); } /* Restore the normal stderr logging. */ (void) dup2(prompt_fd, STDERR_FILENO); (void) close(prompt_fd); if (pkey == NULL && key_data == NULL) { return -1; } if (pkey != NULL) { EVP_PKEY_free(pkey); } if (key_data != NULL) { pr_memscrub(key_data, key_datalen); } destroy_pool(tmp_pool); if (pdata.buflen > 0) { #if OPENSSL_VERSION_NUMBER >= 0x000905000L /* Use the obtained passphrase as additional entropy, ostensibly * unknown to attackers who may be watching the network, for * OpenSSL's PRNG. * * Human language gives about 2-3 bits of entropy per byte (RFC1750). */ RAND_add(pdata.buf, pdata.buflen, pdata.buflen * 0.25); #endif #ifdef HAVE_MLOCK PRIVS_ROOT if (mlock(k->client_pkey, k->pkeysz) < 0) { pr_log_debug(DEBUG1, MOD_PROXY_VERSION ": error locking passphrase into memory: %s", strerror(errno)); } else { pr_log_debug(DEBUG1, MOD_PROXY_VERSION ": passphrase locked into memory"); } PRIVS_RELINQUISH #endif } return 0; } static struct proxy_ssh_pkey *lookup_pkey(void) { struct proxy_ssh_pkey *k, *pkey = NULL; for (k = pkey_list; k; k = k->next) { /* If this pkey matches the current server_rec, mark it and move on. */ if (k->server == main_server) { #ifdef HAVE_MLOCK /* mlock() the passphrase memory areas again; page locks are not * inherited across forks. */ PRIVS_ROOT if (k->client_pkey != NULL) { if (mlock(k->client_pkey, k->pkeysz) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error locking passphrase into memory: %s", strerror(errno)); } } PRIVS_RELINQUISH #endif /* HAVE_MLOCK */ pkey = k; continue; } /* Otherwise, scrub the passphrase's memory areas. */ if (k->client_pkey != NULL) { pr_memscrub(k->client_pkey, k->pkeysz); free(k->client_pkey_ptr); k->client_pkey = k->client_pkey_ptr = NULL; } } return pkey; } static void scrub_pkeys(void) { struct proxy_ssh_pkey *k; if (pkey_list == NULL) { return; } /* Scrub and free all passphrases in memory. */ pr_log_debug(DEBUG5, MOD_PROXY_VERSION ": scrubbing %u %s from memory", npkeys, npkeys != 1 ? "passphrases" : "passphrase"); for (k = pkey_list; k; k = k->next) { if (k->client_pkey != NULL) { pr_memscrub(k->client_pkey, k->pkeysz); free(k->client_pkey_ptr); k->client_pkey = k->client_pkey_ptr = NULL; } } pkey_list = NULL; npkeys = 0; } static int pkey_cb(char *buf, int buflen, int rwflag, void *d) { struct proxy_ssh_pkey *k; if (d == NULL) { return 0; } k = (struct proxy_ssh_pkey *) d; if (k->client_pkey != NULL) { sstrncpy(buf, k->client_pkey, buflen); buf[buflen - 1] = '\0'; return strlen(buf); } return 0; } static int has_req_perms(int fd, const char *path) { struct stat st; if (fstat(fd, &st) < 0) { return -1; } if (st.st_mode & (S_IRWXG|S_IRWXO)) { errno = EACCES; return -1; } return 0; } static uint32_t read_pkey_from_data(pool *p, unsigned char *pkey_data, uint32_t pkey_datalen, EVP_PKEY **pkey, enum proxy_ssh_key_type_e *key_type, int openssh_format) { char *pkey_type = NULL; uint32_t res, len = 0; res = proxy_ssh_msg_read_string(p, &pkey_data, &pkey_datalen, &pkey_type); if (res == 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error reading key: invalid/unsupported key format"); return 0; } len += res; if (strcmp(pkey_type, "ssh-rsa") == 0) { RSA *rsa; const BIGNUM *rsa_e = NULL, *rsa_n = NULL, *rsa_d = NULL; *pkey = EVP_PKEY_new(); if (*pkey == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error allocating EVP_PKEY: %s", proxy_ssh_crypto_get_errors()); return 0; } rsa = RSA_new(); if (rsa == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error allocating RSA: %s", proxy_ssh_crypto_get_errors()); EVP_PKEY_free(*pkey); *pkey = NULL; return 0; } res = proxy_ssh_msg_read_mpint(p, &pkey_data, &pkey_datalen, &rsa_e); if (res == 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error reading key: invalid/unsupported key format"); RSA_free(rsa); EVP_PKEY_free(*pkey); *pkey = NULL; return 0; } len += res; res = proxy_ssh_msg_read_mpint(p, &pkey_data, &pkey_datalen, &rsa_n); if (res == 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error reading key: invalid/unsupported key format"); RSA_free(rsa); EVP_PKEY_free(*pkey); *pkey = NULL; return 0; } len += res; if (openssh_format == TRUE) { const BIGNUM *rsa_p, *rsa_q, *rsa_iqmp; /* The OpenSSH private key format encodes more factors. */ res = proxy_ssh_msg_read_mpint(p, &pkey_data, &pkey_datalen, &rsa_d); if (res == 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error reading key: invalid/unsupported key format"); RSA_free(rsa); EVP_PKEY_free(*pkey); *pkey = NULL; return 0; } len += res; /* RSA_get0_crt_params */ res = proxy_ssh_msg_read_mpint(p, &pkey_data, &pkey_datalen, &rsa_iqmp); if (res == 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error reading key: invalid/unsupported key format"); RSA_free(rsa); EVP_PKEY_free(*pkey); *pkey = NULL; return 0; } len += res; /* RSA_get0_factors */ res = proxy_ssh_msg_read_mpint(p, &pkey_data, &pkey_datalen, &rsa_p); if (res == 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error reading key: invalid/unsupported key format"); RSA_free(rsa); EVP_PKEY_free(*pkey); *pkey = NULL; return 0; } len += res; res = proxy_ssh_msg_read_mpint(p, &pkey_data, &pkey_datalen, &rsa_q); if (res == 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error reading key: invalid/unsupported key format"); RSA_free(rsa); EVP_PKEY_free(*pkey); *pkey = NULL; return 0; } len += res; #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) RSA_set0_crt_params(rsa, NULL, NULL, (BIGNUM *) rsa_iqmp); RSA_set0_factors(rsa, (BIGNUM *) rsa_p, (BIGNUM *) rsa_q); #else rsa->iqmp = rsa_iqmp; rsa->p = rsa_p; rsa->q = rsa_q; #endif /* prior to OpenSSL-1.1.0 */ /* Turns out that for OpenSSH formatted RSA keys, the 'e' and 'n' values * are in the opposite order than the normal PEM format. Typical. */ #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) RSA_set0_key(rsa, (BIGNUM *) rsa_e, (BIGNUM *) rsa_n, (BIGNUM *) rsa_d); #else rsa->e = rsa_n; rsa->n = rsa_e; rsa->d = rsa_d; #endif /* prior to OpenSSL-1.1.0 */ } else { #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) RSA_set0_key(rsa, (BIGNUM *) rsa_n, (BIGNUM *) rsa_e, (BIGNUM *) rsa_d); #else rsa->e = rsa_e; rsa->n = rsa_n; rsa->d = rsa_d; #endif /* prior to OpenSSL-1.1.0 */ } if (EVP_PKEY_assign_RSA(*pkey, rsa) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error assigning RSA to EVP_PKEY: %s", proxy_ssh_crypto_get_errors()); RSA_free(rsa); EVP_PKEY_free(*pkey); *pkey = NULL; return 0; } if (key_type != NULL) { *key_type = PROXY_SSH_KEY_RSA; } } else if (strcmp(pkey_type, "ssh-dss") == 0) { #if !defined(OPENSSL_NO_DSA) DSA *dsa; const BIGNUM *dsa_p, *dsa_q, *dsa_g, *dsa_pub_key, *dsa_priv_key = NULL; *pkey = EVP_PKEY_new(); if (*pkey == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error allocating EVP_PKEY: %s", proxy_ssh_crypto_get_errors()); return 0; } dsa = DSA_new(); if (dsa == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error allocating DSA: %s", proxy_ssh_crypto_get_errors()); EVP_PKEY_free(*pkey); *pkey = NULL; return 0; } res = proxy_ssh_msg_read_mpint(p, &pkey_data, &pkey_datalen, &dsa_p); if (res == 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error reading key: invalid/unsupported key format"); DSA_free(dsa); EVP_PKEY_free(*pkey); *pkey = NULL; return 0; } len += res; res = proxy_ssh_msg_read_mpint(p, &pkey_data, &pkey_datalen, &dsa_q); if (res == 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error reading key: invalid/unsupported key format"); DSA_free(dsa); EVP_PKEY_free(*pkey); *pkey = NULL; return 0; } len += res; res = proxy_ssh_msg_read_mpint(p, &pkey_data, &pkey_datalen, &dsa_g); if (res == 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error reading key: invalid/unsupported key format"); DSA_free(dsa); EVP_PKEY_free(*pkey); *pkey = NULL; return 0; } len += res; res = proxy_ssh_msg_read_mpint(p, &pkey_data, &pkey_datalen, &dsa_pub_key); if (res == 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error reading key: invalid/unsupported key format"); DSA_free(dsa); EVP_PKEY_free(*pkey); *pkey = NULL; return 0; } len += res; if (openssh_format == TRUE) { res = proxy_ssh_msg_read_mpint(p, &pkey_data, &pkey_datalen, &dsa_priv_key); if (res == 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error reading key: invalid/unsupported key format"); DSA_free(dsa); EVP_PKEY_free(*pkey); *pkey = NULL; return 0; } len += res; } #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) DSA_set0_pqg(dsa, (BIGNUM *) dsa_p, (BIGNUM *) dsa_q, (BIGNUM *) dsa_g); DSA_set0_key(dsa, (BIGNUM *) dsa_pub_key, (BIGNUM *) dsa_priv_key); #else dsa->p = dsa_p; dsa->q = dsa_q; dsa->g = dsa_g; dsa->pub_key = dsa_pub_key; dsa->priv_key = dsa_priv_key; #endif /* prior to OpenSSL-1.1.0 */ if (EVP_PKEY_assign_DSA(*pkey, dsa) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error assigning RSA to EVP_PKEY: %s", proxy_ssh_crypto_get_errors()); DSA_free(dsa); EVP_PKEY_free(*pkey); *pkey = NULL; return 0; } if (key_type != NULL) { *key_type = PROXY_SSH_KEY_DSA; } #else (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unsupported public key algorithm '%s'", pkey_type); errno = EINVAL; return 0; #endif /* !OPENSSL_NO_DSA */ #ifdef PR_USE_OPENSSL_ECC } else if (strcmp(pkey_type, "ecdsa-sha2-nistp256") == 0 || strcmp(pkey_type, "ecdsa-sha2-nistp384") == 0 || strcmp(pkey_type, "ecdsa-sha2-nistp521") == 0) { EC_KEY *ec; const char *curve_name; const EC_GROUP *curve; EC_POINT *point; int ec_nid; char *ptr = NULL; res = proxy_ssh_msg_read_string(p, &pkey_data, &pkey_datalen, &ptr); if (res == 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error reading key: invalid/unsupported key format"); return 0; } len += res; curve_name = (const char *) ptr; /* If the curve name does not match the last 8 characters of the * public key type (which, in the case of ECDSA keys, contains the * curve name), then it's definitely a mismatch. */ if (strncmp(pkey_type + 11, curve_name, 9) != 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "EC public key curve name '%s' does not match public key " "algorithm '%s'", curve_name, pkey_type); return 0; } if (strcmp(curve_name, "nistp256") == 0) { ec_nid = NID_X9_62_prime256v1; if (key_type != NULL) { *key_type = PROXY_SSH_KEY_ECDSA_256; } } else if (strcmp(curve_name, "nistp384") == 0) { ec_nid = NID_secp384r1; if (key_type != NULL) { *key_type = PROXY_SSH_KEY_ECDSA_384; } } else if (strcmp(curve_name, "nistp521") == 0) { ec_nid = NID_secp521r1; if (key_type != NULL) { *key_type = PROXY_SSH_KEY_ECDSA_521; } } else { ec_nid = -1; } ec = EC_KEY_new_by_curve_name(ec_nid); if (ec == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error allocating EC_KEY for %s: %s", pkey_type, proxy_ssh_crypto_get_errors()); return 0; } curve = EC_KEY_get0_group(ec); point = EC_POINT_new(curve); if (point == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error allocating EC_POINT for %s: %s", pkey_type, proxy_ssh_crypto_get_errors()); EC_KEY_free(ec); return 0; } res = proxy_ssh_msg_read_ecpoint(p, &pkey_data, &pkey_datalen, curve, &point); if (res == 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error reading key: invalid/unsupported key format"); EC_KEY_free(ec); return 0; } len += res; if (point == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error reading EC_POINT from public key data: %s", strerror(errno)); EC_POINT_free(point); EC_KEY_free(ec); return 0; } if (proxy_ssh_keys_validate_ecdsa_params(curve, point) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error validating EC public key: %s", strerror(errno)); EC_POINT_free(point); EC_KEY_free(ec); return 0; } if (EC_KEY_set_public_key(ec, point) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error setting public key on EC_KEY: %s", proxy_ssh_crypto_get_errors()); EC_POINT_free(point); EC_KEY_free(ec); return 0; } if (openssh_format) { const BIGNUM *ec_priv_key = NULL; res = proxy_ssh_msg_read_mpint(p, &pkey_data, &pkey_datalen, &ec_priv_key); if (res == 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error reading key: invalid/unsupported key format"); EC_POINT_free(point); EC_KEY_free(ec); *pkey = NULL; return 0; } len += res; if (EC_KEY_set_private_key(ec, ec_priv_key) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error setting private key on EC_KEY: %s", proxy_ssh_crypto_get_errors()); EC_POINT_free(point); EC_KEY_free(ec); return 0; } } *pkey = EVP_PKEY_new(); if (*pkey == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error allocating EVP_PKEY: %s", proxy_ssh_crypto_get_errors()); EC_POINT_free(point); EC_KEY_free(ec); return 0; } if (EVP_PKEY_assign_EC_KEY(*pkey, ec) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error assigning ECDSA-256 to EVP_PKEY: %s", proxy_ssh_crypto_get_errors()); EC_POINT_free(point); EC_KEY_free(ec); EVP_PKEY_free(*pkey); *pkey = NULL; return 0; } #endif /* PR_USE_OPENSSL_ECC */ #if defined(PR_USE_SODIUM) } else if (strcmp(pkey_type, "ssh-ed25519") == 0) { if (key_type != NULL) { *key_type = PROXY_SSH_KEY_ED25519; } #endif /* PR_USE_SODIUM */ #if defined(HAVE_X448_OPENSSL) } else if (strcmp(pkey_type, "ssh-ed448") == 0) { if (key_type != NULL) { *key_type = PROXY_SSH_KEY_ED448; } #endif /* HAVE_X448_OPENSSL */ } else { pr_trace_msg(trace_channel, 3, "unsupported public key algorithm '%s'", pkey_type); (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unsupported public key algorithm '%s'", pkey_type); errno = EINVAL; return 0; } return len; } static const char *get_pkey_type_desc(int pkey_type) { const char *key_desc = NULL; switch (pkey_type) { #ifdef EVP_PKEY_NONE case EVP_PKEY_NONE: key_desc = "undefined"; break; #endif #ifdef EVP_PKEY_RSA case EVP_PKEY_RSA: key_desc = "RSA"; break; #endif #ifdef EVP_PKEY_DSA case EVP_PKEY_DSA: key_desc = "DSA"; break; #endif #ifdef EVP_PKEY_DH case EVP_PKEY_DH: key_desc = "DH"; break; #endif #ifdef EVP_PKEY_EC case EVP_PKEY_EC: key_desc = "ECC"; break; #endif default: key_desc = "unknown"; } return key_desc; } static const char *get_key_type_desc(enum proxy_ssh_key_type_e key_type) { const char *key_desc = NULL; switch (key_type) { case PROXY_SSH_KEY_UNKNOWN: key_desc = "unknown"; break; case PROXY_SSH_KEY_DSA: key_desc = "DSA"; break; case PROXY_SSH_KEY_RSA: key_desc = "RSA"; break; case PROXY_SSH_KEY_ECDSA_256: key_desc = "ECDSA256"; break; case PROXY_SSH_KEY_ECDSA_384: key_desc = "ECDSA384"; break; case PROXY_SSH_KEY_ECDSA_521: key_desc = "ECDSA521"; break; case PROXY_SSH_KEY_ED25519: key_desc = "ED25519"; break; case PROXY_SSH_KEY_ED448: key_desc = "ED448"; break; default: key_desc = "undefined"; break; } return key_desc; } #if defined(PR_USE_OPENSSL_ECC) /* Make sure the given ECDSA private key is suitable for use. */ static int validate_ecdsa_private_key(const EC_KEY *ec) { BN_CTX *bn_ctx; BIGNUM *ec_order, *bn_tmp; int ec_order_nbits, priv_key_nbits; /* A BN_CTX is like our pools; we allocate one, use it to get any * number of BIGNUM variables, and only have free up the BN_CTX when * we're done, rather than all of the individual BIGNUMs. */ bn_ctx = BN_CTX_new(); if (bn_ctx == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error allocating BN_CTX: %s", proxy_ssh_crypto_get_errors()); return -1; } BN_CTX_start(bn_ctx); ec_order = BN_CTX_get(bn_ctx); if (ec_order == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error getting new BIGNUM from BN_CTX: %s", proxy_ssh_crypto_get_errors()); BN_CTX_free(bn_ctx); errno = EPERM; return -1; } bn_tmp = BN_CTX_get(bn_ctx); if (bn_tmp == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error getting new BIGNUM from BN_CTX: %s", proxy_ssh_crypto_get_errors()); BN_CTX_free(bn_ctx); errno = EPERM; return -1; } /* Make sure that log2(private key) is greater than log2(EC order)/2. */ if (EC_GROUP_get_order(EC_KEY_get0_group(ec), ec_order, bn_ctx) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error getting the EC group order: %s", proxy_ssh_crypto_get_errors()); BN_CTX_free(bn_ctx); errno = EPERM; return -1; } priv_key_nbits = BN_num_bits(EC_KEY_get0_private_key(ec)); ec_order_nbits = BN_num_bits(ec_order); if (priv_key_nbits <= (ec_order_nbits / 2)) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "ECDSA private key (%d bits) is too small, must be at " "least %d bits", priv_key_nbits, ec_order_nbits); BN_CTX_free(bn_ctx); errno = EACCES; return -1; } /* Ensure that the private key < (EC order - 1). */ if (BN_sub(bn_tmp, ec_order, BN_value_one()) == 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error subtracting one from EC group order: %s", proxy_ssh_crypto_get_errors()); BN_CTX_free(bn_ctx); errno = EPERM; return -1; } if (BN_cmp(EC_KEY_get0_private_key(ec), bn_tmp) >= 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "ECDSA private key is greater than or equal to EC group order, " "rejecting"); BN_CTX_free(bn_ctx); errno = EACCES; return -1; } BN_CTX_free(bn_ctx); return 0; } enum proxy_ssh_key_type_e proxy_ssh_keys_get_key_type(const char *algo) { enum proxy_ssh_key_type_e key_type = PROXY_SSH_KEY_UNKNOWN; if (algo == NULL) { return PROXY_SSH_KEY_UNKNOWN; } if (strcmp(algo, "ssh-dss") == 0) { key_type = PROXY_SSH_KEY_DSA; } else if (strcmp(algo, "ssh-rsa") == 0) { key_type = PROXY_SSH_KEY_RSA; } else if (strcmp(algo, "rsa-sha2-256") == 0) { key_type = PROXY_SSH_KEY_RSA_SHA256; } else if (strcmp(algo, "rsa-sha2-512") == 0) { key_type = PROXY_SSH_KEY_RSA_SHA512; } else if (strcmp(algo, "ecdsa-sha2-nistp256") == 0) { key_type = PROXY_SSH_KEY_ECDSA_256; } else if (strcmp(algo, "ecdsa-sha2-nistp384") == 0) { key_type = PROXY_SSH_KEY_ECDSA_384; } else if (strcmp(algo, "ecdsa-sha2-nistp521") == 0) { key_type = PROXY_SSH_KEY_ECDSA_521; } else if (strcmp(algo, "ssh-ed25519") == 0) { key_type = PROXY_SSH_KEY_ED25519; } else if (strcmp(algo, "ssh-ed448") == 0) { key_type = PROXY_SSH_KEY_ED448; } return key_type; } const char *proxy_ssh_keys_get_key_type_desc(enum proxy_ssh_key_type_e key_type) { const char *key_desc = NULL; switch (key_type) { case PROXY_SSH_KEY_UNKNOWN: key_desc = "unknown"; break; case PROXY_SSH_KEY_DSA: key_desc = "ssh-dss"; break; case PROXY_SSH_KEY_RSA: key_desc = "ssh-rsa"; break; case PROXY_SSH_KEY_RSA_SHA256: key_desc = "rsa-sha2-256"; break; case PROXY_SSH_KEY_RSA_SHA512: key_desc = "rsa-sha2-512"; break; case PROXY_SSH_KEY_ECDSA_256: key_desc = "ecdsa-sha2-nistp256"; break; case PROXY_SSH_KEY_ECDSA_384: key_desc = "ecdsa-sha2-nistp384"; break; case PROXY_SSH_KEY_ECDSA_521: key_desc = "ecdsa-sha2-nistp521"; break; case PROXY_SSH_KEY_ED25519: key_desc = "ssh-ed25519"; break; case PROXY_SSH_KEY_ED448: key_desc = "ssh-ed448"; break; default: key_desc = "undefined"; break; } return key_desc; } /* This is used to validate the ECDSA parameters we might receive e.g. from * a server. These checks come from Section 3.2.2.1 of 'Standards for * Efficient Cryptography Group, "Elliptic Curve Cryptography", SEC 1, * May 2009: * * http://www.secg.org/download/aid-780/sec1-v2.pdf * * as per RFC 5656 recommendation. */ int proxy_ssh_keys_validate_ecdsa_params(const EC_GROUP *group, const EC_POINT *point) { BN_CTX *bn_ctx; BIGNUM *ec_order, *x_coord, *y_coord, *bn_tmp; int coord_nbits, ec_order_nbits; EC_POINT *subgroup_order = NULL; if (EC_METHOD_get_field_type(EC_GROUP_method_of(group)) != NID_X9_62_prime_field) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "ECDSA group is not a prime field, rejecting"); errno = EACCES; return -1; } /* A Q of infinity is unacceptable. */ if (EC_POINT_is_at_infinity(group, point) != 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "ECDSA EC point has infinite value, rejecting"); errno = EACCES; return -1; } /* A BN_CTX is like our pools; we allocate one, use it to get any * number of BIGNUM variables, and only have free up the BN_CTX when * we're done, rather than all of the individual BIGNUMs. */ bn_ctx = BN_CTX_new(); if (bn_ctx == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error allocating BN_CTX: %s", proxy_ssh_crypto_get_errors()); return -1; } BN_CTX_start(bn_ctx); ec_order = BN_CTX_get(bn_ctx); if (ec_order == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error getting new BIGNUM from BN_CTX: %s", proxy_ssh_crypto_get_errors()); BN_CTX_free(bn_ctx); errno = EPERM; return -1; } if (EC_GROUP_get_order(group, ec_order, bn_ctx) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error getting EC group order: %s", proxy_ssh_crypto_get_errors()); BN_CTX_free(bn_ctx); errno = EPERM; return -1; } x_coord = BN_CTX_get(bn_ctx); if (x_coord == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error getting new BIGNUM from BN_CTX: %s", proxy_ssh_crypto_get_errors()); BN_CTX_free(bn_ctx); errno = EPERM; return -1; } y_coord = BN_CTX_get(bn_ctx); if (y_coord == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error getting new BIGNUM from BN_CTX: %s", proxy_ssh_crypto_get_errors()); BN_CTX_free(bn_ctx); errno = EPERM; return -1; } if (EC_POINT_get_affine_coordinates_GFp(group, point, x_coord, y_coord, bn_ctx) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error getting EC point affine coordinates: %s", proxy_ssh_crypto_get_errors()); BN_CTX_free(bn_ctx); errno = EPERM; return -1; } /* Ensure that the following are both true: * * log2(X coord) > log2(EC order)/2 * log2(Y coord) > log2(EC order)/2 */ coord_nbits = BN_num_bits(x_coord); ec_order_nbits = BN_num_bits(ec_order); if (coord_nbits <= (ec_order_nbits / 2)) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "EC public key X coordinate (%d bits) too small (<= %d bits), rejecting", coord_nbits, (ec_order_nbits / 2)); BN_CTX_free(bn_ctx); errno = EACCES; return -1; } coord_nbits = BN_num_bits(y_coord); if (coord_nbits <= (ec_order_nbits / 2)) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "EC public key Y coordinate (%d bits) too small (<= %d bits), rejecting", coord_nbits, (ec_order_nbits / 2)); BN_CTX_free(bn_ctx); errno = EACCES; return -1; } /* Ensure that the following is true: * * subgroup order == infinity */ subgroup_order = EC_POINT_new(group); if (subgroup_order == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error allocating new EC_POINT: %s", proxy_ssh_crypto_get_errors()); BN_CTX_free(bn_ctx); errno = EPERM; return -1; } if (EC_POINT_mul(group, subgroup_order, NULL, point, ec_order, bn_ctx) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error doing EC point multiplication: %s", proxy_ssh_crypto_get_errors()); EC_POINT_free(subgroup_order); BN_CTX_free(bn_ctx); errno = EPERM; return -1; } if (EC_POINT_is_at_infinity(group, subgroup_order) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "EC public key has finite subgroup order, rejecting"); EC_POINT_free(subgroup_order); BN_CTX_free(bn_ctx); errno = EACCES; return -1; } EC_POINT_free(subgroup_order); /* Ensure that the following are both true: * * X < order - 1 * Y < order - 1 */ bn_tmp = BN_CTX_get(bn_ctx); if (bn_tmp == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error getting new BIGNUM from BN_CTX: %s", proxy_ssh_crypto_get_errors()); BN_CTX_free(bn_ctx); errno = EPERM; return -1; } if (BN_sub(bn_tmp, ec_order, BN_value_one()) == 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error subtracting one from EC group order: %s", proxy_ssh_crypto_get_errors()); BN_CTX_free(bn_ctx); errno = EPERM; return -1; } if (BN_cmp(x_coord, bn_tmp) >= 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "EC public key X coordinate too large (>= EC group order - 1), " "rejecting"); BN_CTX_free(bn_ctx); errno = EACCES; return -1; } if (BN_cmp(y_coord, bn_tmp) >= 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "EC public key Y coordinate too large (>= EC group order - 1), " "rejecting"); BN_CTX_free(bn_ctx); errno = EACCES; return -1; } BN_CTX_free(bn_ctx); return 0; } #endif /* PR_USE_OPENSSL_ECC */ #ifdef SFTP_DEBUG_KEYS static void debug_rsa_key(pool *p, const char *label, RSA *rsa) { BIO *bio = NULL; char *data; long datalen; bio = BIO_new(BIO_s_mem()); RSA_print(bio, rsa, 0); BIO_flush(bio); datalen = BIO_get_mem_data(bio, &data); if (data != NULL && datalen > 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "%s",label); (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "%.*s", (int) datalen, data); } BIO_free(bio); } #endif static int get_pkey_type(EVP_PKEY *pkey) { int pkey_type; #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESS) pkey_type = EVP_PKEY_base_id(pkey); #else pkey_type = EVP_PKEY_type(pkey->type); #endif /* OpenSSL 1.1.x and later */ return pkey_type; } static int rsa_compare_keys(pool *p, EVP_PKEY *remote_pkey, EVP_PKEY *local_pkey) { RSA *remote_rsa = NULL, *local_rsa = NULL; const BIGNUM *remote_rsa_e = NULL, *local_rsa_e = NULL; const BIGNUM *remote_rsa_n = NULL, *local_rsa_n = NULL; int res = 0; local_rsa = EVP_PKEY_get1_RSA(local_pkey); remote_rsa = EVP_PKEY_get1_RSA(remote_pkey); #ifdef SFTP_DEBUG_KEYS debug_rsa_key(p, "remote RSA key:", remote_rsa); debug_rsa_key(p, "local RSA key:", local_rsa); #endif #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) RSA_get0_key(remote_rsa, &remote_rsa_n, &remote_rsa_e, NULL); RSA_get0_key(local_rsa, &local_rsa_n, &local_rsa_e, NULL); #else remote_rsa_e = remote_rsa->e; local_rsa_e = local_rsa->e; remote_rsa_n = remote_rsa->n; local_rsa_n = local_rsa->n; #endif /* prior to OpenSSL-1.1.0 */ if (BN_cmp(remote_rsa_e, local_rsa_e) != 0) { pr_trace_msg(trace_channel, 17, "%s", "RSA key mismatch: client-sent RSA key component 'e' does not match " "local RSA key component 'e'"); res = -1; } if (res == 0) { if (BN_cmp(remote_rsa_n, local_rsa_n) != 0) { pr_trace_msg(trace_channel, 17, "%s", "RSA key mismatch: client-sent RSA key component 'n' does not match " "local RSA key component 'n'"); res = -1; } } RSA_free(remote_rsa); RSA_free(local_rsa); return res; } #if !defined(OPENSSL_NO_DSA) static int dsa_compare_keys(pool *p, EVP_PKEY *remote_pkey, EVP_PKEY *local_pkey) { DSA *remote_dsa = NULL, *local_dsa = NULL; const BIGNUM *remote_dsa_p, *remote_dsa_q, *remote_dsa_g; const BIGNUM *local_dsa_p, *local_dsa_q, *local_dsa_g; const BIGNUM *remote_dsa_pub_key, *local_dsa_pub_key; int res = 0; local_dsa = EVP_PKEY_get1_DSA(local_pkey); remote_dsa = EVP_PKEY_get1_DSA(remote_pkey); #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) DSA_get0_pqg(remote_dsa, &remote_dsa_p, &remote_dsa_q, &remote_dsa_g); DSA_get0_pqg(local_dsa, &local_dsa_p, &local_dsa_q, &local_dsa_g); DSA_get0_key(remote_dsa, &remote_dsa_pub_key, NULL); DSA_get0_key(local_dsa, &local_dsa_pub_key, NULL); #else remote_dsa_p = remote_dsa->p; remote_dsa_q = remote_dsa->q; remote_dsa_g = remote_dsa->g; remote_dsa_pub_key = remote_dsa->pub_key; local_dsa_p = local_dsa->p; local_dsa_q = local_dsa->q; local_dsa_g = local_dsa->g; local_dsa_pub_key = local_dsa->pub_key; #endif /* prior to OpenSSL-1.1.0 */ if (BN_cmp(remote_dsa_p, local_dsa_p) != 0) { pr_trace_msg(trace_channel, 17, "%s", "DSA key mismatch: client-sent DSA key parameter 'p' does not match " "local DSA key parameter 'p'"); res = -1; } if (res == 0) { if (BN_cmp(remote_dsa_q, local_dsa_q) != 0) { pr_trace_msg(trace_channel, 17, "%s", "DSA key mismatch: client-sent DSA key parameter 'q' does not match " "local DSA key parameter 'q'"); res = -1; } } if (res == 0) { if (BN_cmp(remote_dsa_g, local_dsa_g) != 0) { pr_trace_msg(trace_channel, 17, "%s", "DSA key mismatch: client-sent DSA key parameter 'g' does not match " "local DSA key parameter 'g'"); res = -1; } } if (res == 0) { if (BN_cmp(remote_dsa_pub_key, local_dsa_pub_key) != 0) { pr_trace_msg(trace_channel, 17, "%s", "DSA key mismatch: client-sent DSA key parameter 'pub_key' does not " "match local DSA key parameter 'pub_key'"); res = -1; } } DSA_free(remote_dsa); DSA_free(local_dsa); return res; } #endif /* OPENSSL_NO_DSA */ #if defined(PR_USE_OPENSSL_ECC) static int ecdsa_compare_keys(pool *p, EVP_PKEY *remote_pkey, EVP_PKEY *local_pkey) { EC_KEY *remote_ec, *local_ec; int res = 0; local_ec = EVP_PKEY_get1_EC_KEY(local_pkey); remote_ec = EVP_PKEY_get1_EC_KEY(remote_pkey); if (EC_GROUP_cmp(EC_KEY_get0_group(local_ec), EC_KEY_get0_group(remote_ec), NULL) != 0) { pr_trace_msg(trace_channel, 17, "%s", "ECC key mismatch: client-sent curve does not match local ECC curve"); res = -1; } if (res == 0) { if (EC_POINT_cmp(EC_KEY_get0_group(local_ec), EC_KEY_get0_public_key(local_ec), EC_KEY_get0_public_key(remote_ec), NULL) != 0) { pr_trace_msg(trace_channel, 17, "%s", "ECC key mismatch: client-sent public key 'Q' does not match " "local ECC public key 'Q'"); res = -1; } } EC_KEY_free(remote_ec); EC_KEY_free(local_ec); return res; } #endif /* PR_USE_OPENSSL_ECC */ #if defined(PR_USE_SODIUM) static int ed25519_compare_keys(pool *p, unsigned char *remote_pubkey_data, uint32_t remote_pubkey_datalen, unsigned char *local_pubkey_data, uint32_t local_pubkey_datalen) { int res = 0; if (remote_pubkey_datalen != local_pubkey_datalen) { return -1; } if (memcmp(remote_pubkey_data, local_pubkey_data, remote_pubkey_datalen) != 0) { res = -1; } return res; } #endif /* PR_USE_SODIUM */ #if defined(HAVE_X448_OPENSSL) static int ed448_compare_keys(pool *p, unsigned char *remote_pubkey_data, uint32_t remote_pubkey_datalen, unsigned char *local_pubkey_data, uint32_t local_pubkey_datalen) { int res = 0; if (remote_pubkey_datalen != local_pubkey_datalen) { return -1; } if (memcmp(remote_pubkey_data, local_pubkey_data, remote_pubkey_datalen) != 0) { res = -1; } return res; } #endif /* HAVE_X448_OPENSSL */ /* Compare a "blob" of pubkey data sent by the server for authentication * with a local file pubkey (from an RFC4716 formatted file). Returns -1 if * there was an error, TRUE if the keys are equals, and FALSE if not. */ int proxy_ssh_keys_compare_keys(pool *p, unsigned char *remote_pubkey_data, uint32_t remote_pubkey_datalen, unsigned char *local_pubkey_data, uint32_t local_pubkey_datalen) { enum proxy_ssh_key_type_e remote_key_type, local_key_type; EVP_PKEY *remote_pkey = NULL, *local_pkey = NULL; int res = -1; uint32_t len = 0; if (remote_pubkey_data == NULL || local_pubkey_data == NULL) { errno = EINVAL; return -1; } remote_key_type = local_key_type = PROXY_SSH_KEY_UNKNOWN; len = read_pkey_from_data(p, remote_pubkey_data, remote_pubkey_datalen, &remote_pkey, &remote_key_type, FALSE); if (len == 0) { return -1; } len = read_pkey_from_data(p, local_pubkey_data, local_pubkey_datalen, &local_pkey, &local_key_type, FALSE); if (len == 0) { int xerrno = errno; if (remote_pkey != NULL) { EVP_PKEY_free(remote_pkey); } errno = xerrno; return -1; } if (remote_pkey != NULL && local_pkey != NULL && remote_key_type == local_key_type) { switch (get_pkey_type(remote_pkey)) { case EVP_PKEY_RSA: { if (rsa_compare_keys(p, remote_pkey, local_pkey) == 0) { res = TRUE; } else { res = FALSE; } break; } #if !defined(OPENSSL_NO_DSA) case EVP_PKEY_DSA: { if (dsa_compare_keys(p, remote_pkey, local_pkey) == 0) { res = TRUE; } else { res = FALSE; } break; } #endif /* !OPENSSL_NO_DSA */ #ifdef PR_USE_OPENSSL_ECC case EVP_PKEY_EC: { if (ecdsa_compare_keys(p, remote_pkey, local_pkey) == 0) { res = TRUE; } else { res = FALSE; } break; } #endif /* PR_USE_OPENSSL_ECC */ default: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to compare %s keys: unsupported key type", get_pkey_type_desc(get_pkey_type(remote_pkey))); errno = ENOSYS; break; } } else if (remote_key_type == PROXY_SSH_KEY_ED25519 && remote_key_type == local_key_type) { #if defined(PR_USE_SODIUM) if (ed25519_compare_keys(p, remote_pubkey_data, remote_pubkey_datalen, local_pubkey_data, local_pubkey_datalen) == 0) { res = TRUE; } else { res = FALSE; } #endif /* PR_USE_SODIUM */ } else if (remote_key_type == PROXY_SSH_KEY_ED448 && remote_key_type == local_key_type) { #if defined(HAVE_X448_OPENSSL) if (ed448_compare_keys(p, remote_pubkey_data, remote_pubkey_datalen, local_pubkey_data, local_pubkey_datalen) == 0) { res = TRUE; } else { res = FALSE; } #endif /* HAVE_X448_OPENSSL */ } else { if (pr_trace_get_level(trace_channel) >= 17) { const char *remote_key_desc, *local_key_desc; remote_key_desc = get_key_type_desc(remote_key_type); local_key_desc = get_key_type_desc(local_key_type); pr_trace_msg(trace_channel, 17, "key mismatch: cannot compare %s key " "(client-sent) with %s key (local)", remote_key_desc, local_key_desc); } res = FALSE; } if (remote_pkey != NULL) { EVP_PKEY_free(remote_pkey); } if (local_pkey != NULL) { EVP_PKEY_free(local_pkey); } return res; } const char *proxy_ssh_keys_get_fingerprint(pool *p, unsigned char *key_data, uint32_t key_datalen, int digest_algo) { #if OPENSSL_VERSION_NUMBER < 0x10100000L || \ defined(HAVE_LIBRESSL) EVP_MD_CTX ctx; #endif /* prior to OpenSSL-1.1.0 */ EVP_MD_CTX *pctx; const EVP_MD *digest; char *digest_name = "none", *fp; unsigned char *fp_data; unsigned int fp_datalen = 0; register unsigned int i; switch (digest_algo) { case PROXY_SSH_KEYS_FP_DIGEST_MD5: digest = EVP_md5(); digest_name = "md5"; break; case PROXY_SSH_KEYS_FP_DIGEST_SHA1: digest = EVP_sha1(); digest_name = "sha1"; break; #if defined(HAVE_SHA256_OPENSSL) case PROXY_SSH_KEYS_FP_DIGEST_SHA256: digest = EVP_sha256(); digest_name = "sha256"; break; #endif /* HAVE_SHA256_OPENSSL */ default: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unsupported key fingerprint digest algorithm (%d)", digest_algo); errno = EACCES; return NULL; } #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) pctx = EVP_MD_CTX_new(); #else pctx = &ctx; #endif /* prior to OpenSSL-1.1.0 */ /* In OpenSSL 0.9.6, many of the EVP_Digest* functions returned void, not * int. Without these ugly OpenSSL version preprocessor checks, the * compiler will error out with "void value not ignored as it ought to be". */ #if OPENSSL_VERSION_NUMBER >= 0x000907000L if (EVP_DigestInit(pctx, digest) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error initializing %s digest: %s", digest_name, proxy_ssh_crypto_get_errors()); # if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) EVP_MD_CTX_free(pctx); # endif /* OpenSSL-1.1.0 and later */ errno = EPERM; return NULL; } #else EVP_DigestInit(pctx, digest); #endif #if OPENSSL_VERSION_NUMBER >= 0x000907000L if (EVP_DigestUpdate(pctx, key_data, key_datalen) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error updating %s digest: %s", digest_name, proxy_ssh_crypto_get_errors()); # if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) EVP_MD_CTX_free(pctx); # endif /* OpenSSL-1.1.0 and later */ errno = EPERM; return NULL; } #else EVP_DigestUpdate(pctx, key_data, key_datalen); #endif fp_data = palloc(p, EVP_MAX_MD_SIZE); #if OPENSSL_VERSION_NUMBER >= 0x000907000L if (EVP_DigestFinal(pctx, fp_data, &fp_datalen) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error finishing %s digest: %s", digest_name, proxy_ssh_crypto_get_errors()); # if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) EVP_MD_CTX_free(pctx); # endif /* OpenSSL-1.1.0 and later */ errno = EPERM; return NULL; } #else EVP_DigestFinal(pctx, fp_data, &fp_datalen); #endif #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) EVP_MD_CTX_free(pctx); #endif /* OpenSSL-1.1.0 and later */ /* Now encode that digest in fp_data as hex characters. */ fp = ""; for (i = 0; i < fp_datalen; i++) { char c[4]; memset(c, '\0', sizeof(c)); pr_snprintf(c, sizeof(c), "%02x:", fp_data[i]); fp = pstrcat(p, fp, &c, NULL); } fp[strlen(fp)-1] = '\0'; return fp; } #if defined(PR_USE_OPENSSL_ECC) /* Returns the NID for the configured EVP_PKEY_EC key. */ static int get_ecdsa_nid(EC_KEY *ec) { register unsigned int i; const EC_GROUP *key_group; EC_GROUP *new_group = NULL; BN_CTX *bn_ctx = NULL; int supported_ecdsa_nids[] = { NID_X9_62_prime256v1, NID_secp384r1, NID_secp521r1, -1 }; int nid; if (ec == NULL) { errno = EINVAL; return -1; } /* Since the EC group might be encoded in different ways, we need to try * different lookups to find the NID. * * First, we see if the EC group is encoded as a "named group" in the * private key. */ key_group = EC_KEY_get0_group(ec); nid = EC_GROUP_get_curve_name(key_group); if (nid > 0) { return nid; } /* Otherwise, we check to see if the group is encoded via explicit group * parameters in the private key. */ bn_ctx = BN_CTX_new(); if (bn_ctx == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error allocated BN_CTX: %s", proxy_ssh_crypto_get_errors()); return -1; } for (i = 0; supported_ecdsa_nids[i] != -1; i++) { new_group = EC_GROUP_new_by_curve_name(supported_ecdsa_nids[i]); if (new_group == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error creating new EC_GROUP by curve name %d: %s", supported_ecdsa_nids[i], proxy_ssh_crypto_get_errors()); BN_CTX_free(bn_ctx); return -1; } if (EC_GROUP_cmp(key_group, new_group, bn_ctx) == 0) { /* We have a match. */ break; } EC_GROUP_free(new_group); new_group = NULL; } BN_CTX_free(bn_ctx); if (supported_ecdsa_nids[i] != -1) { EC_GROUP_set_asn1_flag(new_group, OPENSSL_EC_NAMED_CURVE); if (EC_KEY_set_group(ec, new_group) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error setting EC group on key: %s", proxy_ssh_crypto_get_errors()); EC_GROUP_free(new_group); return -1; } EC_GROUP_free(new_group); } return supported_ecdsa_nids[i]; } #endif /* PR_USE_OPENSSL_ECC */ static int handle_hostkey(pool *p, EVP_PKEY *pkey, const unsigned char *key_data, uint32_t key_datalen, const char *file_path, const char *agent_path) { switch (get_pkey_type(pkey)) { case EVP_PKEY_RSA: { #if OPENSSL_VERSION_NUMBER < 0x0090702fL /* In OpenSSL-0.9.7a and later, RSA blinding is turned on by default. * Thus if our OpenSSL is older than that, manually enable RSA * blinding. */ RSA *rsa; rsa = EVP_PKEY_get1_RSA(pkey); if (rsa) { if (RSA_blinding_on(rsa, NULL) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error enabling RSA blinding for key '%s': %s", file_path ? file_path : agent_path, proxy_ssh_crypto_get_errors()); } else { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "RSA blinding enabled for key '%s'", file_path ? file_path : agent_path); } RSA_free(rsa); } #endif if (rsa_hostkey != NULL) { /* If we have an existing RSA hostkey, free it up. */ EVP_PKEY_free(rsa_hostkey->pkey); rsa_hostkey->pkey = NULL; rsa_hostkey->key_data = NULL; rsa_hostkey->key_datalen = 0; rsa_hostkey->file_path = NULL; rsa_hostkey->agent_path = NULL; } else { rsa_hostkey = pcalloc(p, sizeof(struct proxy_ssh_hostkey)); } rsa_hostkey->key_type = PROXY_SSH_KEY_RSA; rsa_hostkey->pkey = pkey; rsa_hostkey->key_data = key_data; rsa_hostkey->key_datalen = key_datalen; rsa_hostkey->file_path = file_path; rsa_hostkey->agent_path = agent_path; if (file_path != NULL) { pr_trace_msg(trace_channel, 4, "using '%s' as RSA hostkey", file_path); } else if (agent_path != NULL) { pr_trace_msg(trace_channel, 4, "using RSA hostkey from SSH agent at '%s'", agent_path); } break; } case EVP_PKEY_DSA: { if (dsa_hostkey != NULL) { /* If we have an existing DSA hostkey, free it up. */ EVP_PKEY_free(dsa_hostkey->pkey); dsa_hostkey->pkey = NULL; dsa_hostkey->key_data = NULL; dsa_hostkey->key_datalen = 0; dsa_hostkey->file_path = NULL; dsa_hostkey->agent_path = NULL; } else { dsa_hostkey = pcalloc(p, sizeof(struct proxy_ssh_hostkey)); } dsa_hostkey->key_type = PROXY_SSH_KEY_DSA; dsa_hostkey->pkey = pkey; dsa_hostkey->key_data = key_data; dsa_hostkey->key_datalen = key_datalen; dsa_hostkey->file_path = file_path; dsa_hostkey->agent_path = agent_path; if (file_path != NULL) { pr_trace_msg(trace_channel, 4, "using '%s' as DSA hostkey", file_path); } else if (agent_path != NULL) { pr_trace_msg(trace_channel, 4, "using DSA hostkey from SSH agent at '%s'", agent_path); } break; } #ifdef PR_USE_OPENSSL_ECC case EVP_PKEY_EC: { EC_KEY *ec; int ec_nid; ec = EVP_PKEY_get1_EC_KEY(pkey); ec_nid = get_ecdsa_nid(ec); if (ec_nid < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unsupported NID in EC key, ignoring"); EC_KEY_free(ec); EVP_PKEY_free(pkey); return -1; } if (proxy_ssh_keys_validate_ecdsa_params(EC_KEY_get0_group(ec), EC_KEY_get0_public_key(ec)) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error validating EC public key: %s", strerror(errno)); EC_KEY_free(ec); EVP_PKEY_free(pkey); return -1; } if (validate_ecdsa_private_key(ec)) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error validating EC private key: %s", strerror(errno)); EC_KEY_free(ec); EVP_PKEY_free(pkey); return -1; } EC_KEY_free(ec); switch (ec_nid) { case NID_X9_62_prime256v1: if (ecdsa256_hostkey != NULL) { /* If we have an existing 256-bit ECDSA hostkey, free it up. */ EVP_PKEY_free(ecdsa256_hostkey->pkey); ecdsa256_hostkey->pkey = NULL; ecdsa256_hostkey->key_data = NULL; ecdsa256_hostkey->key_datalen = 0; ecdsa256_hostkey->file_path = NULL; ecdsa256_hostkey->agent_path = NULL; } else { ecdsa256_hostkey = pcalloc(p, sizeof(struct proxy_ssh_hostkey)); } ecdsa256_hostkey->key_type = PROXY_SSH_KEY_ECDSA_256; ecdsa256_hostkey->pkey = pkey; ecdsa256_hostkey->key_data = key_data; ecdsa256_hostkey->key_datalen = key_datalen; ecdsa256_hostkey->file_path = file_path; ecdsa256_hostkey->agent_path = agent_path; if (file_path != NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "using '%s' as 256-bit ECDSA hostkey", file_path); } else if (agent_path != NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "using 256-bit ECDSA hostkey from SSH agent at '%s'", agent_path); } break; case NID_secp384r1: if (ecdsa384_hostkey != NULL) { /* If we have an existing 384-bit ECDSA hostkey, free it up. */ EVP_PKEY_free(ecdsa384_hostkey->pkey); ecdsa384_hostkey->pkey = NULL; ecdsa384_hostkey->key_data = NULL; ecdsa384_hostkey->key_datalen = 0; ecdsa384_hostkey->file_path = NULL; ecdsa384_hostkey->agent_path = NULL; } else { ecdsa384_hostkey = pcalloc(p, sizeof(struct proxy_ssh_hostkey)); } ecdsa384_hostkey->key_type = PROXY_SSH_KEY_ECDSA_384; ecdsa384_hostkey->pkey = pkey; ecdsa384_hostkey->key_data = key_data; ecdsa384_hostkey->key_datalen = key_datalen; ecdsa384_hostkey->file_path = file_path; ecdsa384_hostkey->agent_path = agent_path; if (file_path != NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "using '%s' as 384-bit ECDSA hostkey", file_path); } else if (agent_path != NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "using 384-bit ECDSA hostkey from SSH agent at '%s'", agent_path); } break; case NID_secp521r1: if (ecdsa521_hostkey != NULL) { /* If we have an existing 521-bit ECDSA hostkey, free it up. */ EVP_PKEY_free(ecdsa521_hostkey->pkey); ecdsa521_hostkey->pkey = NULL; ecdsa521_hostkey->key_data = NULL; ecdsa521_hostkey->key_datalen = 0; ecdsa521_hostkey->file_path = NULL; ecdsa521_hostkey->agent_path = NULL; } else { ecdsa521_hostkey = pcalloc(p, sizeof(struct proxy_ssh_hostkey)); } ecdsa521_hostkey->key_type = PROXY_SSH_KEY_ECDSA_521; ecdsa521_hostkey->pkey = pkey; ecdsa521_hostkey->key_data = key_data; ecdsa521_hostkey->key_datalen = key_datalen; ecdsa521_hostkey->file_path = file_path; ecdsa521_hostkey->agent_path = agent_path; if (file_path != NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "using '%s' as 521-bit ECDSA hostkey", file_path); } else if (agent_path != NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "using 521-bit hostkey from SSH agent at '%s'", agent_path); } break; } break; } #endif /* PR_USE_OPENSSL_ECC */ #if defined(HAVE_X448_OPENSSL) case EVP_PKEY_ED448: { unsigned char *privkey_data; size_t privkey_datalen; privkey_datalen = (CURVE448_SIZE * 2); privkey_data = palloc(p, privkey_datalen); if (EVP_PKEY_get_raw_private_key(pkey, privkey_data, &privkey_datalen) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error reading ED448 private key from '%s': %s", file_path, proxy_ssh_crypto_get_errors()); EVP_PKEY_free(pkey); return -1; } if (handle_ed448_hostkey(p, privkey_data, privkey_datalen, file_path) < 0) { EVP_PKEY_free(pkey); return -1; } break; } #endif /* HAVE_X448_OPENSSL */ default: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unknown private key type (%d), ignoring", get_pkey_type(pkey)); EVP_PKEY_free(pkey); return -1; } return 0; } static int load_agent_hostkeys(pool *p, const char *path) { register unsigned int i; int accepted_nkeys = 0, res; array_header *key_list; key_list = make_array(p, 0, sizeof(struct agent_key *)); res = proxy_ssh_agent_get_keys(p, path, key_list); if (res < 0) { int xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error loading hostkeys from SSH agent at '%s': %s", path, strerror(xerrno)); errno = xerrno; return -1; } if (key_list->nelts == 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "SSH agent at '%s' returned no keys", path); errno = ENOENT; return -1; } pr_trace_msg(trace_channel, 9, "processing %d keys from SSH agent at '%s'", key_list->nelts, path); for (i = 0; i < key_list->nelts; i++) { EVP_PKEY *pkey; uint32_t len; struct agent_key *agent_key; agent_key = ((struct agent_key **) key_list->elts)[i]; len = read_pkey_from_data(p, agent_key->key_data, agent_key->key_datalen, &pkey, NULL, FALSE); if (len == 0) { continue; } if (handle_hostkey(p, pkey, agent_key->key_data, agent_key->key_datalen, NULL, path) == 0) { accepted_nkeys++; } } if (accepted_nkeys == 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "none of the keys provided by the SSH agent at '%s' were acceptable", path); errno = EINVAL; return -1; } pr_trace_msg(trace_channel, 9, "loaded %d keys from SSH agent at '%s'", accepted_nkeys, path); /* Return the number of keys we successfully accept from the agent. */ return accepted_nkeys; } static struct openssh_cipher *get_openssh_cipher(const char *name) { register unsigned int i; struct openssh_cipher *cipher = NULL; for (i = 0; ciphers[i].algo != NULL; i++) { if (strcmp(ciphers[i].algo, name) == 0) { cipher = &ciphers[i]; break; } } if (cipher == NULL) { errno = ENOENT; return NULL; } if (cipher->get_cipher != NULL) { cipher->cipher = (cipher->get_cipher)(); if (cipher->cipher != NULL) { return cipher; } } /* The CTR algorithms may require our own implementation, not the OpenSSL * implementation. */ cipher->cipher = proxy_ssh_crypto_get_cipher(name, NULL, NULL, NULL); if (cipher->cipher == NULL) { errno = ENOSYS; return NULL; } return cipher; } static int decrypt_openssh_data(pool *p, const char *path, unsigned char *encrypted_data, uint32_t encrypted_len, const char *passphrase, struct openssh_cipher *cipher, const char *kdf_name, unsigned char *kdf_data, uint32_t kdf_len, unsigned char **decrypted_data, uint32_t *decrypted_len) { EVP_CIPHER_CTX *cipher_ctx = NULL; unsigned char *buf, *key, *iv, *salt_data; uint32_t buflen, key_len, rounds, salt_len, len = 0; size_t passphrase_len; if (strcmp(kdf_name, "none") == 0) { *decrypted_data = encrypted_data; *decrypted_len = encrypted_len; return 0; } if (strcmp(kdf_name, "bcrypt") != 0) { pr_trace_msg(trace_channel, 3, "'%s' key uses unsupported %s KDF", path, kdf_name); errno = ENOSYS; return -1; } len = proxy_ssh_msg_read_int(p, &kdf_data, &kdf_len, &salt_len); len = proxy_ssh_msg_read_data(p, &kdf_data, &kdf_len, salt_len, &salt_data); len = proxy_ssh_msg_read_int(p, &kdf_data, &kdf_len, &rounds); pr_trace_msg(trace_channel, 9, "'%s' key %s KDF using %lu bytes of salt, %lu rounds", path, kdf_name, (unsigned long) salt_len, (unsigned long) rounds); /* Compute the decryption key using the KDF and the passphrase. Note that * we derive the key AND the IV using this approach at the same time. */ passphrase_len = strlen(passphrase); key_len = cipher->key_len + cipher->iv_len; pr_trace_msg(trace_channel, 13, "generating %s decryption key using %s KDF (key len = %lu, IV len = %lu)", cipher->algo, kdf_name, (unsigned long) cipher->key_len, (unsigned long) cipher->iv_len); key = pcalloc(p, key_len); if (proxy_ssh_bcrypt_pbkdf2(p, passphrase, passphrase_len, salt_data, salt_len, rounds, key, key_len) < 0) { pr_trace_msg(trace_channel, 3, "error computing key using %s KDF: %s", kdf_name, strerror(errno)); errno = EPERM; return -1; } if (cipher->iv_len > 0) { iv = key + cipher->key_len; } else { iv = NULL; } cipher_ctx = EVP_CIPHER_CTX_new(); if (cipher_ctx == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error allocating cipher context: %s", proxy_ssh_crypto_get_errors()); errno = EPERM; return -1; } #if OPENSSL_VERSION_NUMBER < 0x10100000L || \ defined(HAVE_LIBRESSL) EVP_CIPHER_CTX_init(cipher_ctx); #endif #if defined(PR_USE_OPENSSL_EVP_CIPHERINIT_EX) if (EVP_CipherInit_ex(cipher_ctx, cipher->cipher, NULL, key, iv, 0) != 1) { #else if (EVP_CipherInit(cipher_ctx, cipher->cipher, key, iv, 0) != 1) { #endif /* PR_USE_OPENSSL_EVP_CIPHERINIT_EX */ pr_trace_msg(trace_channel, 3, "error initializing %s cipher for decryption: %s", cipher->algo, proxy_ssh_crypto_get_errors()); EVP_CIPHER_CTX_free(cipher_ctx); pr_memscrub(key, key_len); errno = EPERM; return -1; } if (cipher->key_len > 0) { if (EVP_CIPHER_CTX_set_key_length(cipher_ctx, cipher->key_len) != 1) { pr_trace_msg(trace_channel, 3, "error setting key length (%lu bytes) for %s cipher for decryption: %s", (unsigned long) cipher->key_len, cipher->algo, proxy_ssh_crypto_get_errors()); #if OPENSSL_VERSION_NUMBER < 0x10100000L EVP_CIPHER_CTX_cleanup(cipher_ctx); #endif /* prior to OpenSSL-1.1.0 */ EVP_CIPHER_CTX_free(cipher_ctx); pr_memscrub(key, key_len); errno = EPERM; return -1; } } buflen = encrypted_len; buf = pcalloc(p, buflen); /* TODO: this currently works because our data does NOT contain any extra * trailing AEAD bytes. Need to fix that in the future. */ if (EVP_Cipher(cipher_ctx, buf, encrypted_data, encrypted_len) < 0) { /* This might happen due to a wrong/bad passphrase. */ pr_trace_msg(trace_channel, 3, "error decrypting %s data for key: %s", cipher->algo, proxy_ssh_crypto_get_errors()); #if OPENSSL_VERSION_NUMBER < 0x10100000L EVP_CIPHER_CTX_cleanup(cipher_ctx); #endif /* prior to OpenSSL-1.1.0 */ EVP_CIPHER_CTX_free(cipher_ctx); pr_memscrub(key, key_len); pr_memscrub(buf, buflen); errno = EPERM; return -1; } #if OPENSSL_VERSION_NUMBER < 0x10100000L EVP_CIPHER_CTX_cleanup(cipher_ctx); #endif /* prior to OpenSSL-1.1.0 */ EVP_CIPHER_CTX_free(cipher_ctx); pr_memscrub(key, key_len); *decrypted_data = buf; *decrypted_len = buflen; return 0; } /* See openssh-7.9p1/sshkey.c#sshkey_from_blob_internal(). */ static int deserialize_openssh_private_key(pool *p, const char *path, unsigned char **data, uint32_t *data_len, enum proxy_ssh_key_type_e *key_type, EVP_PKEY **pkey, unsigned char **key, uint32_t *keylen) { uint32_t len = 0, public_keylen = 0, secret_keylen = 0; const char *pkey_type; unsigned char *public_key = NULL, *secret_key = NULL; int have_extra_public_key = FALSE; len = read_pkey_from_data(p, *data, *data_len, pkey, key_type, TRUE); if (len == 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unsupported key type %d found in '%s'", *key_type, path); errno = EPERM; return -1; } /* Advance our pointers for all of the data read from them. */ (*data) += len; (*data_len) -= len; switch (*key_type) { case PROXY_SSH_KEY_DSA: case PROXY_SSH_KEY_RSA: case PROXY_SSH_KEY_ECDSA_256: case PROXY_SSH_KEY_ECDSA_384: case PROXY_SSH_KEY_ECDSA_521: case PROXY_SSH_KEY_RSA_SHA256: case PROXY_SSH_KEY_RSA_SHA512: return 0; case PROXY_SSH_KEY_ED25519: pkey_type = "ssh-ed25519"; break; case PROXY_SSH_KEY_ED448: pkey_type = "ssh-ed448"; break; case PROXY_SSH_KEY_UNKNOWN: default: *key = NULL; *keylen = 0; return 0; } len = proxy_ssh_msg_read_int(p, data, data_len, &public_keylen); len = proxy_ssh_msg_read_data(p, data, data_len, public_keylen, &public_key); if (public_key == NULL) { pr_trace_msg(trace_channel, 2, "error reading %s key: invalid/supported key format", pkey_type); errno = EINVAL; return -1; } len = proxy_ssh_msg_read_int(p, data, data_len, &secret_keylen); /* NOTE: PuTTY's puttygen adds the public key _again_, in the second half * of the secret key data, per commments in its * `sshecc.c#eddsa_new_priv_openssh` function. Thus if this secret key * length is larger than expected for Ed448 keys, only use the first half of * it. Ugh. This "divide in half" hack only works for these keys where the * private and public key sizes are the same. */ #if defined(HAVE_X448_OPENSSL) && defined(HAVE_SHA512_OPENSSL) if (*key_type == PROXY_SSH_KEY_ED448) { if (secret_keylen > (CURVE448_SIZE + 1)) { have_extra_public_key = TRUE; secret_keylen /= 2; } } #endif /* HAVE_X448_OPENSSL and HAVE_SHA512_OPENSSL */ len = proxy_ssh_msg_read_data(p, data, data_len, secret_keylen, &secret_key); if (secret_key == NULL) { pr_trace_msg(trace_channel, 2, "error reading %s key: invalid/supported key format", pkey_type); errno = EINVAL; return -1; } if (have_extra_public_key == TRUE) { unsigned char *extra_data = NULL; /* Read (and ignore) the rest of the secret data */ (void) proxy_ssh_msg_read_data(p, data, data_len, secret_keylen, &extra_data); } /* The secret key is what we need to extract. */ *key = secret_key; *keylen = secret_keylen; return 0; } static int decrypt_openssh_private_key(pool *p, const char *path, unsigned char *encrypted_data, uint32_t encrypted_len, const char *passphrase, struct openssh_cipher *cipher, const char *kdf_name, unsigned char *kdf_data, uint32_t kdf_len, enum proxy_ssh_key_type_e *key_type, EVP_PKEY **pkey, unsigned char **key, uint32_t *keylen) { unsigned char *decrypted_data = NULL, *decrypted_ptr = NULL; uint32_t check_bytes[2], decrypted_len = 0, decrypted_sz = 0, len = 0; char *comment = NULL; int res; unsigned int i = 0; res = decrypt_openssh_data(p, path, encrypted_data, encrypted_len, passphrase, cipher, kdf_name, kdf_data, kdf_len, &decrypted_data, &decrypted_len); if (res < 0) { pr_trace_msg(trace_channel, 6, "failed to decrypt '%s' using %s cipher: %s", path, cipher->algo, strerror(errno)); errno = EINVAL; return -1; } pr_trace_msg(trace_channel, 14, "decrypted %lu bytes into %lu bytes", (unsigned long) encrypted_len, (unsigned long) decrypted_len); decrypted_ptr = decrypted_data; decrypted_sz = decrypted_len; proxy_ssh_msg_read_int(p, &decrypted_data, &decrypted_len, &(check_bytes[0])); proxy_ssh_msg_read_int(p, &decrypted_data, &decrypted_len, &(check_bytes[1])); if (check_bytes[0] != check_bytes[1]) { pr_trace_msg(trace_channel, 6, "'%s' has mismatched check bytes (%lu != %lu); wrong passphrase", path, (unsigned long) check_bytes[0], (unsigned long) check_bytes[1]); (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to read hostkey '%s': wrong passphrase", path); pr_memscrub(decrypted_ptr, decrypted_sz); errno = EINVAL; return -1; } res = deserialize_openssh_private_key(p, path, &decrypted_data, &decrypted_len, key_type, pkey, key, keylen); if (res < 0) { int xerrno = errno; pr_memscrub(decrypted_ptr, decrypted_sz); errno = xerrno; return -1; } len = proxy_ssh_msg_read_string(p, &decrypted_data, &decrypted_len, &comment); if (comment != NULL) { pr_trace_msg(trace_channel, 9, "'%s' comment: '%s'", path, comment); } /* Verify the expected remaining padding. */ for (i = 1; decrypted_len > 0; i++) { unsigned char padding; pr_signals_handle(); len = proxy_ssh_msg_read_byte(p, &decrypted_data, &decrypted_len, &padding); if (padding != (i & 0xFF)) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "'%s' key has invalid padding", path); pr_memscrub(decrypted_ptr, decrypted_sz); errno = EINVAL; return -1; } } pr_memscrub(decrypted_ptr, decrypted_sz); return 0; } static int unwrap_openssh_private_key(pool *p, const char *path, unsigned char *text, size_t text_len, const char *passphrase, enum proxy_ssh_key_type_e *key_type, EVP_PKEY **pkey, unsigned char **key, uint32_t *keylen) { char *cipher_name, *kdf_name, *tmp; unsigned char *buf, *data = NULL, *kdf_data, *encrypted_data; size_t data_len = 0, magicsz; uint32_t buflen, kdf_len = 0, key_count = 0, encrypted_len = 0, len = 0; struct openssh_cipher *cipher = NULL; int xerrno = 0; data = decode_base64(p, text, text_len, &data_len); xerrno = errno; if (data == NULL) { pr_trace_msg(trace_channel, 6, "error base64-decoding key '%s': %s", path, strerror(xerrno)); errno = xerrno; return -1; } magicsz = sizeof(PROXY_SSH_OPENSSH_MAGIC); if (data_len < magicsz) { pr_trace_msg(trace_channel, 6, "'%s' key base64-decoded data too short (%lu bytes < %lu minimum " "required)", path, (unsigned long) data_len, (unsigned long) magicsz); errno = EINVAL; return -1; } if (memcmp(data, PROXY_SSH_OPENSSH_MAGIC, magicsz) != 0) { pr_trace_msg(trace_channel, 6, "'%s' key base64-decoded contains invalid magic value", path); errno = EINVAL; return -1; } data += magicsz; data_len -= magicsz; buf = data; buflen = data_len; len = proxy_ssh_msg_read_string(p, &buf, &buflen, &cipher_name); len = proxy_ssh_msg_read_string(p, &buf, &buflen, &kdf_name); len = proxy_ssh_msg_read_int(p, &buf, &buflen, &kdf_len); len = proxy_ssh_msg_read_data(p, &buf, &buflen, kdf_len, &kdf_data); len = proxy_ssh_msg_read_int(p, &buf, &buflen, &key_count); /* Ignore the public key */ (void) proxy_ssh_msg_read_string(p, &buf, &buflen, &tmp); len = proxy_ssh_msg_read_int(p, &buf, &buflen, &encrypted_len); pr_trace_msg(trace_channel, 9, "'%s' key cipher = '%s', KDF = '%s' (%lu bytes KDF data), " "key count = %lu, (%lu bytes encrypted data)", path, cipher_name, kdf_name, (unsigned long) kdf_len, (unsigned long) key_count, (unsigned long) encrypted_len); cipher = get_openssh_cipher(cipher_name); if (cipher == NULL) { pr_trace_msg(trace_channel, 6, "'%s' key uses unexpected/unsupported cipher (%s)", path, cipher_name); errno = EPERM; return -1; } if ((passphrase == NULL || strlen(passphrase) == 0) && strcmp(cipher_name, "none") != 0) { pr_trace_msg(trace_channel, 6, "'%s' key requires passphrase for cipher (%s)", path, cipher_name); errno = EPERM; return -1; } /* We only support the "none" and "bcrypt" KDFs at present. */ if (strcmp(kdf_name, "bcrypt") != 0 && strcmp(kdf_name, "none") != 0) { pr_trace_msg(trace_channel, 6, "'%s' key encrypted using unsupported KDF '%s'", path, kdf_name); errno = EPERM; return -1; } /* If our KDF is "none" and our cipher is NOT "none", we have a problem. */ if (strcmp(kdf_name, "none") == 0 && strcmp(cipher_name, "none") != 0) { pr_trace_msg(trace_channel, 6, "'%s' key encrypted using mismatched KDF and cipher algorithms: " "KDF '%s', cipher '%s'", path, kdf_name, cipher_name); errno = EPERM; return -1; } /* OpenSSH only supports one key at present. Huh. */ if (key_count != 1) { pr_trace_msg(trace_channel, 6, "'%s' key includes unexpected/unsupported key count (%lu)", path, (unsigned long) key_count); errno = EPERM; return -1; } /* XXX Should we enforce that the KDF data be empty for the "none" KDF? */ if (strcmp(kdf_name, "none") == 0 && kdf_len > 0) { pr_trace_msg(trace_channel, 6, "'%s' key uses KDF 'none', but contains unexpected %lu bytes " "of KDF options", path, (unsigned long) kdf_len); } if (buflen < encrypted_len) { pr_trace_msg(trace_channel, 6, "'%s' key declares %lu bytes of encrypted data, but has " "only %lu bytes remaining", path, (unsigned long) encrypted_len, (unsigned long) buflen); errno = EPERM; return -1; } if (encrypted_len < cipher->blocksz || (encrypted_len % cipher->blocksz) != 0) { pr_trace_msg(trace_channel, 6, "'%s' key declares %lu bytes of encrypted data, which is invalid for " "the %s cipher block size (%lu bytes)", path, (unsigned long) encrypted_len, cipher_name, (unsigned long) cipher->blocksz); errno = EPERM; return -1; } if (buflen < (encrypted_len + cipher->auth_len)) { pr_trace_msg(trace_channel, 6, "'%s' key declares %lu bytes of encrypted data and %lu bytes of auth " "data, but has only %lu bytes remaining", path, (unsigned long) encrypted_len, (unsigned long) cipher->auth_len, (unsigned long) buflen); errno = EPERM; return -1; } len = proxy_ssh_msg_read_data(p, &buf, &buflen, encrypted_len, &encrypted_data); #if 0 if (cipher->auth_len > 0) { /* Read (and ignore) any auth data (for AEAD ciphers). */ (void) proxy_ssh_msg_read_data(p, &encrypted_data, &encrypted_len, cipher->auth_len, NULL); } /* We should have used all of the encrypted data, with none left over. */ if (encrypted_len != 0) { pr_trace_msg(trace_channel, 3, "'%s' key provided too much data (%lu bytes remaining unexpectedly)", path, (unsigned long) encrypted_len); pr_memscrub(buf, buflen); errno = EINVAL; return -1; } #endif return decrypt_openssh_private_key(p, path, encrypted_data, encrypted_len, passphrase, cipher, kdf_name, kdf_data, kdf_len, key_type, pkey, key, keylen); } static int read_openssh_private_key(pool *p, const char *path, int fd, const char *passphrase, enum proxy_ssh_key_type_e *key_type, EVP_PKEY **pkey, unsigned char **key, uint32_t *keylen) { struct stat st; pool *tmp_pool; unsigned char *decoded_buf, *decoded_ptr, *input_buf, *input_ptr; unsigned char *tmp_key = NULL; int res, xerrno = 0; size_t decoded_len, input_len; off_t input_sz; if (p == NULL || path == NULL || fd < 0 || key == NULL || keylen == NULL) { errno = EINVAL; return -1; } if (fstat(fd, &st) < 0) { return -1; } tmp_pool = make_sub_pool(p); /* Read the entire file into memory. */ /* TODO: Impose maximum size limit for this treatment? */ input_sz = st.st_size; input_ptr = input_buf = palloc(tmp_pool, input_sz); input_len = 0; res = read(fd, input_buf, input_sz); xerrno = errno; while (res != 0) { pr_signals_handle(); if (res < 0) { pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": error reading '%s': %s", path, strerror(xerrno)); destroy_pool(tmp_pool); errno = xerrno; return -1; } input_buf += res; input_len += res; input_sz -= res; res = read(fd, input_buf, input_sz); xerrno = errno; } input_buf = input_ptr; /* Now we read from the input buffer into a buffer for decoding, for use * in the unwrapping process. */ decoded_ptr = decoded_buf = palloc(tmp_pool, input_len); decoded_len = 0; /* We know (due to the OpenSSH private key check) that the first bytes * match the expected start, and that the last bytes match the expected end. * So skip past them. */ input_buf += PROXY_SSH_OPENSSH_BEGIN_LEN; input_len -= (PROXY_SSH_OPENSSH_BEGIN_LEN + PROXY_SSH_OPENSSH_END_LEN); while (input_len > 0) { char ch; pr_signals_handle(); ch = *input_buf; /* Skip whitespace */ if (ch != '\r' && ch != '\n') { *decoded_buf++ = ch; decoded_len++; } input_buf++; input_len--; } res = unwrap_openssh_private_key(tmp_pool, path, decoded_ptr, decoded_len, passphrase, key_type, pkey, &tmp_key, keylen); xerrno = errno; if (res < 0) { destroy_pool(tmp_pool); errno = xerrno; return -1; } /* At this point, our unwrapped key is allocated out of the temporary pool. * We need to copy it to memory out of the longer-lived pool given to us. */ if (*keylen > 0) { *key = palloc(p, *keylen); memcpy(*key, tmp_key, *keylen); pr_memscrub(tmp_key, *keylen); } destroy_pool(tmp_pool); return 0; } #if defined(PR_USE_SODIUM) static int handle_ed25519_hostkey(pool *p, const unsigned char *key_data, uint32_t key_datalen, const char *file_path) { unsigned char *public_key; if (ed25519_hostkey != NULL) { /* If we have an existing ED25519 hostkey, free it up. */ pr_memscrub(ed25519_hostkey->ed25519_secret_key, ed25519_hostkey->ed25519_secret_keylen); ed25519_hostkey->ed25519_secret_key = NULL; ed25519_hostkey->ed25519_secret_keylen = 0; pr_memscrub(ed25519_hostkey->ed25519_public_key, ed25519_hostkey->ed25519_public_keylen); ed25519_hostkey->ed25519_public_key = NULL; ed25519_hostkey->ed25519_public_keylen = 0; ed25519_hostkey->file_path = NULL; ed25519_hostkey->agent_path = NULL; } else { ed25519_hostkey = pcalloc(p, sizeof(struct proxy_ssh_hostkey)); } ed25519_hostkey->key_type = PROXY_SSH_KEY_ED25519; ed25519_hostkey->ed25519_secret_key = (unsigned char *) key_data; ed25519_hostkey->ed25519_secret_keylen = key_datalen; /* Use the secret key to get the public key. */ public_key = palloc(p, crypto_sign_ed25519_PUBLICKEYBYTES); if (crypto_sign_ed25519_sk_to_pk(public_key, key_data) != 0) { return -1; } ed25519_hostkey->ed25519_public_key = public_key; ed25519_hostkey->ed25519_public_keylen = crypto_sign_ed25519_PUBLICKEYBYTES; ed25519_hostkey->file_path = file_path; pr_trace_msg(trace_channel, 4, "using '%s' as Ed25519 hostkey", file_path); return 0; } #endif /* PR_USE_SODIUM */ #if defined(HAVE_X448_OPENSSL) static int handle_ed448_hostkey(pool *p, const unsigned char *key_data, uint32_t key_datalen, const char *file_path) { unsigned char *public_key; EVP_PKEY *pkey = NULL; size_t public_keylen = 0; if (ed448_hostkey != NULL) { /* If we have an existing ED448 hostkey, free it up. */ pr_memscrub(ed448_hostkey->ed448_secret_key, ed448_hostkey->ed448_secret_keylen); ed448_hostkey->ed448_secret_key = NULL; ed448_hostkey->ed448_secret_keylen = 0; pr_memscrub(ed448_hostkey->ed448_public_key, ed448_hostkey->ed448_public_keylen); ed448_hostkey->ed448_public_key = NULL; ed448_hostkey->ed448_public_keylen = 0; ed448_hostkey->file_path = NULL; ed448_hostkey->agent_path = NULL; } else { ed448_hostkey = pcalloc(p, sizeof(struct proxy_ssh_hostkey)); } ed448_hostkey->key_type = PROXY_SSH_KEY_ED448; ed448_hostkey->ed448_secret_key = (unsigned char *) key_data; ed448_hostkey->ed448_secret_keylen = key_datalen; pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED448, NULL, ed448_hostkey->ed448_secret_key, ed448_hostkey->ed448_secret_keylen); if (pkey == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error initializing Ed448 private key: %s", proxy_ssh_crypto_get_errors()); return -1; } /* Use the secret key to get the public key. */ public_keylen = (CURVE448_SIZE * 2); public_key = palloc(p, public_keylen); if (EVP_PKEY_get_raw_public_key(pkey, public_key, &public_keylen) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error obtaining Ed448 public key: %s", proxy_ssh_crypto_get_errors()); EVP_PKEY_free(pkey); return -1; } EVP_PKEY_free(pkey); ed448_hostkey->ed448_public_key = public_key; ed448_hostkey->ed448_public_keylen = public_keylen; ed448_hostkey->file_path = file_path; pr_trace_msg(trace_channel, 4, "using '%s' as Ed448 hostkey", file_path); return 0; } #endif /* HAVE_X448_OPENSSL */ static int load_openssh_hostkey(pool *p, const char *path, int fd) { const char *passphrase = NULL; enum proxy_ssh_key_type_e key_type = PROXY_SSH_KEY_UNKNOWN; EVP_PKEY *pkey = NULL; unsigned char *key = NULL; uint32_t keylen = 0; int res; if (client_pkey != NULL) { passphrase = client_pkey->client_pkey; } res = read_openssh_private_key(p, path, fd, passphrase, &key_type, &pkey, &key, &keylen); if (res < 0) { return -1; } switch (key_type) { #if defined(PR_USE_SODIUM) case PROXY_SSH_KEY_ED25519: res = handle_ed25519_hostkey(p, key, keylen, path); break; #endif /* PR_USE_SODIUM */ #if defined(HAVE_X448_OPENSSL) case PROXY_SSH_KEY_ED448: res = handle_ed448_hostkey(p, key, keylen, path); break; #endif /* HAVE_X448_OPENSSL */ default: res = handle_hostkey(p, pkey, NULL, 0, path, NULL); break; } return res; } static int load_file_hostkey(pool *p, const char *path) { int fd, xerrno = 0, openssh_format = FALSE, public_key_format = FALSE; pool *tmp_pool = NULL; BIO *bio = NULL; EVP_PKEY *pkey; pr_signals_block(); PRIVS_ROOT /* XXX Would we ever want to allow client keys to be read from FIFOs? If * so, we would need to include the O_NONBLOCK flag here. */ fd = open(path, O_RDONLY, 0); xerrno = errno; PRIVS_RELINQUISH pr_signals_unblock(); if (fd < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error reading '%s': %s", path, strerror(xerrno)); errno = xerrno; return -1; } if (has_req_perms(fd, path) < 0) { xerrno = errno; if (xerrno == EACCES) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "'%s' is accessible by group or world, which is not allowed", path); } else { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error checking '%s' perms: %s", path, strerror(xerrno)); } (void) close(fd); errno = xerrno; return -1; } if (client_pkey == NULL) { client_pkey = lookup_pkey(); } /* Make sure this is not a public key inadvertently configured as a hostkey. */ public_key_format = is_public_key(fd); if (public_key_format == TRUE) { pr_trace_msg(trace_channel, 3, "hostkey file '%s' uses a public key format", path); (void) pr_log_pri(PR_LOG_WARNING, MOD_PROXY_VERSION ": unable to use public key '%s' for ProxySFTPHostKey", path); (void) close(fd); errno = EINVAL; return -1; } /* If this happens to be in the OpenSSH private key format, handle it * separately. */ openssh_format = is_openssh_private_key(fd); if (openssh_format == TRUE) { int res; pr_trace_msg(trace_channel, 9, "hostkey file '%s' uses OpenSSH key format", path); res = load_openssh_hostkey(p, path, fd); xerrno = errno; (void) close(fd); errno = xerrno; return res; } tmp_pool = make_sub_pool(p); pr_pool_tag(tmp_pool, "Proxy SSH hostkey BIO pool"); /* Rather than using OpenSSL's PEM_read_PrivateKey and the underlying C * library's FILE routines, dealing with unbuffered file handles and * byte-by-byte reads from OpenSSL, we instead provision the file data * into a memory BIO, and let OpenSSL read from that. * * This allows OpenSSL to maintain its byte-by-byte reads, while we read * the file data using filesystem block-sized reads. */ bio = load_hostkey_bio(tmp_pool, fd); if (bio == NULL) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error reading data from fd %d: %s", fd, strerror(xerrno)); (void) close(fd); destroy_pool(tmp_pool); errno = xerrno; return -1; } if (client_pkey != NULL) { pkey = PEM_read_bio_PrivateKey(bio, NULL, pkey_cb, (void *) client_pkey); } else { /* Assume that the key is not passphrase-protected. */ pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, ""); } free_hostkey_bio(bio); if (pkey == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error reading private key from '%s': %s", path, proxy_ssh_crypto_get_errors()); return -1; } return handle_hostkey(p, pkey, NULL, 0, path, NULL); } int proxy_ssh_keys_get_hostkey(pool *p, const char *path) { int res; /* Check whether we are to load keys from a file on disk, or from an * SSH agent. */ if (strncmp(path, "agent:", 6) != 0) { pr_trace_msg(trace_channel, 9, "loading client key from file '%s'", path); res = load_file_hostkey(p, path); } else { const char *agent_path; /* Skip past the "agent:" prefix. */ agent_path = (path + 6); pr_trace_msg(trace_channel, 9, "loading client keys from SSH agent at '%s'", agent_path); res = load_agent_hostkeys(p, agent_path); } return res; } static int get_rsa_hostkey_data(pool *p, const char *key_algo, unsigned char **buf, unsigned char **ptr, uint32_t *buflen) { RSA *rsa; const BIGNUM *rsa_n = NULL, *rsa_e = NULL; rsa = EVP_PKEY_get1_RSA(rsa_hostkey->pkey); if (rsa == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error using RSA hostkey: %s", proxy_ssh_crypto_get_errors()); return -1; } /* XXX Is this buffer large enough? Too large? */ *ptr = *buf = palloc(p, *buflen); proxy_ssh_msg_write_string(buf, buflen, key_algo); #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) RSA_get0_key(rsa, &rsa_n, &rsa_e, NULL); #else rsa_e = rsa->e; rsa_n = rsa->n; #endif /* prior to OpenSSL-1.1.0 */ proxy_ssh_msg_write_mpint(buf, buflen, rsa_e); proxy_ssh_msg_write_mpint(buf, buflen, rsa_n); RSA_free(rsa); return 0; } #if !defined(OPENSSL_NO_DSA) static int get_dsa_hostkey_data(pool *p, unsigned char **buf, unsigned char **ptr, uint32_t *buflen) { DSA *dsa; const BIGNUM *dsa_p = NULL, *dsa_q = NULL, *dsa_g = NULL, *dsa_pub_key = NULL; dsa = EVP_PKEY_get1_DSA(dsa_hostkey->pkey); if (dsa == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error using DSA hostkey: %s", proxy_ssh_crypto_get_errors()); return -1; } /* XXX Is this buffer large enough? Too large? */ *ptr = *buf = palloc(p, *buflen); proxy_ssh_msg_write_string(buf, buflen, "ssh-dss"); #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) DSA_get0_pqg(dsa, &dsa_p, &dsa_q, &dsa_g); DSA_get0_key(dsa, &dsa_pub_key, NULL); #else dsa_p = dsa->p; dsa_q = dsa->q; dsa_g = dsa->g; dsa_pub_key = dsa->pub_key;; #endif /* prior to OpenSSL-1.1.0 */ proxy_ssh_msg_write_mpint(buf, buflen, dsa_p); proxy_ssh_msg_write_mpint(buf, buflen, dsa_q); proxy_ssh_msg_write_mpint(buf, buflen, dsa_g); proxy_ssh_msg_write_mpint(buf, buflen, dsa_pub_key); DSA_free(dsa); return 0; } #endif /* !OPENSSL_NO_DSA */ #if defined(PR_USE_OPENSSL_ECC) static int get_ecdsa_hostkey_data(pool *p, struct proxy_ssh_hostkey *hostkey, const char *algo, const char *curve, unsigned char **buf, unsigned char **ptr, uint32_t *buflen) { EC_KEY *ec; ec = EVP_PKEY_get1_EC_KEY(hostkey->pkey); if (ec == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error using %s hostkey: %s", algo, proxy_ssh_crypto_get_errors()); return -1; } /* XXX Is this buffer large enough? Too large? */ *ptr = *buf = palloc(p, *buflen); proxy_ssh_msg_write_string(buf, buflen, algo); proxy_ssh_msg_write_string(buf, buflen, curve); proxy_ssh_msg_write_ecpoint(buf, buflen, EC_KEY_get0_group(ec), EC_KEY_get0_public_key(ec)); EC_KEY_free(ec); return 0; } #endif /* PR_USE_OPENSSL_ECC */ #if defined(PR_USE_SODIUM) static int get_ed25519_hostkey_data(pool *p, unsigned char **buf, unsigned char **ptr, uint32_t *buflen) { /* XXX Is this buffer large enough? Too large? */ *ptr = *buf = palloc(p, *buflen); proxy_ssh_msg_write_string(buf, buflen, "ssh-ed25519"); proxy_ssh_msg_write_data(buf, buflen, ed25519_hostkey->ed25519_public_key, ed25519_hostkey->ed25519_public_keylen, TRUE); return 0; } #endif /* PR_USE_SODIUM */ #if defined(HAVE_X448_OPENSSL) static int get_ed448_hostkey_data(pool *p, unsigned char **buf, unsigned char **ptr, uint32_t *buflen) { /* XXX Is this buffer large enough? Too large? */ *ptr = *buf = palloc(p, *buflen); proxy_ssh_msg_write_string(buf, buflen, "ssh-ed448"); proxy_ssh_msg_write_data(buf, buflen, ed448_hostkey->ed448_public_key, ed448_hostkey->ed448_public_keylen, TRUE); return 0; } #endif /* HAVE_X448_OPENSSL */ int proxy_ssh_keys_have_hostkey(enum proxy_ssh_key_type_e key_type) { /* If the requested type is PROXY_SSH_KEY_UNKNOWN, the caller is asking * if we have any hostkeys configured at all, regardless of type. */ if (key_type == PROXY_SSH_KEY_UNKNOWN) { if (dsa_hostkey != NULL || rsa_hostkey != NULL) { return 0; } #if defined(PR_USE_OPENSSL_ECC) if (ecdsa256_hostkey != NULL || ecdsa384_hostkey != NULL || ecdsa521_hostkey != NULL) { return 0; } #endif /* PR_USE_OPENSSL_ECC */ #if defined(PR_USE_SODIUM) if (ed25519_hostkey != NULL) { return 0; } #endif /* PR_USE_SODIUM */ #if defined(HAVE_X448_OPENSSL) if (ed448_hostkey != NULL) { return 0; } #endif /* HAVE_X448_OPENSSL */ errno = ENOENT; return -1; } switch (key_type) { case PROXY_SSH_KEY_DSA: if (dsa_hostkey != NULL) { return 0; } break; case PROXY_SSH_KEY_RSA: case PROXY_SSH_KEY_RSA_SHA256: case PROXY_SSH_KEY_RSA_SHA512: if (rsa_hostkey != NULL) { return 0; } break; #if defined(PR_USE_OPENSSL_ECC) case PROXY_SSH_KEY_ECDSA_256: if (ecdsa256_hostkey != NULL) { return 0; } break; case PROXY_SSH_KEY_ECDSA_384: if (ecdsa384_hostkey != NULL) { return 0; } break; case PROXY_SSH_KEY_ECDSA_521: if (ecdsa521_hostkey != NULL) { return 0; } break; #endif /* PR_USE_OPENSSL_ECC */ #if defined(PR_USE_SODIUM) case PROXY_SSH_KEY_ED25519: if (ed25519_hostkey != NULL) { return 0; } break; #endif /* PR_USE_SODIUM */ #if defined(HAVE_X448_OPENSSL) case PROXY_SSH_KEY_ED448: if (ed448_hostkey != NULL) { return 0; } break; #endif /* HAVE_X448_OPENSSL */ default: break; } errno = ENOENT; return -1; } const unsigned char *proxy_ssh_keys_get_hostkey_data(pool *p, enum proxy_ssh_key_type_e key_type, uint32_t *datalen) { unsigned char *buf = NULL, *ptr = NULL; uint32_t buflen = PROXY_SSH_DEFAULT_HOSTKEY_SZ; int res; switch (key_type) { case PROXY_SSH_KEY_RSA: case PROXY_SSH_KEY_RSA_SHA256: case PROXY_SSH_KEY_RSA_SHA512: { const char *key_algo = "ssh-rsa"; if (key_type == PROXY_SSH_KEY_RSA_SHA256) { key_algo = "rsa-sha2-256"; } else if (key_type == PROXY_SSH_KEY_RSA_SHA256) { key_algo = "rsa-sha2-256"; } res = get_rsa_hostkey_data(p, key_algo, &buf, &ptr, &buflen); if (res < 0) { return NULL; } break; } #if !defined(OPENSSL_NO_DSA) case PROXY_SSH_KEY_DSA: { res = get_dsa_hostkey_data(p, &buf, &ptr, &buflen); if (res < 0) { return NULL; } break; } #endif /* !OPENSSL_NO_DSA */ #if defined(PR_USE_OPENSSL_ECC) case PROXY_SSH_KEY_ECDSA_256: { res = get_ecdsa_hostkey_data(p, ecdsa256_hostkey, "ecdsa-sha2-nistp256", "nistp256", &buf, &ptr, &buflen); if (res < 0) { return NULL; } break; } case PROXY_SSH_KEY_ECDSA_384: { res = get_ecdsa_hostkey_data(p, ecdsa384_hostkey, "ecdsa-sha2-nistp384", "nistp384", &buf, &ptr, &buflen); if (res < 0) { return NULL; } break; } case PROXY_SSH_KEY_ECDSA_521: { res = get_ecdsa_hostkey_data(p, ecdsa521_hostkey, "ecdsa-sha2-nistp521", "nistp521", &buf, &ptr, &buflen); if (res < 0) { return NULL; } break; } #endif /* PR_USE_OPENSSL_ECC */ #if defined(PR_USE_SODIUM) case PROXY_SSH_KEY_ED25519: { res = get_ed25519_hostkey_data(p, &buf, &ptr, &buflen); if (res < 0) { return NULL; } break; } #endif /* PR_USE_SODIUM */ #if defined(HAVE_X448_OPENSSL) case PROXY_SSH_KEY_ED448: { res = get_ed448_hostkey_data(p, &buf, &ptr, &buflen); if (res < 0) { return NULL; } break; } #endif /* HAVE_X448_OPENSSL */ default: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unknown/unsupported key type (%d) requested, ignoring", key_type); return NULL; } *datalen = PROXY_SSH_DEFAULT_HOSTKEY_SZ - buflen; /* If the caller provided a pool, make a copy of the data from the * given pool, and return the copy. Make sure to scrub the original * after making the copy. * * Note that we do this copy, even though we use the given pool, since * we only know the actual size of the data after the fact. And we need * to provide the size of the data to the caller, NOT the optimistic size * we allocate out of the pool for writing the data in the first place. * Hence the copy. */ buf = palloc(p, *datalen); memcpy(buf, ptr, *datalen); pr_memscrub(ptr, *datalen); return buf; } static const unsigned char *agent_sign_data(pool *p, const char *agent_path, const unsigned char *key_data, uint32_t key_datalen, const unsigned char *data, size_t datalen, size_t *siglen, int flags) { unsigned char *sig_data; uint32_t sig_datalen = 0; pr_trace_msg(trace_channel, 15, "asking SSH agent at '%s' to sign data", agent_path); /* Ask the agent to sign the data for this hostkey for us. */ sig_data = (unsigned char *) proxy_ssh_agent_sign_data(p, agent_path, key_data, key_datalen, data, datalen, &sig_datalen, flags); if (sig_data == NULL) { int xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "SSH agent at '%s' could not sign data: %s", agent_path, strerror(xerrno)); errno = xerrno; return NULL; } /* The SSH agent already provides the signed data in the correct * SSH2-style. */ *siglen = sig_datalen; return sig_data; } static const unsigned char *get_rsa_signed_data(pool *p, const unsigned char *data, size_t datalen, size_t *siglen, const char *sig_name, const EVP_MD *md) { RSA *rsa; #if OPENSSL_VERSION_NUMBER < 0x10100000L || \ defined(HAVE_LIBRESSL) EVP_MD_CTX ctx; #endif /* prior to OpenSSL-1.1.0 */ EVP_MD_CTX *pctx; unsigned char dgst[EVP_MAX_MD_SIZE], *sig_data; unsigned char *buf, *ptr; size_t bufsz; uint32_t buflen, dgstlen = 0, sig_datalen = 0, sig_rsalen = 0; int res; rsa = EVP_PKEY_get1_RSA(rsa_hostkey->pkey); if (rsa == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error using RSA hostkey: %s", proxy_ssh_crypto_get_errors()); return NULL; } #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) pctx = EVP_MD_CTX_new(); #else pctx = &ctx; #endif /* prior to OpenSSL-1.1.0 */ EVP_DigestInit(pctx, md); EVP_DigestUpdate(pctx, data, datalen); EVP_DigestFinal(pctx, dgst, &dgstlen); #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) EVP_MD_CTX_free(pctx); #endif /* OpenSSL-1.1.0 and later */ sig_rsalen = RSA_size(rsa); sig_data = pcalloc(p, sig_rsalen); res = RSA_sign(EVP_MD_type(md), dgst, dgstlen, sig_data, &sig_datalen, rsa); /* Regardless of whether the RSA signing succeeds or fails, we are done * with the digest buffer. */ pr_memscrub(dgst, dgstlen); if (res != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error signing data using RSA: %s", proxy_ssh_crypto_get_errors()); RSA_free(rsa); return NULL; } /* XXX Is this buffer large enough? Too large? */ buflen = bufsz = PROXY_SSH_MAX_SIG_SZ; ptr = buf = palloc(p, bufsz); /* Now build up the signature, SSH2-style */ proxy_ssh_msg_write_string(&buf, &buflen, sig_name); proxy_ssh_msg_write_data(&buf, &buflen, sig_data, sig_datalen, TRUE); pr_memscrub(sig_data, sig_datalen); RSA_free(rsa); /* At this point, buflen is the amount remaining in the allocated buffer. * So the total length of the signed data is the buffer size, minus those * remaining unused bytes. */ *siglen = (bufsz - buflen); return ptr; } static const unsigned char *rsa_sign_data(pool *p, const unsigned char *data, size_t datalen, size_t *siglen) { if (rsa_hostkey->agent_path != NULL) { return agent_sign_data(p, rsa_hostkey->agent_path, rsa_hostkey->key_data, rsa_hostkey->key_datalen, data, datalen, siglen, 0); } return get_rsa_signed_data(p, data, datalen, siglen, "ssh-rsa", EVP_sha1()); } /* RFC 4253, Section 6.6, is quite specific about the length of a DSA * ("ssh-dss") signature blob. It is comprised of two integers R and S, * each 160 bits (20 bytes), so that the total signature blob is 40 bytes * long. */ #define PROXY_SSH_DSA_INTEGER_LEN 20 #define PROXY_SSH_DSA_SIGNATURE_LEN (PROXY_SSH_DSA_INTEGER_LEN * 2) #if !defined(OPENSSL_NO_DSA) static const unsigned char *dsa_sign_data(pool *p, const unsigned char *data, size_t datalen, size_t *siglen) { DSA *dsa; DSA_SIG *sig; const BIGNUM *sig_r = NULL, *sig_s = NULL; #if OPENSSL_VERSION_NUMBER < 0x10100000L || \ defined(HAVE_LIBRESSL) EVP_MD_CTX ctx; #endif /* prior to OpenSSL-1.1.0 */ EVP_MD_CTX *pctx; const EVP_MD *sha1 = EVP_sha1(); unsigned char dgst[EVP_MAX_MD_SIZE], *sig_data; unsigned char *buf, *ptr; size_t bufsz; uint32_t buflen, dgstlen = 0; unsigned int rlen = 0, slen = 0; if (dsa_hostkey->agent_path != NULL) { return agent_sign_data(p, dsa_hostkey->agent_path, dsa_hostkey->key_data, dsa_hostkey->key_datalen, data, datalen, siglen, 0); } dsa = EVP_PKEY_get1_DSA(dsa_hostkey->pkey); if (dsa == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error using DSA hostkey: %s", proxy_ssh_crypto_get_errors()); return NULL; } #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) pctx = EVP_MD_CTX_new(); #else pctx = &ctx; #endif /* prior to OpenSSL-1.1.0 */ EVP_DigestInit(pctx, sha1); EVP_DigestUpdate(pctx, data, datalen); EVP_DigestFinal(pctx, dgst, &dgstlen); #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) EVP_MD_CTX_free(pctx); #endif /* OpenSSL-1.1.0 and later */ sig = DSA_do_sign(dgst, dgstlen, dsa); if (sig == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error obtaining DSA signature: %s", proxy_ssh_crypto_get_errors()); pr_memscrub(dgst, dgstlen); DSA_free(dsa); return NULL; } /* Got the signature, no need for the digest memory. */ pr_memscrub(dgst, dgstlen); #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) DSA_SIG_get0(sig, &sig_r, &sig_s); #else sig_r = sig->r; sig_s = sig->s; #endif /* prior to OpenSSL-1.1.0 */ rlen = BN_num_bytes(sig_r); slen = BN_num_bytes(sig_s); /* Make sure the values of R and S are big enough. */ if (rlen > PROXY_SSH_DSA_INTEGER_LEN || slen > PROXY_SSH_DSA_INTEGER_LEN) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "bad DSA signature size (%u, %u)", rlen, slen); DSA_SIG_free(sig); DSA_free(dsa); return NULL; } sig_data = pcalloc(p, PROXY_SSH_MAX_SIG_SZ); /* These may look strange, but the pointer arithmetic is necessary to * ensure the correct placement of the R and S values in the signature, * per RFC 4253 Section 6.6 requirements. */ BN_bn2bin(sig_r, sig_data + PROXY_SSH_DSA_SIGNATURE_LEN - PROXY_SSH_DSA_INTEGER_LEN - rlen); BN_bn2bin(sig_s, sig_data + PROXY_SSH_DSA_SIGNATURE_LEN - slen); /* Done with the signature. */ DSA_SIG_free(sig); DSA_free(dsa); /* XXX Is this buffer large enough? Too large? */ buflen = bufsz = PROXY_SSH_MAX_SIG_SZ; ptr = buf = palloc(p, bufsz); /* Now build up the signature, SSH2-style */ proxy_ssh_msg_write_string(&buf, &buflen, "ssh-dss"); proxy_ssh_msg_write_data(&buf, &buflen, sig_data, PROXY_SSH_DSA_SIGNATURE_LEN, TRUE); /* At this point, buflen is the amount remaining in the allocated buffer. * So the total length of the signed data is the buffer size, minus those * remaining unused bytes. */ *siglen = (bufsz - buflen); return ptr; } #endif /* !OPENSSL_NO_DSA */ #if defined(PR_USE_OPENSSL_ECC) static const unsigned char *ecdsa_sign_data(pool *p, const unsigned char *data, size_t datalen, size_t *siglen, int nid) { EVP_PKEY *pkey = NULL; EC_KEY *ec = NULL; ECDSA_SIG *sig; const BIGNUM *sig_r = NULL, *sig_s = NULL; #if OPENSSL_VERSION_NUMBER < 0x10100000L || \ defined(HAVE_LIBRESSL) EVP_MD_CTX ctx; #endif /* prior to OpenSSL-1.1.0 */ EVP_MD_CTX *pctx; const EVP_MD *md; unsigned char dgst[EVP_MAX_MD_SIZE]; unsigned char *buf, *ptr, *sig_buf, *sig_ptr; uint32_t bufsz, buflen, dgstlen = 0, sig_buflen, sig_bufsz; switch (nid) { case NID_X9_62_prime256v1: if (ecdsa256_hostkey->agent_path != NULL) { return agent_sign_data(p, ecdsa256_hostkey->agent_path, ecdsa256_hostkey->key_data, ecdsa256_hostkey->key_datalen, data, datalen, siglen, 0); } ec = EVP_PKEY_get1_EC_KEY(ecdsa256_hostkey->pkey); if (ec == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error using ECDSA-256 hostkey: %s", proxy_ssh_crypto_get_errors()); return NULL; } pkey = ecdsa256_hostkey->pkey; md = EVP_sha256(); break; case NID_secp384r1: if (ecdsa384_hostkey->agent_path != NULL) { return agent_sign_data(p, ecdsa384_hostkey->agent_path, ecdsa384_hostkey->key_data, ecdsa384_hostkey->key_datalen, data, datalen, siglen, 0); } ec = EVP_PKEY_get1_EC_KEY(ecdsa384_hostkey->pkey); if (ec == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error using ECDSA-384 hostkey: %s", proxy_ssh_crypto_get_errors()); return NULL; } pkey = ecdsa384_hostkey->pkey; md = EVP_sha384(); break; case NID_secp521r1: if (ecdsa521_hostkey->agent_path != NULL) { return agent_sign_data(p, ecdsa521_hostkey->agent_path, ecdsa521_hostkey->key_data, ecdsa521_hostkey->key_datalen, data, datalen, siglen, 0); } ec = EVP_PKEY_get1_EC_KEY(ecdsa521_hostkey->pkey); if (ec == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error using ECDSA-521 hostkey: %s", proxy_ssh_crypto_get_errors()); return NULL; } pkey = ecdsa521_hostkey->pkey; md = EVP_sha512(); break; default: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unknown/unsupported ECDSA NID (%d) requested", nid); return NULL; } buflen = bufsz = PROXY_SSH_MAX_SIG_SZ; ptr = buf = palloc(p, bufsz); #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) pctx = EVP_MD_CTX_new(); #else pctx = &ctx; #endif /* prior to OpenSSL-1.1.0 */ EVP_DigestInit(pctx, md); EVP_DigestUpdate(pctx, data, datalen); EVP_DigestFinal(pctx, dgst, &dgstlen); #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) EVP_MD_CTX_free(pctx); #endif /* OpenSSL-1.1.0 and later */ sig = ECDSA_do_sign(dgst, dgstlen, ec); if (sig == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error obtaining ECDSA signature: %s", proxy_ssh_crypto_get_errors()); pr_memscrub(dgst, dgstlen); EC_KEY_free(ec); return NULL; } /* Got the signature, no need for the digest memory. */ pr_memscrub(dgst, dgstlen); /* Unlike DSA, the R and S lengths for ECDSA are dependent on the curve * selected, so we do no sanity checking of their lengths. */ #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) ECDSA_SIG_get0(sig, &sig_r, &sig_s); #else sig_r = sig->r; sig_s = sig->s; #endif /* prior to OpenSSL-1.1.0 */ /* XXX Is this buffer large enough? Too large? */ sig_buflen = sig_bufsz = 256; sig_ptr = sig_buf = palloc(p, sig_bufsz); proxy_ssh_msg_write_mpint(&sig_buf, &sig_buflen, sig_r); proxy_ssh_msg_write_mpint(&sig_buf, &sig_buflen, sig_s); /* Done with the signature. */ ECDSA_SIG_free(sig); EC_KEY_free(ec); /* XXX Is this buffer large enough? Too large? */ buflen = bufsz = PROXY_SSH_MAX_SIG_SZ; ptr = buf = palloc(p, bufsz); /* Now build up the signature, SSH2-style */ switch (nid) { case NID_X9_62_prime256v1: proxy_ssh_msg_write_string(&buf, &buflen, "ecdsa-sha2-nistp256"); break; case NID_secp384r1: proxy_ssh_msg_write_string(&buf, &buflen, "ecdsa-sha2-nistp384"); break; case NID_secp521r1: proxy_ssh_msg_write_string(&buf, &buflen, "ecdsa-sha2-nistp521"); break; } proxy_ssh_msg_write_data(&buf, &buflen, sig_ptr, (sig_bufsz - sig_buflen), TRUE); pr_memscrub(sig_ptr, sig_bufsz); /* At this point, buflen is the amount remaining in the allocated buffer. * So the total length of the signed data is the buffer size, minus those * remaining unused bytes. */ *siglen = (bufsz - buflen); return ptr; } #endif /* PR_USE_OPENSSL_ECC */ #if defined(PR_USE_SODIUM) static const unsigned char *ed25519_sign_data(pool *p, const unsigned char *data, size_t datalen, size_t *siglen) { unsigned char *buf, *ptr, *sig_buf, *sig_ptr; uint32_t bufsz, buflen, sig_buflen, sig_bufsz; unsigned long long slen; int res; /* XXX TODO ED25519: Test this! */ if (ed25519_hostkey->agent_path != NULL) { return agent_sign_data(p, ed25519_hostkey->agent_path, ed25519_hostkey->ed25519_public_key, ed25519_hostkey->ed25519_public_keylen, data, datalen, siglen, 0); } sig_buflen = sig_bufsz = slen = datalen + crypto_sign_ed25519_BYTES; sig_ptr = sig_buf = palloc(p, sig_bufsz); res = crypto_sign_ed25519(sig_buf, &slen, data, datalen, ed25519_hostkey->ed25519_secret_key); if (res != 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "failed to sign data using Ed25519 (%d)", res); pr_memscrub(sig_ptr, sig_bufsz); return NULL; } sig_buflen = slen; if (sig_buflen <= datalen) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "invalid Ed25519 signature (%lu bytes) generated, expected more than " "%lu bytes", (unsigned long) sig_buflen, (unsigned long) datalen); pr_memscrub(sig_ptr, sig_bufsz); return NULL; } /* XXX Is this buffer large enough? Too large? */ buflen = bufsz = PROXY_SSH_MAX_SIG_SZ; ptr = buf = palloc(p, bufsz); /* Now build up the signature, SSH2-style */ proxy_ssh_msg_write_string(&buf, &buflen, "ssh-ed25519"); proxy_ssh_msg_write_data(&buf, &buflen, sig_ptr, sig_buflen - datalen, TRUE); pr_memscrub(sig_ptr, sig_bufsz); /* At this point, buflen is the amount remaining in the allocated buffer. * So the total length of the signed data is the buffer size, minus those * remaining unused bytes. */ *siglen = (bufsz - buflen); return ptr; } #endif /* PR_USE_SODIUM */ #if defined(HAVE_X448_OPENSSL) static const unsigned char *ed448_sign_data(pool *p, const unsigned char *data, size_t datalen, size_t *siglen) { unsigned char *buf, *ptr, *sig_buf; uint32_t bufsz, buflen, sig_buflen, sig_bufsz; EVP_MD_CTX *md_ctx; EVP_PKEY *pkey; /* XXX TODO ED448: Test this! */ if (ed448_hostkey->agent_path != NULL) { return agent_sign_data(p, ed448_hostkey->agent_path, ed448_hostkey->ed448_public_key, ed448_hostkey->ed448_public_keylen, data, datalen, siglen, 0); } sig_buflen = sig_bufsz = datalen + 256; sig_buf = palloc(p, sig_bufsz); md_ctx = EVP_MD_CTX_new(); pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED448, NULL, ed448_hostkey->ed448_secret_key, ed448_hostkey->ed448_secret_keylen); if (pkey == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error initializing Ed448 private key: %s", proxy_ssh_crypto_get_errors()); EVP_MD_CTX_free(md_ctx); return NULL; } if (EVP_DigestSignInit(md_ctx, NULL, NULL, NULL, pkey) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error initializing Ed448 signature: %s", proxy_ssh_crypto_get_errors()); EVP_PKEY_free(pkey); EVP_MD_CTX_free(md_ctx); return NULL; } if (EVP_DigestSign(md_ctx, sig_buf, (size_t *) &sig_buflen, data, datalen) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "failed to sign data using Ed448: %s", proxy_ssh_crypto_get_errors()); EVP_PKEY_free(pkey); EVP_MD_CTX_free(md_ctx); return NULL; } EVP_PKEY_free(pkey); EVP_MD_CTX_free(md_ctx); /* XXX Is this buffer large enough? Too large? */ buflen = bufsz = PROXY_SSH_MAX_SIG_SZ; ptr = buf = palloc(p, bufsz); /* Now build up the signature, SSH2-style */ proxy_ssh_msg_write_string(&buf, &buflen, "ssh-ed448"); proxy_ssh_msg_write_data(&buf, &buflen, sig_buf, sig_buflen, TRUE); pr_memscrub(sig_buf, sig_bufsz); /* At this point, buflen is the amount remaining in the allocated buffer. * So the total length of the signed data is the buffer size, minus those * remaining unused bytes. */ *siglen = (bufsz - buflen); return ptr; } #endif /* HAVE_X448_OPENSSL */ const unsigned char *proxy_ssh_keys_sign_data(pool *p, enum proxy_ssh_key_type_e key_type, const unsigned char *data, size_t datalen, size_t *siglen) { const unsigned char *res; switch (key_type) { case PROXY_SSH_KEY_RSA: res = rsa_sign_data(p, data, datalen, siglen); break; #if !defined(OPENSSL_NO_DSA) case PROXY_SSH_KEY_DSA: res = dsa_sign_data(p, data, datalen, siglen); break; #endif /* !OPENSSL_NO_DSA */ #ifdef PR_USE_OPENSSL_ECC case PROXY_SSH_KEY_ECDSA_256: res = ecdsa_sign_data(p, data, datalen, siglen, NID_X9_62_prime256v1); break; case PROXY_SSH_KEY_ECDSA_384: res = ecdsa_sign_data(p, data, datalen, siglen, NID_secp384r1); break; case PROXY_SSH_KEY_ECDSA_521: res = ecdsa_sign_data(p, data, datalen, siglen, NID_secp521r1); break; #endif /* PR_USE_OPENSSL_ECC */ #if defined(PR_USE_SODIUM) case PROXY_SSH_KEY_ED25519: res = ed25519_sign_data(p, data, datalen, siglen); break; #endif /* PR_USE_SODIUM */ #if defined(HAVE_X448_OPENSSL) case PROXY_SSH_KEY_ED448: res = ed448_sign_data(p, data, datalen, siglen); break; #endif /* HAVE_X448_OPENSSL */ default: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unknown key type (%d) requested for signing, ignoring", key_type); return NULL; } if (res != NULL && p != NULL) { unsigned char *buf; buf = palloc(p, *siglen); memcpy(buf, res, *siglen); pr_memscrub((char *) res, *siglen); return buf; } return res; } int proxy_ssh_keys_verify_pubkey_type(pool *p, unsigned char *pubkey_data, uint32_t pubkey_len, enum proxy_ssh_key_type_e pubkey_type) { EVP_PKEY *pkey = NULL; int res = FALSE; uint32_t len; if (pubkey_data == NULL || pubkey_len == 0) { errno = EINVAL; return -1; } len = read_pkey_from_data(p, pubkey_data, pubkey_len, &pkey, NULL, FALSE); if (len == 0) { return -1; } switch (pubkey_type) { case PROXY_SSH_KEY_RSA: res = (get_pkey_type(pkey) == EVP_PKEY_RSA); break; case PROXY_SSH_KEY_DSA: res = (get_pkey_type(pkey) == EVP_PKEY_DSA); break; #ifdef PR_USE_OPENSSL_ECC case PROXY_SSH_KEY_ECDSA_256: case PROXY_SSH_KEY_ECDSA_384: case PROXY_SSH_KEY_ECDSA_521: if (get_pkey_type(pkey) == EVP_PKEY_EC) { EC_KEY *ec; int ec_nid; ec = EVP_PKEY_get1_EC_KEY(pkey); ec_nid = get_ecdsa_nid(ec); EC_KEY_free(ec); switch (ec_nid) { case NID_X9_62_prime256v1: res = (pubkey_type == PROXY_SSH_KEY_ECDSA_256); break; case NID_secp384r1: res = (pubkey_type == PROXY_SSH_KEY_ECDSA_384); break; case NID_secp521r1: res = (pubkey_type == PROXY_SSH_KEY_ECDSA_521); break; } } break; #endif /* PR_USE_OPENSSL_ECC */ #if defined(PR_USE_SODIUM) case PROXY_SSH_KEY_ED25519: { char *pkey_type; len = proxy_ssh_msg_read_string(p, &pubkey_data, &pubkey_len, &pkey_type); if (strcmp(pkey_type, "ssh-ed25519") != 0) { pr_trace_msg(trace_channel, 8, "invalid public key type '%s' for Ed25519 key", pkey_type); res = FALSE; } else { uint32_t pklen; len = proxy_ssh_msg_read_int(p, &pubkey_data, &pubkey_len, &pklen); res = (pklen == (uint32_t) crypto_sign_ed25519_PUBLICKEYBYTES); if (res == FALSE) { pr_trace_msg(trace_channel, 8, "Ed25519 public key length (%lu bytes) does not match expected " "length (%lu bytes)", (unsigned long) pklen, (unsigned long) crypto_sign_ed25519_PUBLICKEYBYTES); } } break; } #endif /* PR_USE_SODIUM */ default: /* No matching public key type/algorithm. */ errno = ENOENT; res = FALSE; break; } if (pkey != NULL) { EVP_PKEY_free(pkey); } return res; } static int verify_rsa_signed_data(pool *p, EVP_PKEY *pkey, unsigned char *signature, uint32_t signature_len, unsigned char *sig_data, size_t sig_datalen, const EVP_MD *md) { #if OPENSSL_VERSION_NUMBER < 0x10100000L || \ defined(HAVE_LIBRESSL) EVP_MD_CTX ctx; #endif /* prior to OpenSSL-1.1.0 */ EVP_MD_CTX *pctx; RSA *rsa; uint32_t len, sig_len; unsigned char digest[EVP_MAX_MD_SIZE], *sig; unsigned int digest_len = 0, modulus_len = 0; int ok = FALSE, res = 0; len = proxy_ssh_msg_read_int(p, &signature, &signature_len, &sig_len); if (len == 0) { errno = EINVAL; return -1; } len = proxy_ssh_msg_read_data(p, &signature, &signature_len, sig_len, &sig); if (len == 0) { errno = EINVAL; return -1; } if (sig == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error verifying RSA signature: missing signature data"); errno = EINVAL; return -1; } rsa = EVP_PKEY_get1_RSA(pkey); modulus_len = RSA_size(rsa); /* If the signature provided by the server is more than the expected * key length, the verification will fail. */ if (sig_len > modulus_len) { RSA_free(rsa); (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error verifying RSA signature: signature len (%lu) > RSA modulus " "len (%u)", (unsigned long) sig_len, modulus_len); errno = EINVAL; return -1; } /* If the signature provided by the server is less than the expected * key length, the verification will fail. In such cases, we need to * pad the provided signature with leading zeros (Bug#3992). */ if (sig_len < modulus_len) { unsigned int padding_len; unsigned char *padded_sig; padding_len = modulus_len - sig_len; padded_sig = pcalloc(p, modulus_len); pr_trace_msg(trace_channel, 12, "padding server-sent RSA signature " "(%lu) bytes with %u bytes of zeroed data", (unsigned long) sig_len, padding_len); memmove(padded_sig + padding_len, sig, sig_len); sig = padded_sig; sig_len = (uint32_t) modulus_len; } #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) pctx = EVP_MD_CTX_new(); #else pctx = &ctx; #endif /* prior to OpenSSL-1.1.0 */ EVP_DigestInit(pctx, md); EVP_DigestUpdate(pctx, sig_data, sig_datalen); EVP_DigestFinal(pctx, digest, &digest_len); #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) EVP_MD_CTX_free(pctx); #endif /* OpenSSL-1.1.0 and later */ ok = RSA_verify(EVP_MD_type(md), digest, digest_len, sig, sig_len, rsa); if (ok == 1) { res = 0; } else { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error verifying RSA signature: %s", proxy_ssh_crypto_get_errors()); errno = EINVAL; res = -1; } pr_memscrub(digest, digest_len); RSA_free(rsa); return res; } static int rsa_verify_signed_data(pool *p, EVP_PKEY *pkey, unsigned char *signature, uint32_t signature_len, unsigned char *sig_data, size_t sig_datalen) { return verify_rsa_signed_data(p, pkey, signature, signature_len, sig_data, sig_datalen, EVP_sha1()); } #if defined(HAVE_SHA256_OPENSSL) static int rsa_sha256_verify_signed_data(pool *p, EVP_PKEY *pkey, unsigned char *signature, uint32_t signature_len, unsigned char *sig_data, size_t sig_datalen) { return verify_rsa_signed_data(p, pkey, signature, signature_len, sig_data, sig_datalen, EVP_sha256()); } #endif /* HAVE_SHA256_OPENSSL */ #if defined(HAVE_SHA512_OPENSSL) static int rsa_sha512_verify_signed_data(pool *p, EVP_PKEY *pkey, unsigned char *signature, uint32_t signature_len, unsigned char *sig_data, size_t sig_datalen) { return verify_rsa_signed_data(p, pkey, signature, signature_len, sig_data, sig_datalen, EVP_sha512()); } #endif /* HAVE_SHA512_OPENSSL */ #if !defined(OPENSSL_NO_DSA) static int dsa_verify_signed_data(pool *p, EVP_PKEY *pkey, unsigned char *signature, uint32_t signature_len, unsigned char *sig_data, size_t sig_datalen) { #if OPENSSL_VERSION_NUMBER < 0x10100000L || \ defined(HAVE_LIBRESSL) EVP_MD_CTX ctx; #endif /* prior to OpenSSL-1.1.0 */ EVP_MD_CTX *pctx; DSA *dsa; DSA_SIG *dsa_sig; const BIGNUM *sig_r, *sig_s; uint32_t len, sig_len; unsigned char digest[EVP_MAX_MD_SIZE], *sig; unsigned int digest_len = 0; int ok = FALSE, res = 0; len = proxy_ssh_msg_read_int(p, &signature, &signature_len, &sig_len); if (len == 0) { errno = EINVAL; return -1; } /* A DSA signature string is composed of 2 20 character parts. */ if (sig_len != 40) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "bad DSA signature len (%lu)", (unsigned long) sig_len); } len = proxy_ssh_msg_read_data(p, &signature, &signature_len, sig_len, &sig); if (len == 0) { errno = EINVAL; return -1; } if (sig == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error verifying DSA signature: missing signature data"); errno = EINVAL; return -1; } dsa = EVP_PKEY_get1_DSA(pkey); dsa_sig = DSA_SIG_new(); #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) DSA_SIG_get0(dsa_sig, &sig_r, &sig_s); #else sig_r = dsa_sig->r; sig_s = dsa_sig->s; #endif /* prior to OpenSSL-1.1.0 */ sig_r = BN_bin2bn(sig, 20, (BIGNUM *) sig_r); if (sig_r == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error obtaining 'r' DSA signature component: %s", proxy_ssh_crypto_get_errors()); DSA_free(dsa); DSA_SIG_free(dsa_sig); return -1; } sig_s = BN_bin2bn(sig + 20, 20, (BIGNUM *) sig_s); if (sig_s == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error obtaining 's' DSA signature component: %s", proxy_ssh_crypto_get_errors()); BN_clear_free((BIGNUM *) sig_r); DSA_free(dsa); DSA_SIG_free(dsa_sig); return -1; } #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) pctx = EVP_MD_CTX_new(); #else pctx = &ctx; #endif /* prior to OpenSSL-1.1.0 */ EVP_DigestInit(pctx, EVP_sha1()); EVP_DigestUpdate(pctx, sig_data, sig_datalen); EVP_DigestFinal(pctx, digest, &digest_len); #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) EVP_MD_CTX_free(pctx); #endif /* OpenSSL-1.1.0 and later */ #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) # if OPENSSL_VERSION_NUMBER >= 0x10100006L DSA_SIG_set0(dsa_sig, (BIGNUM *) sig_r, (BIGNUM *) sig_s); # else /* XXX What to do here? */ # endif /* prior to OpenSSL-1.1.0-pre6 */ #else dsa_sig->r = sig_r; dsa_sig->s = sig_s; #endif /* prior to OpenSSL-1.1.0 */ ok = DSA_do_verify(digest, digest_len, dsa_sig, dsa); if (ok == 1) { res = 0; } else { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error verifying DSA signature: %s", proxy_ssh_crypto_get_errors()); errno = EINVAL; res = -1; } pr_memscrub(digest, digest_len); DSA_free(dsa); DSA_SIG_free(dsa_sig); return res; } #endif /* !OPENSSL_NO_DSA */ #if defined(PR_USE_OPENSSL_ECC) static int ecdsa_verify_signed_data(pool *p, EVP_PKEY *pkey, unsigned char *signature, uint32_t signature_len, unsigned char *sig_data, size_t sig_datalen, char *sig_type) { #if OPENSSL_VERSION_NUMBER < 0x10100000L || \ defined(HAVE_LIBRESSL) EVP_MD_CTX ctx; #endif /* prior to OpenSSL-1.1.0 */ EVP_MD_CTX *pctx; const EVP_MD *md = NULL; EC_KEY *ec; ECDSA_SIG *ecdsa_sig; const BIGNUM *sig_r, *sig_s; uint32_t len, sig_len; unsigned char digest[EVP_MAX_MD_SIZE], *sig; unsigned int digest_len = 0; int ok = FALSE, res = 0; len = proxy_ssh_msg_read_int(p, &signature, &signature_len, &sig_len); if (len == 0) { errno = EINVAL; return -1; } len = proxy_ssh_msg_read_data(p, &signature, &signature_len, sig_len, &sig); if (len == 0) { errno = EINVAL; return -1; } if (sig == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error verifying ECDSA signature: missing signature data"); errno = EINVAL; return -1; } ecdsa_sig = ECDSA_SIG_new(); if (ecdsa_sig == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error allocating new ECDSA_SIG: %s", proxy_ssh_crypto_get_errors()); return -1; } #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) ECDSA_SIG_get0(ecdsa_sig, &sig_r, &sig_s); #else sig_r = ecdsa_sig->r; sig_s = ecdsa_sig->s; #endif /* prior to OpenSSL-1.1.0 */ len = proxy_ssh_msg_read_mpint(p, &sig, &sig_len, &sig_r); if (len == 0) { ECDSA_SIG_free(ecdsa_sig); errno = EINVAL; return -1; } if (sig_r == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error reading 'r' ECDSA signature component: %s", proxy_ssh_crypto_get_errors()); ECDSA_SIG_free(ecdsa_sig); return -1; } len = proxy_ssh_msg_read_mpint(p, &sig, &sig_len, &sig_s); if (len == 0) { ECDSA_SIG_free(ecdsa_sig); errno = EINVAL; return -1; } if (sig_s == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error reading 's' ECDSA signature component: %s", proxy_ssh_crypto_get_errors()); ECDSA_SIG_free(ecdsa_sig); return -1; } /* Skip past the common leading prefix "ecdsa-sha2-" to compare just * last 9 characters. */ if (strcmp(sig_type + 11, "nistp256") == 0) { md = EVP_sha256(); } else if (strcmp(sig_type + 11, "nistp384") == 0) { md = EVP_sha384(); } else if (strcmp(sig_type + 11, "nistp521") == 0) { md = EVP_sha512(); } #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) pctx = EVP_MD_CTX_new(); #else pctx = &ctx; #endif /* prior to OpenSSL-1.1.0 */ EVP_DigestInit(pctx, md); EVP_DigestUpdate(pctx, sig_data, sig_datalen); EVP_DigestFinal(pctx, digest, &digest_len); #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) EVP_MD_CTX_free(pctx); #endif /* OpenSSL-1.1.0 and later */ ec = EVP_PKEY_get1_EC_KEY(pkey); #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) # if OPENSSL_VERSION_NUMBER >= 0x10100006L ECDSA_SIG_set0(ecdsa_sig, (BIGNUM *) sig_r, (BIGNUM *) sig_s); # else /* XXX What to do here? */ # endif /* prior to OpenSSL-1.1.0-pre6 */ #else ecdsa_sig->r = sig_r; ecdsa_sig->s = sig_s; #endif /* prior to OpenSSL-1.1.0 */ ok = ECDSA_do_verify(digest, digest_len, ecdsa_sig, ec); if (ok == 1) { res = 0; } else { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error verifying ECDSA signature: %s", proxy_ssh_crypto_get_errors()); errno = EINVAL; res = -1; } pr_memscrub(digest, digest_len); EC_KEY_free(ec); ECDSA_SIG_free(ecdsa_sig); return res; } #endif /* PR_USE_OPENSSL_ECC */ #if defined(PR_USE_SODIUM) static int ed25519_verify_signed_data(pool *p, unsigned char *pubkey_data, uint32_t pubkey_datalen, unsigned char *signature, uint32_t signature_len, unsigned char *sig_data, size_t sig_datalen) { char *pkey_type; uint32_t len, public_keylen, sig_len; unsigned char *msg, *public_key, *signed_msg, *sig; unsigned long long msg_len, signed_msglen; int res; len = proxy_ssh_msg_read_string(p, &pubkey_data, &pubkey_datalen, &pkey_type); if (len == 0) { errno = EINVAL; return -1; } if (strcmp(pkey_type, "ssh-ed25519") != 0) { pr_trace_msg(trace_channel, 17, "public key type '%s' does not match expected key type 'ssh-ed25519'", pkey_type); errno = EINVAL; return -1; } len = proxy_ssh_msg_read_int(p, &pubkey_data, &pubkey_datalen, &public_keylen); if (len == 0) { errno = EINVAL; return -1; } if (public_keylen != crypto_sign_ed25519_PUBLICKEYBYTES) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "invalid Ed25519 public key length (%lu bytes), expected %lu bytes", (unsigned long) public_keylen, (unsigned long) crypto_sign_ed25519_PUBLICKEYBYTES); errno = EINVAL; return -1; } len = proxy_ssh_msg_read_data(p, &pubkey_data, &pubkey_datalen, public_keylen, &public_key); if (len == 0) { errno = EINVAL; return -1; } len = proxy_ssh_msg_read_int(p, &signature, &signature_len, &sig_len); if (len == 0) { errno = EINVAL; return -1; } len = proxy_ssh_msg_read_data(p, &signature, &signature_len, sig_len, &sig); if (len == 0) { errno = EINVAL; return -1; } if (sig == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error verifying Ed25519 signature: missing signature data"); errno = EINVAL; return -1; } if (sig_len > crypto_sign_ed25519_BYTES) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "Ed25519 signature length (%lu bytes) exceeds valid length (%lu bytes)", (unsigned long) sig_len, (unsigned long) crypto_sign_ed25519_BYTES); errno = EINVAL; return -1; } signed_msglen = sig_len + sig_datalen; signed_msg = palloc(p, signed_msglen); memcpy(signed_msg, sig, sig_len); memcpy(signed_msg + sig_len, sig_data, sig_datalen); msg_len = signed_msglen; msg = palloc(p, msg_len); res = crypto_sign_ed25519_open(msg, &msg_len, signed_msg, signed_msglen, public_key); if (res != 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "failed Ed25519 signature verification (%d)", res); res = -1; } if (res == 0) { if (msg_len != sig_datalen) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "invalid Ed25519 signature length (%lu bytes), expected %lu bytes", (unsigned long) sig_datalen, (unsigned long) msg_len); errno = EINVAL; res = -1; } } if (res == 0) { if (sodium_memcmp(msg, sig_data, msg_len) != 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "invalid Ed25519 signature (mismatched data)"); errno = EINVAL; res = -1; } } pr_memscrub(signed_msg, signed_msglen); pr_memscrub(msg, msg_len); return res; } #endif /* PR_USE_SODIUM */ int proxy_ssh_keys_verify_signed_data(pool *p, const char *pubkey_algo, unsigned char *pubkey_data, uint32_t pubkey_datalen, unsigned char *signature, uint32_t signature_len, unsigned char *sig_data, size_t sig_datalen) { EVP_PKEY *pkey = NULL; char *sig_type; uint32_t len; int res = 0; if (pubkey_algo == NULL || pubkey_data == NULL || signature == NULL || sig_data == NULL || sig_datalen == 0) { errno = EINVAL; return -1; } len = read_pkey_from_data(p, pubkey_data, pubkey_datalen, &pkey, NULL, FALSE); if (len == 0) { return -1; } if (strcmp(pubkey_algo, "ssh-dss") == 0) { if (proxy_ssh_interop_supports_feature(PROXY_SSH_FEAT_HAVE_PUBKEY_ALGO_IN_DSA_SIG)) { len = proxy_ssh_msg_read_string(p, &signature, &signature_len, &sig_type); if (len == 0) { errno = EINVAL; return -1; } } else { /* The server did not prepend the public key algorithm name to their * signature data, so there is no need to extract that string. * We will ASSUME that the public key algorithm provided elsewhere * in the 'publickey' USERAUTH_REQUEST is accurate. */ pr_trace_msg(trace_channel, 9, "assuming server did not prepend public " "key algorithm name to DSA signature"); sig_type = "ssh-dss"; } } else { len = proxy_ssh_msg_read_string(p, &signature, &signature_len, &sig_type); if (len == 0) { errno = EINVAL; return -1; } } if (strcmp(sig_type, "ssh-rsa") == 0) { res = rsa_verify_signed_data(p, pkey, signature, signature_len, sig_data, sig_datalen); #if defined(HAVE_SHA256_OPENSSL) } else if (strcmp(sig_type, "rsa-sha2-256") == 0) { res = rsa_sha256_verify_signed_data(p, pkey, signature, signature_len, sig_data, sig_datalen); #endif /* HAVE_SHA256_OPENSSL */ #if defined(HAVE_SHA512_OPENSSL) } else if (strcmp(sig_type, "rsa-sha2-512") == 0) { res = rsa_sha512_verify_signed_data(p, pkey, signature, signature_len, sig_data, sig_datalen); #endif /* HAVE_SHA512_OPENSSL */ #if !defined(OPENSSL_NO_DSA) } else if (strcmp(sig_type, "ssh-dss") == 0) { res = dsa_verify_signed_data(p, pkey, signature, signature_len, sig_data, sig_datalen); #endif /* !OPENSSL_NO_DSA */ #ifdef PR_USE_OPENSSL_ECC } else if (strcmp(sig_type, "ecdsa-sha2-nistp256") == 0 || strcmp(sig_type, "ecdsa-sha2-nistp384") == 0 || strcmp(sig_type, "ecdsa-sha2-nistp521") == 0) { if (strcmp(pubkey_algo, sig_type) != 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to verify signed data: public key algorithm '%s' does not " "match signature algorithm '%s'", pubkey_algo, sig_type); return -1; } res = ecdsa_verify_signed_data(p, pkey, signature, signature_len, sig_data, sig_datalen, sig_type); #endif /* PR_USE_OPENSSL_ECC */ #if defined(PR_USE_SODIUM) } else if (strcmp(sig_type, "ssh-ed25519") == 0) { res = ed25519_verify_signed_data(p, pubkey_data, pubkey_datalen, signature, signature_len, sig_data, sig_datalen); #endif /* PR_USE_SODIUM */ } else { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to verify signed data: unsupported signature algorithm '%s'", sig_type); errno = EINVAL; return -1; } if (pkey != NULL) { EVP_PKEY_free(pkey); } return res; } int proxy_ssh_keys_set_passphrase_provider(const char *provider) { if (provider == NULL) { errno = EINVAL; return -1; } passphrase_provider = provider; return 0; } void proxy_ssh_keys_get_passphrases(void) { server_rec *s = NULL; for (s = (server_rec *) server_list->xas_list; s; s = s->next) { config_rec *c; struct proxy_ssh_pkey *k; c = find_config(s->conf, CONF_PARAM, "ProxySFTPHostKey", FALSE); while (c != NULL) { int flags; pr_signals_handle(); flags = *((int *) c->argv[1]); /* Skip any agent-provided ProxySFTPHostKey directives, as well as any * "disabling key" directives. */ if (flags != 0 || strncmp(c->argv[0], "agent:", 6) == 0) { c = find_config_next(c, c->next, CONF_PARAM, "ProxySFTPHostKey", FALSE); continue; } k = pcalloc(s->pool, sizeof(struct proxy_ssh_pkey)); k->pkeysz = PEM_BUFSIZE-1; k->server = s; if (get_passphrase(k, c->argv[0]) < 0) { int xerrno = errno; const char *errstr; errstr = proxy_ssh_crypto_get_errors(); pr_log_pri(PR_LOG_WARNING, MOD_PROXY_VERSION ": error reading passphrase for ProxySFTPHostKey '%s': %s", (const char *) c->argv[0], errstr ? errstr : strerror(xerrno)); pr_log_pri(PR_LOG_ERR, MOD_PROXY_VERSION ": unable to use key in ProxySFTPHostKey '%s', exiting", (const char *) c->argv[0]); pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BAD_CONFIG, NULL); } k->next = pkey_list; pkey_list = k; npkeys++; c = find_config_next(c, c->next, CONF_PARAM, "ProxySFTPHostKey", FALSE); } } } static int clear_dsa_hostkey(void) { if (dsa_hostkey == NULL) { errno = ENOENT; return -1; } if (dsa_hostkey->pkey != NULL) { EVP_PKEY_free(dsa_hostkey->pkey); } dsa_hostkey = NULL; return 0; } static int clear_ecdsa_hostkey(void) { #if defined(PR_USE_OPENSSL_ECC) int count = 0; if (ecdsa256_hostkey != NULL) { if (ecdsa256_hostkey->pkey != NULL) { EVP_PKEY_free(ecdsa256_hostkey->pkey); } ecdsa256_hostkey = NULL; count++; } if (ecdsa384_hostkey != NULL) { if (ecdsa384_hostkey->pkey != NULL) { EVP_PKEY_free(ecdsa384_hostkey->pkey); } ecdsa384_hostkey = NULL; count++; } if (ecdsa521_hostkey != NULL) { if (ecdsa521_hostkey->pkey != NULL) { EVP_PKEY_free(ecdsa521_hostkey->pkey); } ecdsa521_hostkey = NULL; count++; } if (count > 0) { return 0; } #endif /* PR_USE_OPENSSL_ECC */ errno = ENOENT; return -1; } static int clear_ed25519_hostkey(void) { #if defined(PR_USE_SODIUM) if (ed25519_hostkey == NULL) { errno = ENOENT; return -1; } if (ed25519_hostkey->ed25519_secret_key != NULL) { pr_memscrub(ed25519_hostkey->ed25519_secret_key, ed25519_hostkey->ed25519_secret_keylen); ed25519_hostkey->ed25519_secret_key = NULL; ed25519_hostkey->ed25519_secret_keylen = 0; } if (ed25519_hostkey->ed25519_public_key != NULL) { pr_memscrub(ed25519_hostkey->ed25519_public_key, ed25519_hostkey->ed25519_public_keylen); ed25519_hostkey->ed25519_public_key = NULL; ed25519_hostkey->ed25519_public_keylen = 0; } ed25519_hostkey = NULL; #endif /* PR_USE_SODIUM */ return 0; } static int clear_rsa_hostkey(void) { if (rsa_hostkey == NULL) { errno = ENOENT; return -1; } if (rsa_hostkey->pkey != NULL) { EVP_PKEY_free(rsa_hostkey->pkey); } rsa_hostkey = NULL; return 0; } /* Make sure that no valuable information can be inadvertently written * out to swap. */ void proxy_ssh_keys_free(void) { scrub_pkeys(); clear_dsa_hostkey(); clear_ecdsa_hostkey(); clear_ed25519_hostkey(); clear_rsa_hostkey(); } #endif /* PR_USE_OPENSSL */ proftpd-mod_proxy-0.9.5/lib/proxy/ssh/mac.c000066400000000000000000000766571475737016700207170ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH MACs * Copyright (c) 2021-2022 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "proxy/ssh/ssh2.h" #include "proxy/ssh/msg.h" #include "proxy/ssh/packet.h" #include "proxy/ssh/crypto.h" #include "proxy/ssh/cipher.h" #include "proxy/ssh/mac.h" #include "proxy/ssh/umac.h" #include "proxy/ssh/session.h" #include "proxy/ssh/disconnect.h" #include "proxy/ssh/interop.h" #if defined(PR_USE_OPENSSL) #include #include struct proxy_ssh_mac { pool *pool; const char *algo; int algo_type; int is_etm; const EVP_MD *digest; unsigned char *key; /* The keysz and key_len are usually the same; they can differ if, for * example, the client always truncates the MAC key len to 16 bits. */ size_t keysz; uint32_t key_len; uint32_t mac_len; }; #define PROXY_SSH_MAC_ALGO_TYPE_HMAC 1 #define PROXY_SSH_MAC_ALGO_TYPE_UMAC64 2 #define PROXY_SSH_MAC_ALGO_TYPE_UMAC128 3 #define PROXY_SSH_MAC_FL_READ_MAC 1 #define PROXY_SSH_MAC_FL_WRITE_MAC 2 /* We need to keep the old MACs around, so that we can handle N arbitrary * packets to/from the client using the old keys, as during rekeying. * Thus we have two read MAC contexts, two write MAC contexts. * The cipher idx variable indicates which of the MACs is currently in use. */ static struct proxy_ssh_mac read_macs[] = { { NULL, NULL, 0, FALSE, NULL, NULL, 0, 0, 0 }, { NULL, NULL, 0, FALSE, NULL, NULL, 0, 0, 0 } }; static HMAC_CTX *hmac_read_ctxs[2]; static struct umac_ctx *umac_read_ctxs[2]; static struct proxy_ssh_mac write_macs[] = { { NULL, NULL, 0, FALSE, NULL, NULL, 0, 0, 0 }, { NULL, NULL, 0, FALSE, NULL, NULL, 0, 0, 0 } }; static HMAC_CTX *hmac_write_ctxs[2]; static struct umac_ctx *umac_write_ctxs[2]; static size_t mac_blockszs[2] = { 0, 0 }; static unsigned int read_mac_idx = 0; static unsigned int write_mac_idx = 0; static void clear_mac(struct proxy_ssh_mac *); static unsigned int get_next_read_index(void) { if (read_mac_idx == 1) { return 0; } return 1; } static unsigned int get_next_write_index(void) { if (write_mac_idx == 1) { return 0; } return 1; } static void switch_read_mac(void) { /* First we can clear the read MAC, kept from rekeying. */ if (read_macs[read_mac_idx].key) { clear_mac(&(read_macs[read_mac_idx])); #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) HMAC_CTX_reset(hmac_read_ctxs[read_mac_idx]); #elif OPENSSL_VERSION_NUMBER > 0x000907000L HMAC_CTX_cleanup(hmac_read_ctxs[read_mac_idx]); #else HMAC_cleanup(hmac_read_ctxs[read_mac_idx]); #endif if (read_macs[read_mac_idx].algo_type == PROXY_SSH_MAC_ALGO_TYPE_UMAC64) { proxy_ssh_umac_reset(umac_read_ctxs[read_mac_idx]); } else if (read_macs[read_mac_idx].algo_type == PROXY_SSH_MAC_ALGO_TYPE_UMAC128) { proxy_ssh_umac128_reset(umac_read_ctxs[read_mac_idx]); } mac_blockszs[read_mac_idx] = 0; /* Now we can switch the index. */ if (read_mac_idx == 1) { read_mac_idx = 0; return; } read_mac_idx = 1; } } static void switch_write_mac(void) { /* First we can clear the write MAC, kept from rekeying. */ if (write_macs[write_mac_idx].key) { clear_mac(&(write_macs[write_mac_idx])); #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) HMAC_CTX_reset(hmac_write_ctxs[write_mac_idx]); #elif OPENSSL_VERSION_NUMBER > 0x000907000L HMAC_CTX_cleanup(hmac_write_ctxs[write_mac_idx]); #else HMAC_cleanup(hmac_write_ctxs[write_mac_idx]); #endif if (write_macs[write_mac_idx].algo_type == PROXY_SSH_MAC_ALGO_TYPE_UMAC64) { proxy_ssh_umac_reset(umac_write_ctxs[write_mac_idx]); } else if (write_macs[write_mac_idx].algo_type == PROXY_SSH_MAC_ALGO_TYPE_UMAC128) { proxy_ssh_umac128_reset(umac_write_ctxs[write_mac_idx]); } /* Now we can switch the index. */ if (write_mac_idx == 1) { write_mac_idx = 0; return; } write_mac_idx = 1; } } static void clear_mac(struct proxy_ssh_mac *mac) { if (mac->key != NULL) { pr_memscrub(mac->key, mac->keysz); free(mac->key); mac->key = NULL; mac->keysz = 0; mac->key_len = 0; } mac->digest = NULL; mac->algo = NULL; } static int init_mac(pool *p, struct proxy_ssh_mac *mac, HMAC_CTX *hmac_ctx, struct umac_ctx *umac_ctx) { /* Currently unused. */ (void) p; if (strcmp(mac->algo, "none") == 0) { return 0; } #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) HMAC_CTX_reset(hmac_ctx); #elif OPENSSL_VERSION_NUMBER > 0x000907000L HMAC_CTX_init(hmac_ctx); #else /* Reset the HMAC context. */ HMAC_Init(hmac_ctx, NULL, 0, NULL); #endif if (mac->algo_type == PROXY_SSH_MAC_ALGO_TYPE_HMAC) { #if OPENSSL_VERSION_NUMBER > 0x000907000L # if OPENSSL_VERSION_NUMBER >= 0x10000001L if (HMAC_Init_ex(hmac_ctx, mac->key, mac->key_len, mac->digest, NULL) != 1) { pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error initializing HMAC: %s", proxy_ssh_crypto_get_errors()); errno = EPERM; return -1; } # else HMAC_Init_ex(hmac_ctx, mac->key, mac->key_len, mac->digest, NULL); # endif /* OpenSSL-1.0.0 and later */ #else HMAC_Init(hmac_ctx, mac->key, mac->key_len, mac->digest); #endif } else if (mac->algo_type == PROXY_SSH_MAC_ALGO_TYPE_UMAC64) { proxy_ssh_umac_reset(umac_ctx); proxy_ssh_umac_init(umac_ctx, mac->key); } else if (mac->algo_type == PROXY_SSH_MAC_ALGO_TYPE_UMAC128) { proxy_ssh_umac128_reset(umac_ctx); proxy_ssh_umac128_init(umac_ctx, mac->key); } return 0; } static int get_mac(struct proxy_ssh_packet *pkt, struct proxy_ssh_mac *mac, HMAC_CTX *hmac_ctx, struct umac_ctx *umac_ctx, int etm_mac, int flags) { unsigned char *mac_data = NULL; unsigned char *buf, *ptr; uint32_t buflen, bufsz = 0, mac_len = 0, len = 0; if (mac->algo_type == PROXY_SSH_MAC_ALGO_TYPE_HMAC) { /* Always leave a little extra room in the buffer. */ bufsz = (sizeof(uint32_t) * 2) + pkt->packet_len + 64; mac_data = pcalloc(pkt->pool, EVP_MAX_MD_SIZE); if (etm_mac == TRUE) { bufsz += proxy_ssh_mac_get_block_size(); } buflen = bufsz; ptr = buf = palloc(pkt->pool, bufsz); len += proxy_ssh_msg_write_int(&buf, &buflen, pkt->seqno); len += proxy_ssh_msg_write_int(&buf, &buflen, pkt->packet_len); if (etm_mac == FALSE) { /* For Encrypt-Then-Mac modes, padding and its length will be part of * the encrypted payload. */ len += proxy_ssh_msg_write_byte(&buf, &buflen, pkt->padding_len); } len += proxy_ssh_msg_write_data(&buf, &buflen, pkt->payload, pkt->payload_len, FALSE); if (etm_mac == FALSE) { len += proxy_ssh_msg_write_data(&buf, &buflen, pkt->padding, pkt->padding_len, FALSE); } #if OPENSSL_VERSION_NUMBER > 0x000907000L # if OPENSSL_VERSION_NUMBER >= 0x10000001L if (HMAC_Init_ex(hmac_ctx, NULL, 0, NULL, NULL) != 1) { pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error resetting HMAC context: %s", proxy_ssh_crypto_get_errors()); errno = EPERM; return -1; } # else HMAC_Init_ex(hmac_ctx, NULL, 0, NULL, NULL); # endif /* OpenSSL-1.0.0 and later */ #else HMAC_Init(hmac_ctx, NULL, 0, NULL); #endif /* OpenSSL-0.9.7 and later */ #if OPENSSL_VERSION_NUMBER >= 0x10000001L if (HMAC_Update(hmac_ctx, ptr, len) != 1) { pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error adding %lu bytes of data to HMAC context: %s", (unsigned long) len, proxy_ssh_crypto_get_errors()); errno = EPERM; return -1; } if (HMAC_Final(hmac_ctx, mac_data, &mac_len) != 1) { pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error finalizing HMAC context: %s", proxy_ssh_crypto_get_errors()); errno = EPERM; return -1; } #else HMAC_Update(hmac_ctx, ptr, len); HMAC_Final(hmac_ctx, mac_data, &mac_len); #endif /* OpenSSL-1.0.0 and later */ } else if (mac->algo_type == PROXY_SSH_MAC_ALGO_TYPE_UMAC64 || mac->algo_type == PROXY_SSH_MAC_ALGO_TYPE_UMAC128) { unsigned char nonce[8], *nonce_ptr; uint32_t nonce_len = 0; /* Always leave a little extra room in the buffer. */ bufsz = sizeof(uint32_t) + pkt->packet_len + 64; mac_data = pcalloc(pkt->pool, EVP_MAX_MD_SIZE); if (etm_mac == TRUE) { bufsz += proxy_ssh_mac_get_block_size(); } buflen = bufsz; ptr = buf = palloc(pkt->pool, bufsz); len += proxy_ssh_msg_write_int(&buf, &buflen, pkt->packet_len); if (etm_mac == FALSE) { /* For Encrypt-Then-Mac modes, padding and its length will be part of * the encrypted payload. */ len += proxy_ssh_msg_write_byte(&buf, &buflen, pkt->padding_len); } len += proxy_ssh_msg_write_data(&buf, &buflen, pkt->payload, pkt->payload_len, FALSE); if (etm_mac == FALSE) { len += proxy_ssh_msg_write_data(&buf, &buflen, pkt->padding, pkt->padding_len, FALSE); } nonce_ptr = nonce; nonce_len = sizeof(nonce); (void) proxy_ssh_msg_write_long(&nonce_ptr, &nonce_len, pkt->seqno); if (mac->algo_type == PROXY_SSH_MAC_ALGO_TYPE_UMAC64) { proxy_ssh_umac_reset(umac_ctx); proxy_ssh_umac_update(umac_ctx, ptr, len); proxy_ssh_umac_final(umac_ctx, mac_data, nonce); mac_len = 8; } else if (mac->algo_type == PROXY_SSH_MAC_ALGO_TYPE_UMAC128) { proxy_ssh_umac128_reset(umac_ctx); proxy_ssh_umac128_update(umac_ctx, ptr, len); proxy_ssh_umac128_final(umac_ctx, mac_data, nonce); mac_len = 16; } } if (mac_len == 0) { pkt->mac = NULL; pkt->mac_len = 0; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error computing MAC using %s: %s", mac->algo, proxy_ssh_crypto_get_errors()); errno = EIO; return -1; } if (mac->mac_len != 0) { mac_len = mac->mac_len; } if (flags & PROXY_SSH_MAC_FL_READ_MAC) { if (memcmp(mac_data, pkt->mac, mac_len) != 0) { unsigned int i = 0; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "MAC from server differs from expected MAC using %s", mac->algo); #ifdef SFTP_DEBUG_PACKET (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "client MAC (len %lu):", (unsigned long) pkt->mac_len); for (i = 0; i < mac_len;) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, " %02x%02x %02x%02x %02x%02x %02x%02x", ((unsigned char *) pkt->mac)[i], ((unsigned char *) pkt->mac)[i+1], ((unsigned char *) pkt->mac)[i+2], ((unsigned char *) pkt->mac)[i+3], ((unsigned char *) pkt->mac)[i+4], ((unsigned char *) pkt->mac)[i+5], ((unsigned char *) pkt->mac)[i+6], ((unsigned char *) pkt->mac)[i+7]); i += 8; } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "client MAC (len %lu):", (unsigned long) mac_len); for (i = 0; i < mac_len;) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, " %02x%02x %02x%02x %02x%02x %02x%02x", ((unsigned char *) mac)[i], ((unsigned char *) mac)[i+1], ((unsigned char *) mac)[i+2], ((unsigned char *) mac)[i+3], ((unsigned char *) mac)[i+4], ((unsigned char *) mac)[i+5], ((unsigned char *) mac)[i+6], ((unsigned char *) mac)[i+7]); i += 8; } #else /* Avoid compiler warning. */ (void) i; #endif errno = EINVAL; return -1; } } else if (flags & PROXY_SSH_MAC_FL_WRITE_MAC) { /* Debugging. */ #ifdef SFTP_DEBUG_PACKET if (pkt->mac_len > 0) { unsigned int i = 0; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "client MAC (len %lu, seqno %lu):", (unsigned long) pkt->mac_len, (unsigned long) pkt->seqno); for (i = 0; i < mac_len;) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, " %02x%02x %02x%02x %02x%02x %02x%02x", ((unsigned char *) pkt->mac)[i], ((unsigned char *) pkt->mac)[i+1], ((unsigned char *) pkt->mac)[i+2], ((unsigned char *) pkt->mac)[i+3], ((unsigned char *) pkt->mac)[i+4], ((unsigned char *) pkt->mac)[i+5], ((unsigned char *) pkt->mac)[i+6], ((unsigned char *) pkt->mac)[i+7]); i += 8; } } #endif } pkt->mac_len = mac_len; pkt->mac = pcalloc(pkt->pool, pkt->mac_len); memcpy(pkt->mac, mac_data, mac_len); return 0; } static int set_mac_key(struct proxy_ssh_mac *mac, const EVP_MD *md, const unsigned char *k, uint32_t klen, const char *h, uint32_t hlen, char *letter, const unsigned char *id, uint32_t id_len) { #if OPENSSL_VERSION_NUMBER < 0x10100000L || \ defined(HAVE_LIBRESSL) EVP_MD_CTX ctx; #endif /* prior to OpenSSL-1.1.0 */ EVP_MD_CTX *pctx; unsigned char *key = NULL; size_t key_sz; uint32_t key_len = 0; key_sz = proxy_ssh_crypto_get_size(EVP_MD_block_size(mac->digest), EVP_MD_size(md)); if (key_sz == 0) { if (strcmp(mac->algo, "none") == 0) { return 0; } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to determine key length for MAC '%s'", mac->algo); errno = EINVAL; return -1; } key = malloc(key_sz); if (key == NULL) { pr_log_pri(PR_LOG_ALERT, MOD_PROXY_VERSION ": Out of memory!"); _exit(1); } #if OPENSSL_VERSION_NUMBER < 0x10100000L || \ defined(HAVE_LIBRESSL) pctx = &ctx; #else pctx = EVP_MD_CTX_new(); #endif /* prior to OpenSSL-1.1.0 */ /* In OpenSSL 0.9.6, many of the EVP_Digest* functions returned void, not * int. Without these ugly OpenSSL version preprocessor checks, the * compiler will error out with "void value not ignored as it ought to be". */ #if OPENSSL_VERSION_NUMBER >= 0x000907000L if (EVP_DigestInit(pctx, md) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error initializing message digest: %s", proxy_ssh_crypto_get_errors()); free(key); # if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) EVP_MD_CTX_free(pctx); # endif /* OpenSSL-1.1.0 and later */ return -1; } #else EVP_DigestInit(pctx, md); #endif #if OPENSSL_VERSION_NUMBER >= 0x000907000L if (EVP_DigestUpdate(pctx, k, klen) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error updating message digest with K: %s", proxy_ssh_crypto_get_errors()); free(key); # if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) EVP_MD_CTX_free(pctx); # endif /* OpenSSL-1.1.0 and later */ return -1; } #else EVP_DigestUpdate(pctx, k, klen); #endif #if OPENSSL_VERSION_NUMBER >= 0x000907000L if (EVP_DigestUpdate(pctx, h, hlen) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error updating message digest with H: %s", proxy_ssh_crypto_get_errors()); free(key); # if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) EVP_MD_CTX_free(pctx); # endif /* OpenSSL-1.1.0 and later */ return -1; } #else EVP_DigestUpdate(pctx, h, hlen); #endif #if OPENSSL_VERSION_NUMBER >= 0x000907000L if (EVP_DigestUpdate(pctx, letter, sizeof(char)) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error updating message digest with '%c': %s", *letter, proxy_ssh_crypto_get_errors()); free(key); # if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) EVP_MD_CTX_free(pctx); # endif /* OpenSSL-1.1.0 and later */ return -1; } #else EVP_DigestUpdate(pctx, letter, sizeof(char)); #endif #if OPENSSL_VERSION_NUMBER >= 0x000907000L if (EVP_DigestUpdate(pctx, (char *) id, id_len) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error updating message digest with ID: %s", proxy_ssh_crypto_get_errors()); free(key); # if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) EVP_MD_CTX_free(pctx); # endif /* OpenSSL-1.1.0 and later */ return -1; } #else EVP_DigestUpdate(pctx, (char *) id, id_len); #endif #if OPENSSL_VERSION_NUMBER >= 0x000907000L if (EVP_DigestFinal(pctx, key, &key_len) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error finalizing message digest: %s", proxy_ssh_crypto_get_errors()); pr_memscrub(key, key_sz); free(key); # if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) EVP_MD_CTX_free(pctx); # endif /* OpenSSL-1.1.0 and later */ return -1; } #else EVP_DigestFinal(pctx, key, &key_len); #endif /* If we need more, keep hashing, as per RFC, until we have enough * material. */ while (key_sz > key_len) { uint32_t len = key_len; pr_signals_handle(); #if OPENSSL_VERSION_NUMBER >= 0x000907000L if (EVP_DigestInit(pctx, md) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error initializing message digest: %s", proxy_ssh_crypto_get_errors()); pr_memscrub(key, key_sz); free(key); # if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) EVP_MD_CTX_free(pctx); # endif /* OpenSSL-1.1.0 and later */ return -1; } #else EVP_DigestInit(pctx, md); #endif #if OPENSSL_VERSION_NUMBER >= 0x000907000L if (EVP_DigestUpdate(pctx, k, klen) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error updating message digest with K: %s", proxy_ssh_crypto_get_errors()); pr_memscrub(key, key_sz); free(key); # if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) EVP_MD_CTX_free(pctx); # endif /* OpenSSL-1.1.0 and later */ return -1; } #else EVP_DigestUpdate(pctx, k, klen); #endif #if OPENSSL_VERSION_NUMBER >= 0x000907000L if (EVP_DigestUpdate(pctx, h, hlen) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error updating message digest with H: %s", proxy_ssh_crypto_get_errors()); pr_memscrub(key, key_sz); free(key); # if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) EVP_MD_CTX_free(pctx); # endif /* OpenSSL-1.1.0 and later */ return -1; } #else EVP_DigestUpdate(pctx, h, hlen); #endif #if OPENSSL_VERSION_NUMBER >= 0x000907000L if (EVP_DigestUpdate(pctx, key, len) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error updating message digest with data: %s", proxy_ssh_crypto_get_errors()); pr_memscrub(key, key_sz); free(key); # if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) EVP_MD_CTX_free(pctx); # endif /* OpenSSL-1.1.0 and later */ return -1; } #else EVP_DigestUpdate(pctx, key, len); #endif #if OPENSSL_VERSION_NUMBER >= 0x000907000L if (EVP_DigestFinal(pctx, key + len, &len) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error finalizing message digest: %s", proxy_ssh_crypto_get_errors()); pr_memscrub(key, key_sz); free(key); # if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) EVP_MD_CTX_free(pctx); # endif /* OpenSSL-1.1.0 and later */ return -1; } #else EVP_DigestFinal(pctx, key + len, &len); #endif key_len += len; } mac->key = key; mac->keysz = key_sz; #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) EVP_MD_CTX_free(pctx); #endif /* OpenSSL-1.1.0 and later */ if (mac->algo_type == PROXY_SSH_MAC_ALGO_TYPE_HMAC) { mac->key_len = EVP_MD_size(mac->digest); } else if (mac->algo_type == PROXY_SSH_MAC_ALGO_TYPE_UMAC64 || mac->algo_type == PROXY_SSH_MAC_ALGO_TYPE_UMAC128) { mac->key_len = EVP_MD_block_size(mac->digest); } if (!proxy_ssh_interop_supports_feature(PROXY_SSH_FEAT_MAC_LEN)) { mac->key_len = 16; } return 0; } size_t proxy_ssh_mac_get_block_size(void) { return mac_blockszs[read_mac_idx]; } void proxy_ssh_mac_set_block_size(size_t blocksz) { if (blocksz > mac_blockszs[read_mac_idx]) { mac_blockszs[read_mac_idx] = blocksz; } } const char *proxy_ssh_mac_get_read_algo(void) { if (read_macs[read_mac_idx].key != NULL || strcmp(read_macs[read_mac_idx].algo, "none") == 0) { return read_macs[read_mac_idx].algo; } /* It is possible for there to be no MAC, as for some ciphers such as * AES-GCM. Rather than returning NULL here, we indicate this by returning * a string (see Issue #1411). */ return "implicit"; } int proxy_ssh_mac_is_read_etm(void) { if (read_macs[read_mac_idx].key != NULL) { return read_macs[read_mac_idx].is_etm; } return FALSE; } int proxy_ssh_mac_set_read_algo(pool *p, const char *algo) { const char *etm_suffix; size_t algo_len, etm_len; uint32_t mac_len; unsigned int idx = read_mac_idx; /* For authenticated encryption ciphers, there is no separate MAC. */ if (proxy_ssh_cipher_get_read_auth_size() > 0) { return 0; } if (read_macs[idx].key != NULL) { /* If we have an existing key, it means that we are currently rekeying. */ idx = get_next_read_index(); } /* Clear any potential UMAC contexts at this index. */ if (umac_read_ctxs[idx] != NULL) { switch (read_macs[idx].algo_type) { case PROXY_SSH_MAC_ALGO_TYPE_UMAC64: proxy_ssh_umac_delete(umac_read_ctxs[idx]); umac_read_ctxs[idx] = NULL; break; case PROXY_SSH_MAC_ALGO_TYPE_UMAC128: proxy_ssh_umac128_delete(umac_read_ctxs[idx]); umac_read_ctxs[idx] = NULL; break; } } read_macs[idx].digest = proxy_ssh_crypto_get_digest(algo, &mac_len); if (read_macs[idx].digest == NULL) { return -1; } /* Note that we use a new pool, each time the algorithm is set (which * happens during key exchange) to prevent undue memory growth for * long-lived sessions with many rekeys. */ if (read_macs[idx].pool != NULL) { destroy_pool(read_macs[idx].pool); } read_macs[idx].pool = make_sub_pool(p); pr_pool_tag(read_macs[idx].pool, "Proxy SFTP MAC read pool"); read_macs[idx].algo = pstrdup(read_macs[idx].pool, algo); if (strcmp(read_macs[idx].algo, "umac-64@openssh.com") == 0 || strcmp(read_macs[idx].algo, "umac-64-etm@openssh.com") == 0) { read_macs[idx].algo_type = PROXY_SSH_MAC_ALGO_TYPE_UMAC64; umac_read_ctxs[idx] = proxy_ssh_umac_alloc(); } else if (strcmp(read_macs[idx].algo, "umac-128@openssh.com") == 0 || strcmp(read_macs[idx].algo, "umac-128-etm@openssh.com") == 0) { read_macs[idx].algo_type = PROXY_SSH_MAC_ALGO_TYPE_UMAC128; umac_read_ctxs[idx] = proxy_ssh_umac128_alloc(); } else { read_macs[idx].algo_type = PROXY_SSH_MAC_ALGO_TYPE_HMAC; } read_macs[idx].mac_len = mac_len; algo_len = strlen(algo); etm_suffix = "-etm@openssh.com"; etm_len = strlen(etm_suffix); if (pr_strnrstr(algo, algo_len, etm_suffix, etm_len, 0) == TRUE) { read_macs[idx].is_etm = TRUE; } return 0; } int proxy_ssh_mac_set_read_key(pool *p, const EVP_MD *md, const unsigned char *k, uint32_t klen, const char *h, uint32_t hlen, int role) { const unsigned char *id = NULL; uint32_t id_len; char letter; size_t blocksz; struct proxy_ssh_mac *mac; HMAC_CTX *hmac_ctx; struct umac_ctx *umac_ctx; /* For authenticated encryption ciphers, there is no separate MAC. */ if (proxy_ssh_cipher_get_read_auth_size() > 0) { return 0; } switch_read_mac(); mac = &(read_macs[read_mac_idx]); hmac_ctx = hmac_read_ctxs[read_mac_idx]; umac_ctx = umac_read_ctxs[read_mac_idx]; id_len = proxy_ssh_session_get_id(&id); /* The letters used depend on the role; see: * https://tools.ietf.org/html/rfc4253#section-7.2 * * If we are the CLIENT, then we use the letters for the "server to client" * flows, since we are READING from the server. */ /* client-to-server HASH(K || H || "E" || session_id) * server-to-client HASH(K || H || "F" || session_id) */ letter = (role == PROXY_SSH_ROLE_CLIENT ? 'F' : 'E'); set_mac_key(mac, md, k, klen, h, hlen, &letter, id, id_len); if (init_mac(p, mac, hmac_ctx, umac_ctx) < 0) { return -1; } if (mac->mac_len == 0) { blocksz = EVP_MD_size(mac->digest); } else { blocksz = mac->mac_len; } proxy_ssh_mac_set_block_size(blocksz); return 0; } int proxy_ssh_mac_read_data(struct proxy_ssh_packet *pkt) { struct proxy_ssh_mac *mac; HMAC_CTX *hmac_ctx; struct umac_ctx *umac_ctx; int res, etm_mac = FALSE; /* For authenticated encryption ciphers, there is no separate MAC. */ if (proxy_ssh_cipher_get_read_auth_size() > 0) { return 0; } etm_mac = proxy_ssh_mac_is_read_etm(); mac = &(read_macs[read_mac_idx]); hmac_ctx = hmac_read_ctxs[read_mac_idx]; umac_ctx = umac_read_ctxs[read_mac_idx]; if (mac->key == NULL) { pkt->mac = NULL; pkt->mac_len = 0; return 0; } res = get_mac(pkt, mac, hmac_ctx, umac_ctx, etm_mac, PROXY_SSH_MAC_FL_READ_MAC); if (res < 0) { return -1; } return 0; } const char *proxy_ssh_mac_get_write_algo(void) { if (write_macs[write_mac_idx].key != NULL) { return write_macs[write_mac_idx].algo; } /* It is possible for there to be no MAC, as for some ciphers such as * AES-GCM. Rather than returning NULL here, we indicate this by returning * a string (see Issue #1411). */ return "implicit"; } int proxy_ssh_mac_is_write_etm(void) { if (write_macs[write_mac_idx].key != NULL) { return write_macs[write_mac_idx].is_etm; } return FALSE; } int proxy_ssh_mac_set_write_algo(pool *p, const char *algo) { const char *etm_suffix; size_t algo_len, etm_len; uint32_t mac_len; unsigned int idx = write_mac_idx; /* For authenticated encryption ciphers, there is no separate MAC. */ if (proxy_ssh_cipher_get_write_auth_size() > 0) { return 0; } if (write_macs[idx].key != NULL) { /* If we have an existing key, it means that we are currently rekeying. */ idx = get_next_write_index(); } /* Clear any potential UMAC contexts at this index. */ if (umac_write_ctxs[idx] != NULL) { switch (write_macs[idx].algo_type) { case PROXY_SSH_MAC_ALGO_TYPE_UMAC64: proxy_ssh_umac_delete(umac_write_ctxs[idx]); umac_write_ctxs[idx] = NULL; break; case PROXY_SSH_MAC_ALGO_TYPE_UMAC128: proxy_ssh_umac128_delete(umac_write_ctxs[idx]); umac_write_ctxs[idx] = NULL; break; } } write_macs[idx].digest = proxy_ssh_crypto_get_digest(algo, &mac_len); if (write_macs[idx].digest == NULL) { return -1; } /* Note that we use a new pool, each time the algorithm is set (which * happens during key exchange) to prevent undue memory growth for * long-lived sessions with many rekeys. */ if (write_macs[idx].pool != NULL) { destroy_pool(write_macs[idx].pool); } write_macs[idx].pool = make_sub_pool(p); pr_pool_tag(write_macs[idx].pool, "Proxy SFTP MAC write pool"); write_macs[idx].algo = pstrdup(write_macs[idx].pool, algo); if (strcmp(write_macs[idx].algo, "umac-64@openssh.com") == 0 || strcmp(write_macs[idx].algo, "umac-64-etm@openssh.com") == 0) { write_macs[idx].algo_type = PROXY_SSH_MAC_ALGO_TYPE_UMAC64; umac_write_ctxs[idx] = proxy_ssh_umac_alloc(); } else if (strcmp(write_macs[idx].algo, "umac-128@openssh.com") == 0 || strcmp(write_macs[idx].algo, "umac-128-etm@openssh.com") == 0) { write_macs[idx].algo_type = PROXY_SSH_MAC_ALGO_TYPE_UMAC128; umac_write_ctxs[idx] = proxy_ssh_umac128_alloc(); } else { write_macs[idx].algo_type = PROXY_SSH_MAC_ALGO_TYPE_HMAC; } write_macs[idx].mac_len = mac_len; algo_len = strlen(algo); etm_suffix = "-etm@openssh.com"; etm_len = strlen(etm_suffix); if (pr_strnrstr(algo, algo_len, etm_suffix, etm_len, 0) == TRUE) { write_macs[idx].is_etm = TRUE; } return 0; } int proxy_ssh_mac_set_write_key(pool *p, const EVP_MD *md, const unsigned char *k, uint32_t klen, const char *h, uint32_t hlen, int role) { const unsigned char *id = NULL; uint32_t id_len; char letter; struct proxy_ssh_mac *mac; HMAC_CTX *hmac_ctx; struct umac_ctx *umac_ctx; /* For authenticated encryption ciphers, there is no separate MAC. */ if (proxy_ssh_cipher_get_write_auth_size() > 0) { return 0; } switch_write_mac(); mac = &(write_macs[write_mac_idx]); hmac_ctx = hmac_write_ctxs[write_mac_idx]; umac_ctx = umac_write_ctxs[write_mac_idx]; id_len = proxy_ssh_session_get_id(&id); /* The letters used depend on the role; see: * https://tools.ietf.org/html/rfc4253#section-7.2 * * If we are the CLIENT, then we use the letters for the "client to server" * flows, since we are WRITING to the server. */ /* client-to-server HASH(K || H || "E" || session_id) * server-to-client HASH(K || H || "F" || session_id) */ letter = (role == PROXY_SSH_ROLE_CLIENT ? 'E' : 'F'); set_mac_key(mac, md, k, klen, h, hlen, &letter, id, id_len); if (init_mac(p, mac, hmac_ctx, umac_ctx) < 0) { return -1; } return 0; } int proxy_ssh_mac_write_data(struct proxy_ssh_packet *pkt) { struct proxy_ssh_mac *mac; HMAC_CTX *hmac_ctx; struct umac_ctx *umac_ctx; int res, etm_mac = FALSE; /* For authenticated encryption ciphers, there is no separate MAC. */ if (proxy_ssh_cipher_get_write_auth_size() > 0) { return 0; } etm_mac = proxy_ssh_mac_is_write_etm(); mac = &(write_macs[write_mac_idx]); hmac_ctx = hmac_write_ctxs[write_mac_idx]; umac_ctx = umac_write_ctxs[write_mac_idx]; if (mac->key == NULL) { pkt->mac = NULL; pkt->mac_len = 0; return 0; } res = get_mac(pkt, mac, hmac_ctx, umac_ctx, etm_mac, PROXY_SSH_MAC_FL_WRITE_MAC); if (res < 0) { return -1; } return 0; } #if OPENSSL_VERSION_NUMBER < 0x10100000L || \ defined(HAVE_LIBRESSL) /* In older versions of OpenSSL, there was not a way to dynamically allocate * an HMAC_CTX object. Thus we have these static objects for those * older versions. */ static HMAC_CTX read_ctx1, read_ctx2; static HMAC_CTX write_ctx1, write_ctx2; #endif /* prior to OpenSSL-1.1.0 */ int proxy_ssh_mac_init(void) { #if OPENSSL_VERSION_NUMBER < 0x10100000L || \ defined(HAVE_LIBRESSL) hmac_read_ctxs[0] = &read_ctx1; hmac_read_ctxs[1] = &read_ctx2; hmac_write_ctxs[0] = &write_ctx1; hmac_write_ctxs[1] = &write_ctx2; #else hmac_read_ctxs[0] = HMAC_CTX_new(); hmac_read_ctxs[1] = HMAC_CTX_new(); hmac_write_ctxs[0] = HMAC_CTX_new(); hmac_write_ctxs[1] = HMAC_CTX_new(); #endif /* OpenSSL-1.1.0 and later */ umac_read_ctxs[0] = NULL; umac_read_ctxs[1] = NULL; umac_write_ctxs[0] = NULL; umac_write_ctxs[1] = NULL; return 0; } int proxy_ssh_mac_free(void) { #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) HMAC_CTX_free(hmac_read_ctxs[0]); HMAC_CTX_free(hmac_read_ctxs[1]); HMAC_CTX_free(hmac_write_ctxs[0]); HMAC_CTX_free(hmac_write_ctxs[1]); #endif /* OpenSSL-1.1.0 and later */ return 0; } #endif /* PR_USE_OPENSSL */ proftpd-mod_proxy-0.9.5/lib/proxy/ssh/misc.c000066400000000000000000000050751475737016700210740ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH miscellany * Copyright (c) 2021 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "proxy/ssh/misc.h" #if defined(PR_USE_OPENSSL) int proxy_ssh_misc_namelist_contains(pool *p, const char *namelist, const char *name) { register unsigned int i; int res = FALSE; pool *tmp_pool; array_header *list; const char **elts; tmp_pool = make_sub_pool(p); pr_pool_tag(tmp_pool, "Contains name pool"); list = pr_str_text_to_array(tmp_pool, namelist, ','); elts = (const char **) list->elts; for (i = 0; i < list->nelts; i++) { if (strcmp(elts[i], name) == 0) { res = TRUE; break; } } destroy_pool(tmp_pool); return res; } const char *proxy_ssh_misc_namelist_shared(pool *p, const char *c2s_names, const char *s2c_names) { register unsigned int i; const char *name = NULL, **client_names, **server_names; pool *tmp_pool; array_header *client_list, *server_list; tmp_pool = make_sub_pool(p); pr_pool_tag(tmp_pool, "Share name pool"); client_list = pr_str_text_to_array(tmp_pool, c2s_names, ','); client_names = (const char **) client_list->elts; server_list = pr_str_text_to_array(tmp_pool, s2c_names, ','); server_names = (const char **) server_list->elts; for (i = 0; i < client_list->nelts; i++) { register unsigned int j; if (name != NULL) { break; } for (j = 0; j < server_list->nelts; j++) { if (strcmp(client_names[i], server_names[j]) == 0) { name = client_names[i]; break; } } } name = pstrdup(p, name); destroy_pool(tmp_pool); return name; } #endif /* PR_USE_OPENSSL */ proftpd-mod_proxy-0.9.5/lib/proxy/ssh/msg.c000066400000000000000000000411701475737016700207230ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH message format * Copyright (c) 2021-2024 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "proxy/session.h" #include "proxy/ssh/ssh2.h" #include "proxy/ssh/msg.h" #include "proxy/ssh/crypto.h" #include "proxy/ssh/disconnect.h" #if defined(PR_USE_OPENSSL) #if defined(PR_USE_OPENSSL_ECC) /* Max GFp field length = 528 bits. SEC1 uncompressed encoding uses 2 * bitstring points. SEC1 specifies a 1 byte point type header. */ # define MAX_ECPOINT_LEN ((528*2 / 8) + 1) #endif /* PR_USE_OPENSSL_ECC */ static const char *trace_channel = "proxy.ssh.msg"; static conn_t *get_backend_conn(void) { const struct proxy_session *proxy_sess; proxy_sess = pr_table_get(session.notes, "mod_proxy.proxy-session", NULL); if (proxy_sess == NULL) { return NULL; } return proxy_sess->backend_ctrl_conn; } uint32_t proxy_ssh_msg_read_byte(pool *p, unsigned char **buf, uint32_t *buflen, unsigned char *byte) { (void) p; if (*buflen < sizeof(char)) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "message format error: unable to read byte (buflen = %lu)", (unsigned long) *buflen); return 0; } memcpy(byte, *buf, sizeof(unsigned char)); (*buf) += sizeof(unsigned char); (*buflen) -= sizeof(unsigned char); return sizeof(unsigned char); } uint32_t proxy_ssh_msg_read_bool(pool *p, unsigned char **buf, uint32_t *buflen, int *b) { unsigned char byte = 0; uint32_t len; (void) p; len = proxy_ssh_msg_read_byte(p, buf, buflen, &byte); if (len == 0) { return 0; } *b = byte; return len; } uint32_t proxy_ssh_msg_read_data(pool *p, unsigned char **buf, uint32_t *buflen, size_t datalen, unsigned char **data) { if (datalen == 0) { return 0; } if (*buflen < datalen) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "message format error: unable to read %lu bytes of raw data " "(buflen = %lu)", (unsigned long) datalen, (unsigned long) *buflen); return 0; } *data = palloc(p, datalen); memcpy(*data, *buf, datalen); (*buf) += datalen; (*buflen) -= datalen; return datalen; } uint32_t proxy_ssh_msg_read_int(pool *p, unsigned char **buf, uint32_t *buflen, uint32_t *val) { (void) p; if (*buflen < sizeof(uint32_t)) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "message format error: unable to read int (buflen = %lu)", (unsigned long) *buflen); return 0; } memcpy(val, *buf, sizeof(uint32_t)); (*buf) += sizeof(uint32_t); (*buflen) -= sizeof(uint32_t); *val = ntohl(*val); return sizeof(uint32_t); } uint32_t proxy_ssh_msg_read_long(pool *p, unsigned char **buf, uint32_t *buflen, uint64_t *val) { unsigned char data[8]; if (*buflen < sizeof(data)) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "message format error: unable to read long (buflen = %lu)", (unsigned long) *buflen); return 0; } memcpy(data, *buf, sizeof(data)); (*buf) += sizeof(data); (*buflen) -= sizeof(data); (*val) = (uint64_t) data[0] << 56; (*val) |= (uint64_t) data[1] << 48; (*val) |= (uint64_t) data[2] << 40; (*val) |= (uint64_t) data[3] << 32; (*val) |= (uint64_t) data[4] << 24; (*val) |= (uint64_t) data[5] << 16; (*val) |= (uint64_t) data[6] << 8; (*val) |= (uint64_t) data[7]; return sizeof(data); } uint32_t proxy_ssh_msg_read_mpint(pool *p, unsigned char **buf, uint32_t *buflen, const BIGNUM **mpint) { unsigned char *mpint_data = NULL; const unsigned char *data = NULL, *ptr = NULL; uint32_t datalen = 0, mpint_len = 0, len = 0, total_len = 0; len = proxy_ssh_msg_read_int(p, buf, buflen, &mpint_len); if (len == 0) { return 0; } if (*buflen < mpint_len) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "message format error: unable to read %lu bytes of mpint (buflen = %lu)", (unsigned long) len, (unsigned long) *buflen); return 0; } if (len > (1024 * 16)) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "message format error: unable to handle mpint of %lu bytes", (unsigned long) len); return 0; } total_len += len; len = proxy_ssh_msg_read_data(p, buf, buflen, mpint_len, &mpint_data); if (len == 0) { return 0; } total_len += len; ptr = (const unsigned char *) mpint_data; if ((ptr[0] & 0x80) != 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "message format error: negative mpint numbers not supported"); return 0; } /* Trim any leading zeros. */ data = ptr; datalen = mpint_len; while (datalen > 0 && *data == 0x00) { pr_signals_handle(); data++; datalen--; } *mpint = BN_bin2bn(data, (int) datalen, NULL); if (*mpint == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "message format error: unable to convert binary mpint: %s", proxy_ssh_crypto_get_errors()); return 0; } return total_len; } uint32_t proxy_ssh_msg_read_string(pool *p, unsigned char **buf, uint32_t *buflen, char **text) { uint32_t data_len = 0, len = 0; /* If there is no data remaining, treat this as if the string is empty * (see Bug#4093). */ if (*buflen == 0) { pr_trace_msg(trace_channel, 9, "malformed message format (buflen = %lu) for reading text, using \"\"", (unsigned long) *buflen); *text = pstrdup(p, ""); return 1; } len = proxy_ssh_msg_read_int(p, buf, buflen, &data_len); if (len == 0) { return 0; } /* We can't use proxy_ssh_msg_read_data() here, since we need to allocate and * populate a buffer that is one byte longer than the len just read in, * for the terminating NUL. */ if (*buflen < data_len) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "message format error: unable to read %lu bytes of string data " "(buflen = %lu)", (unsigned long) data_len, (unsigned long) *buflen); return 0; } *text = palloc(p, data_len + 1); if (data_len > 0) { memcpy(*text, *buf, data_len); (*buf) += data_len; (*buflen) -= data_len; } (*text)[data_len] = '\0'; return len + data_len; } #if defined(PR_USE_OPENSSL) && defined(PR_USE_OPENSSL_ECC) uint32_t proxy_ssh_msg_read_ecpoint(pool *p, unsigned char **buf, uint32_t *buflen, const EC_GROUP *curve, EC_POINT **point) { BN_CTX *bn_ctx; unsigned char *data = NULL; uint32_t datalen = 0, len = 0, total_len = 0; len = proxy_ssh_msg_read_int(p, buf, buflen, &datalen); if (len == 0) { return 0; } if (*buflen < datalen) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "message format error: unable to read %lu bytes of EC point" " (buflen = %lu)", (unsigned long) datalen, (unsigned long) *buflen); return 0; } if (datalen > MAX_ECPOINT_LEN) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "message format error: EC point length too long (%lu > max %lu)", (unsigned long) datalen, (unsigned long) MAX_ECPOINT_LEN); return 0; } total_len += len; len = proxy_ssh_msg_read_data(p, buf, buflen, datalen, &data); if (len == 0) { return 0; } if (data == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "message format error: unable to read %lu bytes of EC point data", (unsigned long) datalen); return 0; } total_len += len; if (data[0] != POINT_CONVERSION_UNCOMPRESSED) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "message format error: EC point data formatted incorrectly " "(leading byte 0x%02x should be 0x%02x)", data[0], POINT_CONVERSION_UNCOMPRESSED); return 0; } bn_ctx = BN_CTX_new(); if (bn_ctx == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error allocating new BN_CTX: %s", proxy_ssh_crypto_get_errors()); return 0; } if (EC_POINT_oct2point(curve, *point, data, datalen, bn_ctx) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "message format error: unable to convert binary EC point data: %s", proxy_ssh_crypto_get_errors()); BN_CTX_free(bn_ctx); return 0; } BN_CTX_free(bn_ctx); pr_memscrub(data, datalen); return total_len; } #endif /* PR_USE_OPENSSL_ECC */ uint32_t proxy_ssh_msg_write_byte(unsigned char **buf, uint32_t *buflen, unsigned char byte) { uint32_t len = 0; if (*buflen < sizeof(char)) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "message format error: unable to write byte (buflen = %lu)", (unsigned long) *buflen); pr_log_stacktrace(proxy_logfd, MOD_PROXY_VERSION); PROXY_SSH_DISCONNECT_CONN(get_backend_conn(), PROXY_SSH_DISCONNECT_BY_APPLICATION, NULL); } len = sizeof(unsigned char); memcpy(*buf, &byte, len); (*buf) += len; (*buflen) -= len; return len; } uint32_t proxy_ssh_msg_write_bool(unsigned char **buf, uint32_t *buflen, unsigned char b) { return proxy_ssh_msg_write_byte(buf, buflen, b == 0 ? 0 : 1); } uint32_t proxy_ssh_msg_write_data(unsigned char **buf, uint32_t *buflen, const unsigned char *data, size_t datalen, int write_len) { uint32_t len = 0; if (write_len) { len += proxy_ssh_msg_write_int(buf, buflen, datalen); } if (*buflen < datalen) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "message format error: unable to write %lu bytes of raw data " "(buflen = %lu)", (unsigned long) datalen, (unsigned long) *buflen); pr_log_stacktrace(proxy_logfd, MOD_PROXY_VERSION); PROXY_SSH_DISCONNECT_CONN(get_backend_conn(), PROXY_SSH_DISCONNECT_BY_APPLICATION, NULL); } if (datalen > 0) { memcpy(*buf, data, datalen); (*buf) += datalen; (*buflen) -= datalen; len += datalen; } return len; } uint32_t proxy_ssh_msg_write_int(unsigned char **buf, uint32_t *buflen, uint32_t val) { uint32_t len; if (*buflen < sizeof(uint32_t)) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "message format error: unable to write int (buflen = %lu)", (unsigned long) *buflen); pr_log_stacktrace(proxy_logfd, MOD_PROXY_VERSION); PROXY_SSH_DISCONNECT_CONN(get_backend_conn(), PROXY_SSH_DISCONNECT_BY_APPLICATION, NULL); } len = sizeof(uint32_t); val = htonl(val); memcpy(*buf, &val, len); (*buf) += len; (*buflen) -= len; return len; } uint32_t proxy_ssh_msg_write_long(unsigned char **buf, uint32_t *buflen, uint64_t val) { unsigned char data[8]; if (*buflen < sizeof(uint64_t)) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "message format error: unable to write long (buflen = %lu)", (unsigned long) *buflen); pr_log_stacktrace(proxy_logfd, MOD_PROXY_VERSION); PROXY_SSH_DISCONNECT_CONN(get_backend_conn(), PROXY_SSH_DISCONNECT_BY_APPLICATION, NULL); } data[0] = (unsigned char) (val >> 56) & 0xFF; data[1] = (unsigned char) (val >> 48) & 0xFF; data[2] = (unsigned char) (val >> 40) & 0xFF; data[3] = (unsigned char) (val >> 32) & 0xFF; data[4] = (unsigned char) (val >> 24) & 0xFF; data[5] = (unsigned char) (val >> 16) & 0xFF; data[6] = (unsigned char) (val >> 8) & 0xFF; data[7] = (unsigned char) val & 0xFF; return proxy_ssh_msg_write_data(buf, buflen, data, sizeof(data), FALSE); } uint32_t proxy_ssh_msg_write_mpint(unsigned char **buf, uint32_t *buflen, const BIGNUM *mpint) { unsigned char *data = NULL; size_t datalen = 0; int res = 0; uint32_t len = 0; if (BN_is_zero(mpint)) { return proxy_ssh_msg_write_int(buf, buflen, 0); } #if OPENSSL_VERSION_NUMBER >= 0x0090801fL if (BN_is_negative(mpint)) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "message format error: unable to write mpint (negative numbers not " "supported)"); pr_log_stacktrace(proxy_logfd, MOD_PROXY_VERSION); PROXY_SSH_DISCONNECT_CONN(get_backend_conn(), PROXY_SSH_DISCONNECT_BY_APPLICATION, NULL); } #endif /* OpenSSL-0.9.8a or later */ datalen = BN_num_bytes(mpint) + 1; if (*buflen < datalen) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "message format error: unable to write %lu bytes of mpint (buflen = %lu)", (unsigned long) datalen, (unsigned long) *buflen); pr_log_stacktrace(proxy_logfd, MOD_PROXY_VERSION); PROXY_SSH_DISCONNECT_CONN(get_backend_conn(), PROXY_SSH_DISCONNECT_BY_APPLICATION, NULL); } data = malloc(datalen); if (data == NULL) { pr_log_pri(PR_LOG_ALERT, MOD_PROXY_VERSION ": Out of memory!"); _exit(1); } data[0] = 0; res = BN_bn2bin(mpint, data + 1); if (res < 0 || res != (int) (datalen - 1)) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "message format error: BN_bn2bin() failed: expected %lu bytes, got %d", (unsigned long) (datalen - 1), res); pr_memscrub(data, datalen); free(data); PROXY_SSH_DISCONNECT_CONN(get_backend_conn(), PROXY_SSH_DISCONNECT_BY_APPLICATION, NULL); /* Needed to avoid compiler (and static code analysis) complaints. */ return 0; } if (data[1] & 0x80) { len += proxy_ssh_msg_write_data(buf, buflen, data, datalen, TRUE); } else { len += proxy_ssh_msg_write_data(buf, buflen, data + 1, datalen - 1, TRUE); } pr_memscrub(data, datalen); free(data); return len; } uint32_t proxy_ssh_msg_write_string(unsigned char **buf, uint32_t *buflen, const char *text) { uint32_t text_len = 0; text_len = strlen(text); return proxy_ssh_msg_write_data(buf, buflen, (const unsigned char *) text, text_len, TRUE); } #if defined(PR_USE_OPENSSL) && defined(PR_USE_OPENSSL_ECC) uint32_t proxy_ssh_msg_write_ecpoint(unsigned char **buf, uint32_t *buflen, const EC_GROUP *curve, const EC_POINT *point) { unsigned char *data = NULL; size_t datalen = 0; uint32_t len = 0; BN_CTX *bn_ctx; bn_ctx = BN_CTX_new(); if (bn_ctx == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error allocating new BN_CTX: %s", proxy_ssh_crypto_get_errors()); pr_log_stacktrace(proxy_logfd, MOD_PROXY_VERSION); PROXY_SSH_DISCONNECT_CONN(get_backend_conn(), PROXY_SSH_DISCONNECT_BY_APPLICATION, NULL); } datalen = EC_POINT_point2oct(curve, point, POINT_CONVERSION_UNCOMPRESSED, NULL, 0, bn_ctx); if (datalen > MAX_ECPOINT_LEN) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "message format error: EC point length too long (%lu > max %lu)", (unsigned long) datalen, (unsigned long) MAX_ECPOINT_LEN); pr_log_stacktrace(proxy_logfd, MOD_PROXY_VERSION); PROXY_SSH_DISCONNECT_CONN(get_backend_conn(), PROXY_SSH_DISCONNECT_BY_APPLICATION, NULL); } if (*buflen < datalen) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "message format error: unable to write %lu bytes of EC point " "(buflen = %lu)", (unsigned long) datalen, (unsigned long) *buflen); pr_log_stacktrace(proxy_logfd, MOD_PROXY_VERSION); PROXY_SSH_DISCONNECT_CONN(get_backend_conn(), PROXY_SSH_DISCONNECT_BY_APPLICATION, NULL); } data = malloc(datalen); if (data == NULL) { pr_log_pri(PR_LOG_ALERT, MOD_PROXY_VERSION ": Out of memory!"); _exit(1); } if (EC_POINT_point2oct(curve, point, POINT_CONVERSION_UNCOMPRESSED, data, datalen, bn_ctx) != datalen) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error writing EC point data: Length mismatch"); pr_memscrub(data, datalen); free(data); BN_CTX_free(bn_ctx); PROXY_SSH_DISCONNECT_CONN(get_backend_conn(), PROXY_SSH_DISCONNECT_BY_APPLICATION, NULL); /* Needed to avoid compiler (and static code analysis) complaints. */ return 0; } len = proxy_ssh_msg_write_data(buf, buflen, (const unsigned char *) data, datalen, TRUE); pr_memscrub(data, datalen); free(data); BN_CTX_free(bn_ctx); return len; } #endif /* PR_USE_OPENSSL_ECC */ #endif /* PR_USE_OPENSSL */ proftpd-mod_proxy-0.9.5/lib/proxy/ssh/packet.c000066400000000000000000002052041475737016700214040ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH packet IO * Copyright (c) 2021-2023 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "proxy/ssh/ssh2.h" #include "proxy/ssh/packet.h" #include "proxy/ssh/msg.h" #include "proxy/ssh/disconnect.h" #include "proxy/ssh/cipher.h" #include "proxy/ssh/mac.h" #include "proxy/ssh/compress.h" #include "proxy/ssh/kex.h" #include "proxy/ssh/auth.h" #include "proxy/ssh/service.h" #if HAVE_SYS_UIO_H # include #endif #ifndef MAX # define MAX(x, y) (((x) > (y)) ? (x) : (y)) # define MIN(x, y) (((x) < (y)) ? (x) : (y)) #endif #if defined(PR_USE_OPENSSL) extern pr_response_t *resp_list, *resp_err_list; static int (*frontend_packet_write)(int, void *) = NULL; static uint32_t packet_client_seqno = 0; static uint32_t packet_server_seqno = 0; /* Maximum length of the payload data of an SSH2 packet we're willing to * accept. Any packets reporting a payload length longer than this will be * ignored/dropped. */ #define PROXY_SSH_PACKET_MAX_PAYLOAD_LEN (256 * 1024) static int poll_timeout_secs = -1; static unsigned long poll_timeout_ms = 0; static const char *client_version = PROXY_SSH_ID_DEFAULT_STRING; static const char *version_id = PROXY_SSH_ID_DEFAULT_STRING "\r\n"; static int sent_version_id = FALSE; static void is_server_alive(conn_t *conn); /* Count of the number of "server alive" messages sent without a response. */ static unsigned int server_alive_max = 0, server_alive_count = 0; static unsigned int server_alive_interval = 0; static const char *trace_channel = "proxy.ssh.packet"; static const char *timing_channel = "timing"; #define DEFAULT_POLL_ATTEMPTS 3 static unsigned long poll_attempts = DEFAULT_POLL_ATTEMPTS; int proxy_ssh_packet_conn_mpoll(conn_t *frontend_conn, conn_t *backend_conn, int io) { fd_set rfds, wfds; struct timeval tv; int res, frontend_sockfd = -1, backend_sockfd = -1, maxfd = -1, timeout_sec, using_server_alive = FALSE; unsigned int ntimeouts = 0; unsigned long timeout_usec = 0; if (poll_timeout_secs == -1) { /* If we have "server alive" timeout interval configured, use that -- * but only if we have already done the key exchange, and are not * rekeying. * * Otherwise, we use the default (i.e. TimeoutIdle). */ if (server_alive_interval > 0 && (!(proxy_sess_state & PROXY_SESS_STATE_SSH_REKEYING) && (proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_AUTH))) { timeout_sec = server_alive_interval; using_server_alive = TRUE; } else { timeout_sec = pr_data_get_timeout(PR_DATA_TIMEOUT_IDLE); } timeout_usec = 0; } else { timeout_sec = poll_timeout_secs; timeout_usec = (poll_timeout_ms * 1000); } tv.tv_sec = timeout_sec; tv.tv_usec = timeout_usec; if (io == PROXY_SSH_PACKET_IO_READ) { if (frontend_conn != NULL) { frontend_sockfd = frontend_conn->rfd; } if (backend_conn != NULL) { backend_sockfd = backend_conn->rfd; } } else { if (frontend_conn != NULL) { frontend_sockfd = frontend_conn->wfd; } if (backend_conn != NULL) { backend_sockfd = backend_conn->wfd; } } pr_trace_msg(trace_channel, 19, "waiting for max of %lu secs %lu ms while polling sockets %d/%d for %s " "using select(2)", (unsigned long) tv.tv_sec, (unsigned long) (tv.tv_usec / 1000), frontend_sockfd, backend_sockfd, io == PROXY_SSH_PACKET_IO_READ ? "reading" : "writing"); while (TRUE) { pr_signals_handle(); FD_ZERO(&rfds); FD_ZERO(&wfds); switch (io) { case PROXY_SSH_PACKET_IO_READ: { if (frontend_conn != NULL) { FD_SET(frontend_sockfd, &rfds); if (frontend_sockfd > maxfd) { maxfd = frontend_sockfd; } } if (backend_conn != NULL) { FD_SET(backend_sockfd, &rfds); if (backend_sockfd > maxfd) { maxfd = backend_sockfd; } } res = select(maxfd + 1, &rfds, NULL, NULL, &tv); break; } case PROXY_SSH_PACKET_IO_WRITE: { if (frontend_conn != NULL) { FD_SET(frontend_sockfd, &wfds); if (frontend_sockfd > maxfd) { maxfd = frontend_sockfd; } } if (backend_conn != NULL) { FD_SET(backend_sockfd, &wfds); if (backend_sockfd > maxfd) { maxfd = backend_sockfd; } } res = select(maxfd + 1, NULL, &wfds, NULL, &tv); break; } default: errno = EINVAL; return -1; } if (res < 0) { int xerrno = errno; if (xerrno == EINTR) { pr_signals_handle(); continue; } pr_trace_msg(trace_channel, 18, "error calling select(2) on fd %d/%d: %s", frontend_sockfd, backend_sockfd, strerror(xerrno)); errno = xerrno; return -1; } else if (res == 0) { tv.tv_sec = timeout_sec; tv.tv_usec = timeout_usec; ntimeouts++; if (ntimeouts > poll_attempts) { pr_trace_msg(trace_channel, 18, "polling on socket %d/%d timed out after %lu sec %lu ms " "(%u attempts)", frontend_sockfd, backend_sockfd, (unsigned long) tv.tv_sec, (unsigned long) (tv.tv_usec / 1000), ntimeouts); errno = ETIMEDOUT; return -1; } if (using_server_alive == TRUE) { if (backend_conn != NULL) { is_server_alive(backend_conn); } } else { pr_trace_msg(trace_channel, 18, "polling on socket %d/%d timed out after %lu sec %lu ms, " "trying again (timeout #%u)", frontend_sockfd, backend_sockfd, (unsigned long) tv.tv_sec, (unsigned long) (tv.tv_usec / 1000), ntimeouts); (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "polling on socket %d/%d timed out after %lu sec %lu ms, " "trying again (timeout #%u)", frontend_sockfd, backend_sockfd, (unsigned long) tv.tv_sec, (unsigned long) (tv.tv_usec / 1000), ntimeouts); } continue; } break; } /* Which connection has data? Return 0 if it's the frontend connection, * otherwise return 1 for the backend connection. */ if (frontend_conn != NULL) { if (io == PROXY_SSH_PACKET_IO_READ) { if (FD_ISSET(frontend_sockfd, &rfds)) { res = 0; } } else { if (FD_ISSET(frontend_sockfd, &wfds)) { res = 0; } } } if (backend_conn != NULL) { if (io == PROXY_SSH_PACKET_IO_READ) { if (FD_ISSET(backend_sockfd, &rfds)) { res = 1; } } else { if (FD_ISSET(backend_sockfd, &wfds)) { res = 1; } } } return res; } int proxy_ssh_packet_conn_poll(conn_t *conn, int io) { int res; /* This is only ever called on the backend connection. */ res = proxy_ssh_packet_conn_mpoll(NULL, conn, io); if (res < 0) { return -1; } return 0; } /* The purpose of conn_read() is to loop until either we have read in the * requested reqlen from the socket, or the socket gives us an I/O error. * We want to prevent short reads from causing problems elsewhere (e.g. * in the decipher or MAC code). * * It is the caller's responsibility to ensure that buf is large enough to * hold reqlen bytes. */ int proxy_ssh_packet_conn_read(conn_t *conn, void *buf, size_t reqlen, int flags) { void *ptr; size_t remainlen; if (reqlen == 0) { return 0; } errno = 0; ptr = buf; remainlen = reqlen; while (remainlen > 0) { int res; if (proxy_ssh_packet_conn_poll(conn, PROXY_SSH_PACKET_IO_READ) < 0) { return -1; } /* The socket we accept is blocking, thus there's no need to handle * EAGAIN/EWOULDBLOCK errors. */ res = read(conn->rfd, ptr, remainlen); while (res <= 0) { if (res < 0) { int xerrno = errno; if (xerrno == EINTR) { pr_signals_handle(); res = read(conn->rfd, ptr, remainlen); continue; } pr_trace_msg(trace_channel, 16, "error reading from server (fd %d): %s", conn->rfd, strerror(xerrno)); (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error reading from server (fd %d): %s", conn->rfd, strerror(xerrno)); errno = xerrno; /* We explicitly disconnect the server here, rather than sending * a DISCONNECT message, because the errors below all indicate * a problem with the TCP connection, such that trying to write * more data on that connection would cause problems. */ if (errno == ECONNRESET || errno == ECONNABORTED || #ifdef ETIMEDOUT errno == ETIMEDOUT || #endif /* ETIMEDOUT */ #ifdef ENOTCONN errno == ENOTCONN || #endif /* ENOTCONN */ #ifdef ESHUTDOWN errno == ESHUTDOWN || #endif /* ESHUTDOWNN */ errno == EPIPE) { xerrno = errno; pr_trace_msg(trace_channel, 16, "disconnecting server (%s)", strerror(xerrno)); (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "disconnecting server (%s)", strerror(xerrno)); pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_CLIENT_EOF, strerror(xerrno)); } return -1; } else { /* If we read zero bytes here, treat it as an EOF and hang up on * the uncommunicative client. */ pr_trace_msg(trace_channel, 16, "%s", "disconnecting server (received EOF)"); (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "disconnecting server (received EOF)"); pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_CLIENT_EOF, NULL); } } session.total_raw_in += reqlen; if ((size_t) res == remainlen) { break; } if (flags & PROXY_SSH_PACKET_READ_FL_PESSIMISTIC) { pr_trace_msg(trace_channel, 20, "read %lu bytes, expected %lu bytes; " "pessimistically returning", (unsigned long) res, (unsigned long) remainlen); break; } pr_trace_msg(trace_channel, 20, "read %lu bytes, expected %lu bytes; " "reading more", (unsigned long) res, (unsigned long) remainlen); ptr = ((char *) ptr + res); remainlen -= res; } return reqlen; } static const char *get_msg_cmd_desc(unsigned char msg_type) { const char *desc; desc = proxy_ssh_packet_get_msg_type_desc(msg_type); if (strncmp(desc, "SSH_MSG_", 8) == 0) { desc += 8; } return desc; } void proxy_ssh_packet_log_cmd(struct proxy_ssh_packet *pkt, int from_frontend) { cmd_rec *cmd; const char *pkt_cmd, *pkt_note, *pkt_note_text; /* Get a short version of the packet type for our cmd_rec/logging. */ pkt_cmd = get_msg_cmd_desc(proxy_ssh_packet_peek_msg_type(pkt)); /* XXX What to use as the cmd_rec arg? channel ID for CHANNEL_ commands; * what else? Or maybe just hardcode "-" for now? */ cmd = pr_cmd_alloc(pkt->pool, 1, pstrdup(pkt->pool, pkt_cmd)); cmd->arg = pstrdup(pkt->pool, "-"); cmd->cmd_class = CL_MISC|CL_SSH; /* Add a note to indicate the destination/target for this packet, be it * "frontend" or "backend". */ pkt_note = "proxy.ssh.direction"; pkt_note_text = from_frontend == TRUE ? "backend" : "frontend"; if (pr_table_add_dup(cmd->notes, pkt_note, pkt_note_text, 0) < 0) { int xerrno = errno; if (xerrno != EEXIST) { pr_trace_msg(trace_channel, 8, "error setting '%s' note: %s", pkt_note, strerror(xerrno)); } } pr_cmd_dispatch_phase(cmd, LOG_CMD, 0); destroy_pool(cmd->pool); } static void handle_global_request_msg(struct proxy_ssh_packet *pkt, const struct proxy_session *proxy_sess, int from_frontend) { unsigned char *buf, *ptr; uint32_t buflen, len; char *request_name; int want_reply; buf = pkt->payload; buflen = pkt->payload_len; /* Skip past the message type. */ buf += sizeof(char); buflen -= sizeof(char); len = proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &request_name); if (proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_KEX) { /* For most GLOBAL_REQUEST packets, once we have completed our kex, * we proxy the packet to the frontend client. * * However, some GLOBAL_REQUEST types, such as the OpenSSH hostkey rotation * extension, is NOT suitable for proxying to the frontend client, for * the frontend client is concerned with our hostkeys, not the backend * hostkeys. */ if (strcmp(request_name, "hostkeys-00@openssh.com") != 0) { proxy_ssh_packet_proxied(proxy_sess, pkt, from_frontend); return; } } len = proxy_ssh_msg_read_bool(pkt->pool, &buf, &buflen, &want_reply); (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "server sent GLOBAL_REQUEST for '%s', %s", request_name, want_reply ? "denying" : "ignoring"); if (want_reply == TRUE) { struct proxy_ssh_packet *pkt2; uint32_t bufsz; int res; buflen = bufsz = 1024; ptr = buf = palloc(pkt->pool, bufsz); len = proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_REQUEST_FAILURE); pkt2 = proxy_ssh_packet_create(pkt->pool); pkt2->payload = ptr; pkt2->payload_len = len; res = proxy_ssh_packet_write(proxy_sess->backend_ctrl_conn, pkt2); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error writing REQUEST_FAILURE message: %s", strerror(errno)); } } destroy_pool(pkt->pool); } static void handle_server_alive_msg(struct proxy_ssh_packet *pkt, char msg_type) { const char *msg_desc; msg_desc = proxy_ssh_packet_get_msg_type_desc(msg_type); pr_trace_msg(trace_channel, 12, "server sent %s message, considering server alive", msg_desc); server_alive_count = 0; destroy_pool(pkt->pool); } static int handle_frontend_rekey(struct proxy_ssh_packet *pkt, const struct proxy_session *proxy_sess) { /* Reset the mod_sftp internal machinery, such that it handles this * frontend-requested rekey. */ pr_trace_msg("proxy.ssh", 19, "frontend-initiated rekeying STARTED, resetting mod_sftp packet handler"); proxy_ssh_packet_set_frontend_packet_handle(pkt->pool, NULL); /* Make sure to remove our listener for mod_sftp's read-loop, until such * time as the frontend rekeying completes. */ pr_event_unregister(&proxy_module, "mod_sftp.ssh2.read-poll", NULL); /* Do NOT destroy this packet's pool! */ errno = ENOSYS; return -1; } static void is_server_alive(conn_t *conn) { unsigned char *buf, *ptr; uint32_t bufsz, buflen, len = 0; struct proxy_ssh_packet *pkt; pool *tmp_pool; if (++server_alive_count > server_alive_max) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "ProxySFTPServerAlive threshold (max %u checks, %u sec interval) " "reached, disconnecting client", server_alive_max, server_alive_interval); PROXY_SSH_DISCONNECT_CONN(conn, PROXY_SSH_DISCONNECT_BY_APPLICATION, "server alive threshold reached"); } tmp_pool = make_sub_pool(session.pool); bufsz = buflen = 64; ptr = buf = palloc(tmp_pool, bufsz); pr_trace_msg(trace_channel, 9, "sending GLOBAL_REQUEST (keepalive@proftpd.org)"); len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_GLOBAL_REQUEST); len += proxy_ssh_msg_write_string(&buf, &buflen, "keepalive@proftpd.org"); len += proxy_ssh_msg_write_bool(&buf, &buflen, TRUE); pkt = proxy_ssh_packet_create(tmp_pool); pkt->payload = ptr; pkt->payload_len = len; (void) proxy_ssh_packet_write(conn, pkt); destroy_pool(tmp_pool); } /* Attempt to read in a random amount of data (up to the maximum amount of * SSH2 packet data we support) from the socket. This is used to help * mitigate the plaintext recovery attack described by CPNI-957037. * * Technically this is only necessary if a CBC mode cipher is in use, but * there should be no harm in using for any cipher; we are going to * disconnect the client after reading this data anyway. */ static void read_packet_discard(conn_t *conn) { size_t buflen; buflen = PROXY_SSH_MAX_PACKET_LEN - ((int) (PROXY_SSH_MAX_PACKET_LEN * (rand() / (RAND_MAX + 1.0)))); pr_trace_msg(trace_channel, 3, "reading %lu bytes of data for discarding", (unsigned long) buflen); if (buflen > 0) { char buf[PROXY_SSH_MAX_PACKET_LEN]; int flags; /* We don't necessarily want to wait for the entire random amount of data * to be read in. */ flags = PROXY_SSH_PACKET_READ_FL_PESSIMISTIC; proxy_ssh_packet_conn_read(conn, buf, buflen, flags); } return; } static int read_packet_len(conn_t *conn, struct proxy_ssh_packet *pkt, unsigned char *buf, size_t *offset, size_t *buflen, size_t bufsz, int etm_mac) { uint32_t packet_len = 0, len = 0; size_t readsz; int res; unsigned char *ptr = NULL; readsz = proxy_ssh_cipher_get_read_block_size(); /* Since the packet length may be encrypted, we need to read in the first * cipher_block_size bytes from the socket, and try to decrypt them, to know * how many more bytes there are in the packet. */ if (pkt->aad_len > 0) { /* If we are dealing with an authenticated encryption algorithm, or an * ETM mode, read enough to include the AAD. For ETM modes, leave the * first block for later. */ if (etm_mac == TRUE) { readsz = pkt->aad_len; } else { readsz += pkt->aad_len; } } res = proxy_ssh_packet_conn_read(conn, buf, readsz, 0); if (res < 0) { return res; } len = res; if (proxy_ssh_cipher_read_data(pkt, buf, readsz, &ptr, &len) < 0) { return -1; } memmove(&packet_len, ptr, sizeof(uint32_t)); pkt->packet_len = ntohl(packet_len); ptr += sizeof(uint32_t); len -= sizeof(uint32_t); /* Copy the remaining unencrypted bytes from the block into the given * buffer. */ if (len > 0) { memmove(buf, ptr, len); *buflen = (size_t) len; } *offset = 0; return 0; } static int read_packet_padding_len(conn_t *conn, struct proxy_ssh_packet *pkt, unsigned char *buf, size_t *offset, size_t *buflen, size_t bufsz) { if (*buflen > sizeof(char)) { /* XXX Assume the data in the buffer is unencrypted, and thus usable. */ memmove(&pkt->padding_len, buf + *offset, sizeof(char)); /* Advance the buffer past the byte we just read off. */ *offset += sizeof(char); *buflen -= sizeof(char); return 0; } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to read padding len: not enough data in buffer (%u bytes)", (unsigned int) *buflen); return -1; } static int read_packet_payload(conn_t *conn, struct proxy_ssh_packet *pkt, unsigned char *buf, size_t *offset, size_t *buflen, size_t bufsz, int etm_mac) { unsigned char *ptr = NULL; int res; uint32_t payload_len = pkt->payload_len, padding_len = 0, auth_len = 0, data_len, len = 0; /* For authenticated encryption or ETM modes, we will NOT have the * pkt->padding_len field yet. * * For authenticated encryption, we need to read in the first block, then * decrypt it, to find the padding. * * For ETM, we only want to find the payload and padding AFTER we've read * the entire (encrypted) payload, MAC'd it, THEN decrypt it. */ if (pkt->padding_len > 0) { padding_len = pkt->padding_len; } auth_len = proxy_ssh_cipher_get_read_auth_size(); if (payload_len + padding_len + auth_len == 0 && etm_mac == FALSE) { return 0; } if (payload_len > 0) { /* We don't want to reject the packet outright yet; but we can ignore * the payload data we're going to read in. This packet will fail * eventually anyway. */ if (payload_len > PROXY_SSH_PACKET_MAX_PAYLOAD_LEN) { pr_trace_msg(trace_channel, 20, "payload len (%lu bytes) exceeds max payload len (%lu), " "ignoring payload", (unsigned long) payload_len, (unsigned long) PROXY_SSH_PACKET_MAX_PAYLOAD_LEN); pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "server sent buggy/malicious packet payload length, ignoring"); errno = EPERM; return -1; } pkt->payload = pcalloc(pkt->pool, payload_len); } /* If there's data in the buffer we received, it's probably already part * of the payload, unencrypted. That will leave the remaining payload * data, if any, to be read in and decrypted. */ if (*buflen > 0) { if (*buflen < payload_len) { memmove(pkt->payload, buf + *offset, *buflen); payload_len -= *buflen; *offset = 0; *buflen = 0; } else { /* There's enough already for the payload length. Nice. */ memmove(pkt->payload, buf + *offset, payload_len); *offset += payload_len; *buflen -= payload_len; payload_len = 0; } } /* The padding length is required to be greater than zero. However, we may * not know the padding length yet, as for authenticated encryption or ETM * modes. */ if (padding_len > 0) { pkt->padding = pcalloc(pkt->pool, padding_len); } /* If there's data in the buffer we received, it's probably already part * of the padding, unencrypted. That will leave the remaining padding * data, if any, to be read in and decrypted. */ if (*buflen > 0 && padding_len > 0) { if (*buflen < padding_len) { memmove(pkt->padding, buf + *offset, *buflen); padding_len -= *buflen; *offset = 0; *buflen = 0; } else { /* There's enough already for the padding length. Nice. */ memmove(pkt->padding, buf + *offset, padding_len); *offset += padding_len; *buflen -= padding_len; padding_len = 0; } } if (etm_mac == TRUE) { data_len = pkt->packet_len; } else { data_len = payload_len + padding_len + auth_len; } if (data_len == 0) { return 0; } if (data_len > bufsz) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "remaining packet data (%lu bytes) exceeds packet buffer size (%lu " "bytes)", (unsigned long) data_len, (unsigned long) bufsz); errno = EPERM; return -1; } res = proxy_ssh_packet_conn_read(conn, buf + *offset, data_len, 0); if (res < 0) { return res; } len = res; /* For ETM modes, we do NOT want to decrypt the data yet; we need to read/ * compare MACs first. */ if (etm_mac == TRUE) { *buflen = res; } else { if (proxy_ssh_cipher_read_data(pkt, buf + *offset, data_len, &ptr, &len) < 0) { return -1; } if (payload_len > 0) { memmove(pkt->payload + (pkt->payload_len - payload_len), ptr, payload_len); } memmove(pkt->padding + (pkt->padding_len - padding_len), ptr + payload_len, padding_len); } return 0; } static int read_packet_mac(conn_t *conn, struct proxy_ssh_packet *pkt, unsigned char *buf) { int res; uint32_t mac_len = pkt->mac_len; if (mac_len == 0) { return 0; } res = proxy_ssh_packet_conn_read(conn, buf, mac_len, 0); if (res < 0) { return res; } pkt->mac = palloc(pkt->pool, pkt->mac_len); memmove(pkt->mac, buf, res); return 0; } struct proxy_ssh_packet *proxy_ssh_packet_create(pool *p) { pool *tmp_pool; struct proxy_ssh_packet *pkt; tmp_pool = make_sub_pool(p); pr_pool_tag(tmp_pool, "Proxy SSH2 packet pool"); pkt = pcalloc(tmp_pool, sizeof(struct proxy_ssh_packet)); pkt->pool = tmp_pool; pkt->m = &proxy_module; pkt->packet_len = 0; pkt->payload = NULL; pkt->payload_len = 0; pkt->padding_len = 0; pkt->aad = NULL; pkt->aad_len = 0; return pkt; } char proxy_ssh_packet_get_msg_type(struct proxy_ssh_packet *pkt) { char msg_type; memmove(&msg_type, pkt->payload, sizeof(char)); pkt->payload += sizeof(char); pkt->payload_len -= sizeof(char); return msg_type; } char proxy_ssh_packet_peek_msg_type(const struct proxy_ssh_packet *pkt) { char msg_type; memmove(&msg_type, pkt->payload, sizeof(char)); return msg_type; } const char *proxy_ssh_packet_get_msg_type_desc(unsigned char msg_type) { switch (msg_type) { case PROXY_SSH_MSG_DISCONNECT: return "SSH_MSG_DISCONNECT"; case PROXY_SSH_MSG_IGNORE: return "SSH_MSG_IGNORE"; case PROXY_SSH_MSG_UNIMPLEMENTED: return "SSH_MSG_UNIMPLEMENTED"; case PROXY_SSH_MSG_DEBUG: return "SSH_MSG_DEBUG"; case PROXY_SSH_MSG_SERVICE_REQUEST: return "SSH_MSG_SERVICE_REQUEST"; case PROXY_SSH_MSG_SERVICE_ACCEPT: return "SSH_MSG_SERVICE_ACCEPT"; case PROXY_SSH_MSG_EXT_INFO: return "SSH_MSG_EXT_INFO"; case PROXY_SSH_MSG_KEXINIT: return "SSH_MSG_KEXINIT"; case PROXY_SSH_MSG_NEWKEYS: return "SSH_MSG_NEWKEYS"; case PROXY_SSH_MSG_KEX_DH_INIT: return "SSH_MSG_KEX_DH_INIT"; case PROXY_SSH_MSG_KEX_DH_REPLY: return "SSH_MSG_KEX_DH_REPLY"; case PROXY_SSH_MSG_KEX_DH_GEX_INIT: return "SSH_MSG_KEX_DH_GEX_INIT"; case PROXY_SSH_MSG_KEX_DH_GEX_REPLY: return "SSH_MSG_KEX_DH_GEX_REPLY"; case PROXY_SSH_MSG_KEX_DH_GEX_REQUEST: return "SSH_MSG_KEX_DH_GEX_REQUEST"; case PROXY_SSH_MSG_USER_AUTH_REQUEST: return "SSH_MSG_USERAUTH_REQUEST"; case PROXY_SSH_MSG_USER_AUTH_FAILURE: return "SSH_MSG_USERAUTH_FAILURE"; case PROXY_SSH_MSG_USER_AUTH_SUCCESS: return "SSH_MSG_USERAUTH_SUCCESS"; case PROXY_SSH_MSG_USER_AUTH_BANNER: return "SSH_MSG_USERAUTH_BANNER"; case PROXY_SSH_MSG_USER_AUTH_PASSWD: return "SSH_MSG_USERAUTH_PASSWD"; case PROXY_SSH_MSG_USER_AUTH_INFO_RESP: return "SSH_MSG_USERAUTH_INFO_RESP"; case PROXY_SSH_MSG_GLOBAL_REQUEST: return "SSH_MSG_GLOBAL_REQUEST"; case PROXY_SSH_MSG_REQUEST_SUCCESS: return "SSH_MSG_REQUEST_SUCCESS"; case PROXY_SSH_MSG_REQUEST_FAILURE: return "SSH_MSG_REQUEST_FAILURE"; case PROXY_SSH_MSG_CHANNEL_OPEN: return "SSH_MSG_CHANNEL_OPEN"; case PROXY_SSH_MSG_CHANNEL_OPEN_CONFIRMATION: return "SSH_MSG_CHANNEL_OPEN_CONFIRMATION"; case PROXY_SSH_MSG_CHANNEL_OPEN_FAILURE: return "SSH_MSG_CHANNEL_OPEN_FAILURE"; case PROXY_SSH_MSG_CHANNEL_WINDOW_ADJUST: return "SSH_MSG_CHANNEL_WINDOW_ADJUST"; case PROXY_SSH_MSG_CHANNEL_DATA: return "SSH_MSG_CHANNEL_DATA"; case PROXY_SSH_MSG_CHANNEL_EXTENDED_DATA: return "SSH_MSG_CHANNEL_EXTENDED_DATA"; case PROXY_SSH_MSG_CHANNEL_EOF: return "SSH_MSG_CHANNEL_EOF"; case PROXY_SSH_MSG_CHANNEL_CLOSE: return "SSH_MSG_CHANNEL_CLOSE"; case PROXY_SSH_MSG_CHANNEL_REQUEST: return "SSH_MSG_CHANNEL_REQUEST"; case PROXY_SSH_MSG_CHANNEL_SUCCESS: return "SSH_MSG_CHANNEL_SUCCESS"; case PROXY_SSH_MSG_CHANNEL_FAILURE: return "SSH_MSG_CHANNEL_FAILURE"; } return "(unknown)"; } int proxy_ssh_packet_get_poll_attempts(unsigned int *nattempts) { if (nattempts == NULL) { errno = EINVAL; return -1; } *nattempts = poll_attempts; return 0; } int proxy_ssh_packet_set_poll_attempts(unsigned int nattempts) { if (nattempts == 0) { poll_attempts = DEFAULT_POLL_ATTEMPTS; } else { poll_attempts = nattempts; } return 0; } int proxy_ssh_packet_get_poll_timeout(int *secs, unsigned long *ms) { if (secs == NULL || ms == NULL) { errno = EINVAL; return -1; } *secs = poll_timeout_secs; *ms = poll_timeout_ms; return 0; } int proxy_ssh_packet_set_poll_timeout(int secs, unsigned long ms) { if (secs < 0) { poll_timeout_secs = -1; poll_timeout_ms = 0; } else { poll_timeout_secs = secs; poll_timeout_ms = ms; } return 0; } int proxy_ssh_packet_set_server_alive(unsigned int max, unsigned int interval) { server_alive_max = max; server_alive_interval = interval; return 0; } static void reset_timers(void) { int res; /* Handle the case where timers might be being processed at the moment. */ res = pr_timer_reset(PR_TIMER_IDLE, ANY_MODULE); while (res < 0) { if (errno == EINTR) { pr_signals_handle(); res = pr_timer_reset(PR_TIMER_IDLE, ANY_MODULE); } break; } res = pr_timer_reset(PR_TIMER_STALLED, ANY_MODULE); while (res < 0) { if (errno == EINTR) { pr_signals_handle(); res = pr_timer_reset(PR_TIMER_STALLED, ANY_MODULE); } break; } } static int check_packet_lengths(conn_t *conn, struct proxy_ssh_packet *pkt) { if (pkt->packet_len < 5) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "packet length too short (%lu), less than minimum packet length (5)", (unsigned long) pkt->packet_len); read_packet_discard(conn); return -1; } if (pkt->packet_len > PROXY_SSH_MAX_PACKET_LEN) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "packet length too long (%lu), exceeds maximum packet length (%lu)", (unsigned long) pkt->packet_len, (unsigned long) PROXY_SSH_MAX_PACKET_LEN); read_packet_discard(conn); return -1; } /* Per Section 6 of RFC4253, the minimum padding length is 4, the * maximum padding length is 255. */ if (pkt->padding_len < PROXY_SSH_MIN_PADDING_LEN) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "padding length too short (%u), less than minimum padding length (%u)", (unsigned int) pkt->padding_len, (unsigned int) PROXY_SSH_MIN_PADDING_LEN); read_packet_discard(conn); return -1; } if (pkt->padding_len > pkt->packet_len) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "padding length too long (%u), exceeds packet length (%lu)", (unsigned int) pkt->padding_len, (unsigned long) pkt->packet_len); read_packet_discard(conn); return -1; } return 0; } int proxy_ssh_packet_read(conn_t *conn, struct proxy_ssh_packet *pkt) { unsigned char buf[PROXY_SSH_MAX_PACKET_LEN]; size_t buflen, bufsz = PROXY_SSH_MAX_PACKET_LEN, offset = 0, auth_len = 0; int etm_mac = FALSE; pr_session_set_idle(); auth_len = proxy_ssh_cipher_get_read_auth_size(); if (auth_len > 0) { /* Authenticated encryption ciphers do not encrypt the packet length, * and instead use it as Additional Authenticated Data (AAD). */ pkt->aad_len = sizeof(uint32_t); } etm_mac = proxy_ssh_mac_is_read_etm(); if (etm_mac == TRUE) { /* ETM modes do not encrypt the packet length, and instead use it as * Additional Authenticated Data (AAD). */ pkt->aad_len = sizeof(uint32_t); } while (TRUE) { uint32_t encrypted_datasz, req_blocksz; unsigned char *buf2 = NULL; size_t buflen2 = 0, bufsz2 = 0; pr_signals_handle(); /* This is in a while loop in order to consume any debug/ignore * messages which the client may send. */ buflen = 0; memset(buf, 0, sizeof(buf)); if (read_packet_len(conn, pkt, buf, &offset, &buflen, bufsz, etm_mac) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "no data to be read from socket %d", conn->rfd); return -1; } pr_trace_msg(trace_channel, 20, "SSH2 packet len = %lu bytes", (unsigned long) pkt->packet_len); /* In order to mitigate the plaintext recovery attack described in * CPNI-957037: * * http://www.cpni.gov.uk/Docs/Vulnerability_Advisory_SSH.txt * * we do NOT check that the packet length is sane here; we have to * wait until the MAC check succeeds. */ /* Note: Checking for the RFC4253-recommended minimum packet length * of 16 bytes causes KEX to fail (the NEWKEYS packet is 12 bytes). * Thus that particular check is omitted. */ if (etm_mac == FALSE) { if (read_packet_padding_len(conn, pkt, buf, &offset, &buflen, bufsz) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "no data to be read from socket %d", conn->rfd); read_packet_discard(conn); return -1; } pr_trace_msg(trace_channel, 20, "SSH2 packet padding len = %u bytes", (unsigned int) pkt->padding_len); if (check_packet_lengths(conn, pkt) < 0) { return -1; } pkt->payload_len = (pkt->packet_len - pkt->padding_len - 1); } pr_trace_msg(trace_channel, 20, "SSH2 packet payload len = %lu bytes", (unsigned long) pkt->payload_len); /* Read both payload and padding, since we may need to have both before * decrypting the data. */ if (read_packet_payload(conn, pkt, buf, &offset, &buflen, bufsz, etm_mac) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to read payload from socket %d", conn->rfd); read_packet_discard(conn); return -1; } pkt->mac_len = proxy_ssh_mac_get_block_size(); pr_trace_msg(trace_channel, 20, "SSH2 packet MAC len = %lu bytes", (unsigned long) pkt->mac_len); if (etm_mac == TRUE) { bufsz2 = buflen2 = pkt->mac_len; buf2 = pcalloc(pkt->pool, bufsz2); /* The MAC routines assume the presence of the necessary data in * pkt->payload, so we temporarily put our encrypted packet data there. */ pkt->payload = buf; pkt->payload_len = buflen; pkt->seqno = packet_server_seqno; if (read_packet_mac(conn, pkt, buf2) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to read MAC from socket %d", conn->rfd); read_packet_discard(conn); return -1; } if (proxy_ssh_mac_read_data(pkt) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to verify MAC on packet from socket %d", conn->rfd); /* In order to further mitigate CPNI-957037, we will read in a * random amount of more data from the network before closing * the connection. */ read_packet_discard(conn); return -1; } /* Now we can decrypt the payload; `buf/buflen` are the encrypted * packet from read_packet_payload(). */ bufsz2 = buflen2 = PROXY_SSH_MAX_PACKET_LEN; buf2 = pcalloc(pkt->pool, bufsz2); if (proxy_ssh_cipher_read_data(pkt, buf, buflen, &buf2, (uint32_t *) &buflen2) < 0) { return -1; } offset = 0; if (read_packet_padding_len(conn, pkt, buf2, &offset, &buflen2, bufsz2) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "no data to be read from socket %d", conn->rfd); read_packet_discard(conn); return -1; } pr_trace_msg(trace_channel, 20, "SSH2 packet padding len = %u bytes", (unsigned int) pkt->padding_len); } else { memset(buf, 0, sizeof(buf)); if (read_packet_mac(conn, pkt, buf) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to read MAC from socket %d", conn->rfd); read_packet_discard(conn); return -1; } pkt->seqno = packet_server_seqno; if (proxy_ssh_mac_read_data(pkt) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to verify MAC on packet from socket %d", conn->rfd); /* In order to further mitigate CPNI-957037, we will read in a * random amount of more data from the network before closing * the connection. */ read_packet_discard(conn); return -1; } } /* Now that the MAC check has passed, we can do sanity checks based * on the fields we have read in, and trust that those fields are * correct. */ if (check_packet_lengths(conn, pkt) < 0) { return -1; } if (etm_mac == TRUE) { pkt->payload_len = (pkt->packet_len - pkt->padding_len - 1); if (pkt->payload_len > 0) { pkt->payload = pcalloc(pkt->pool, pkt->payload_len); memmove(pkt->payload, buf2 + offset, pkt->payload_len); } pkt->padding = pcalloc(pkt->pool, pkt->padding_len); memmove(pkt->padding, buf2 + offset + pkt->payload_len, pkt->padding_len); } /* From RFC4253, Section 6: * * random padding * Arbitrary-length padding, such that the total length of * (packet_length || padding_length || payload || random padding) * is a multiple of the cipher block size or 8, whichever is * larger. * * Thus packet_len + sizeof(uint32_t) (for the actual packet length field) * is that "(packet_length || padding_length || payload || padding)" * value. */ req_blocksz = MAX(8, proxy_ssh_cipher_get_read_block_size()); encrypted_datasz = pkt->packet_len + sizeof(uint32_t); /* If AAD bytes are present, they are not encrypted. */ if (pkt->aad_len > 0) { encrypted_datasz -= pkt->aad_len; } if (encrypted_datasz % req_blocksz != 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "packet length (%lu) not a multiple of the required block size (%lu)", (unsigned long) encrypted_datasz, (unsigned long) req_blocksz); read_packet_discard(conn); return -1; } /* XXX I'm not so sure about this check; we SHOULD have a maximum * payload check, but using the max packet length check for the payload * length seems awkward. */ if (pkt->payload_len > PROXY_SSH_MAX_PACKET_LEN) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "payload length too long (%lu), exceeds maximum payload length (%lu) " "(packet len %lu, padding len %u)", (unsigned long) pkt->payload_len, (unsigned long) PROXY_SSH_MAX_PACKET_LEN, (unsigned long) pkt->packet_len, (unsigned int) pkt->padding_len); read_packet_discard(conn); return -1; } /* Sanity checks passed; move on to the reading the packet payload. */ if (proxy_ssh_compress_read_data(pkt) < 0) { return -1; } packet_server_seqno++; reset_timers(); break; } return 0; } static int write_packet_padding(struct proxy_ssh_packet *pkt) { register unsigned int i; uint32_t packet_len = 0; size_t blocksz; blocksz = proxy_ssh_cipher_get_write_block_size(); /* RFC 4253, section 6, says that the random padding is calculated * as follows: * * random padding * Arbitrary-length padding, such that the total length of * (packet_length || padding_length || payload || random padding) * is a multiple of the cipher block size or 8, whichever is * larger. There MUST be at least four bytes of padding. The * padding SHOULD consist of random bytes. The maximum amount of * padding is 255 bytes. * * This means: * * packet len = sizeof(packet_len field) + sizeof(padding_len field) + * sizeof(payload field) + sizeof(padding field) */ packet_len = sizeof(uint32_t) + sizeof(char) + pkt->payload_len; if (pkt->aad_len > 0) { /* Packet length is not encrypted for encrypted authentication, or * Encrypt-Then-MAC modes. */ packet_len -= pkt->aad_len; } pkt->padding_len = (char) (blocksz - (packet_len % blocksz)); if (pkt->padding_len < 4) { /* As per RFC, there must be at least 4 bytes of padding. So if the * above calculated less, then we need to add another block's worth * of padding. */ pkt->padding_len += blocksz; } pkt->padding = palloc(pkt->pool, pkt->padding_len); /* Fill the padding with pseudo-random data. */ for (i = 0; i < pkt->padding_len; i++) { pkt->padding[i] = (unsigned char) pr_random_next(0, UCHAR_MAX); } return 0; } #define PROXY_SSH_PACKET_IOVSZ 12 static struct iovec packet_iov[PROXY_SSH_PACKET_IOVSZ]; static unsigned int packet_niov = 0; int proxy_ssh_packet_send(conn_t *conn, struct proxy_ssh_packet *pkt) { unsigned char buf[PROXY_SSH_MAX_PACKET_LEN * 2], msg_type; size_t buflen = 0, bufsz = PROXY_SSH_MAX_PACKET_LEN; uint32_t packet_len = 0, auth_len = 0; int res, write_len = 0, block_alarms = FALSE, etm_mac = FALSE; /* No interruptions, please. If, for example, we are interrupted here * by the SFTPRekey timer, that timer will cause this same function to * be called -- but the packet_iov/packet_niov values will be different. * Which in turn leads to malformed packets, and thus badness (Bug#4216). */ if (proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_AUTH) { block_alarms = TRUE; } if (block_alarms == TRUE) { pr_alarms_block(); } auth_len = proxy_ssh_cipher_get_write_auth_size(); if (auth_len > 0) { /* Authenticated encryption ciphers do not encrypt the packet length, * and instead use it as Additional Authenticated Data (AAD). */ pkt->aad_len = sizeof(uint32_t); pkt->aad = NULL; } etm_mac = proxy_ssh_mac_is_write_etm(); if (etm_mac == TRUE) { /* Encrypt-Then-Mac modes do not encrypt the packet length; treat it * as Additional Authenticated Data (AAD). */ pkt->aad_len = sizeof(uint32_t); pkt->aad = NULL; } /* Clear the iovec array before sending the data, if possible. */ if (packet_niov == 0) { memset(packet_iov, 0, sizeof(packet_iov)); } msg_type = proxy_ssh_packet_peek_msg_type(pkt); if (proxy_ssh_compress_write_data(pkt) < 0) { int xerrno = errno; if (block_alarms == TRUE) { pr_alarms_unblock(); } errno = xerrno; return -1; } if (write_packet_padding(pkt) < 0) { int xerrno = errno; if (block_alarms == TRUE) { pr_alarms_unblock(); } errno = xerrno; return -1; } /* Packet length: padding len + payload + padding */ pkt->packet_len = packet_len = sizeof(char) + pkt->payload_len + pkt->padding_len; pkt->seqno = packet_client_seqno; memset(buf, 0, sizeof(buf)); buflen = bufsz; if (etm_mac == TRUE) { if (proxy_ssh_cipher_write_data(pkt, buf, &buflen) < 0) { int xerrno = errno; if (block_alarms == TRUE) { pr_alarms_unblock(); } errno = xerrno; return -1; } /* Once we have the encrypted data, overwrite the plaintext packet payload * with it, so that the MAC is calculated from the encrypted data. */ pkt->payload = buf; pkt->payload_len = buflen; if (proxy_ssh_mac_write_data(pkt) < 0) { int xerrno = errno; if (block_alarms == TRUE) { pr_alarms_unblock(); } errno = xerrno; return -1; } } else { if (proxy_ssh_mac_write_data(pkt) < 0) { int xerrno = errno; if (block_alarms == TRUE) { pr_alarms_unblock(); } errno = xerrno; return -1; } if (proxy_ssh_cipher_write_data(pkt, buf, &buflen) < 0) { int xerrno = errno; if (block_alarms == TRUE) { pr_alarms_unblock(); } errno = xerrno; return -1; } } if (buflen > 0) { /* We have encrypted data, which means we don't need as many of the * iovec slots as for unencrypted data. */ if (sent_version_id == FALSE) { packet_iov[packet_niov].iov_base = (void *) version_id; packet_iov[packet_niov].iov_len = strlen(version_id); write_len += packet_iov[packet_niov].iov_len; packet_niov++; } if (pkt->aad_len > 0) { pr_trace_msg(trace_channel, 20, "sending %lu bytes of packet AAD data", (unsigned long) pkt->aad_len); packet_iov[packet_niov].iov_base = (void *) pkt->aad; packet_iov[packet_niov].iov_len = pkt->aad_len; write_len += packet_iov[packet_niov].iov_len; packet_niov++; } pr_trace_msg(trace_channel, 20, "sending %lu bytes of packet payload data", (unsigned long) buflen); packet_iov[packet_niov].iov_base = (void *) buf; packet_iov[packet_niov].iov_len = buflen; write_len += packet_iov[packet_niov].iov_len; packet_niov++; if (pkt->mac_len > 0) { pr_trace_msg(trace_channel, 20, "sending %lu bytes of packet MAC data", (unsigned long) pkt->mac_len); packet_iov[packet_niov].iov_base = (void *) pkt->mac; packet_iov[packet_niov].iov_len = pkt->mac_len; write_len += packet_iov[packet_niov].iov_len; packet_niov++; } } else { /* Don't forget to convert the packet len to network-byte order, since * this length is sent over the wire. */ packet_len = htonl(packet_len); if (sent_version_id == FALSE) { packet_iov[packet_niov].iov_base = (void *) version_id; packet_iov[packet_niov].iov_len = strlen(version_id); write_len += packet_iov[packet_niov].iov_len; packet_niov++; } packet_iov[packet_niov].iov_base = (void *) &packet_len; packet_iov[packet_niov].iov_len = sizeof(uint32_t); write_len += packet_iov[packet_niov].iov_len; packet_niov++; packet_iov[packet_niov].iov_base = (void *) &(pkt->padding_len); packet_iov[packet_niov].iov_len = sizeof(char); write_len += packet_iov[packet_niov].iov_len; packet_niov++; packet_iov[packet_niov].iov_base = (void *) pkt->payload; packet_iov[packet_niov].iov_len = pkt->payload_len; write_len += packet_iov[packet_niov].iov_len; packet_niov++; packet_iov[packet_niov].iov_base = (void *) pkt->padding; packet_iov[packet_niov].iov_len = pkt->padding_len; write_len += packet_iov[packet_niov].iov_len; packet_niov++; if (pkt->mac_len > 0) { packet_iov[packet_niov].iov_base = (void *) pkt->mac; packet_iov[packet_niov].iov_len = pkt->mac_len; write_len += packet_iov[packet_niov].iov_len; packet_niov++; } } if (proxy_ssh_packet_conn_poll(conn, PROXY_SSH_PACKET_IO_WRITE) < 0) { int xerrno = errno; /* Socket not writable? Clear the array, and try again. */ memset(packet_iov, 0, sizeof(packet_iov)); packet_niov = 0; if (block_alarms == TRUE) { pr_alarms_unblock(); } errno = xerrno; return -1; } /* The socket we accept is blocking, thus there's no need to handle * EAGAIN/EWOULDBLOCK errors. */ res = writev(conn->wfd, packet_iov, packet_niov); while (res < 0) { int xerrno = errno; if (xerrno == EINTR) { pr_signals_handle(); res = writev(conn->wfd, packet_iov, packet_niov); continue; } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error writing packet (fd %d): %s", conn->wfd, strerror(xerrno)); if (xerrno == ECONNRESET || xerrno == ECONNABORTED || xerrno == EPIPE) { if (block_alarms == TRUE) { pr_alarms_unblock(); } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "disconnecting server (%s)", strerror(xerrno)); pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION, strerror(xerrno)); } /* Always clear the iovec array after sending the data. */ memset(packet_iov, 0, sizeof(packet_iov)); packet_niov = 0; if (block_alarms == TRUE) { pr_alarms_unblock(); } errno = xerrno; return -1; } session.total_raw_out += res; /* Always clear the iovec array after sending the data. */ memset(packet_iov, 0, sizeof(packet_iov)); packet_niov = 0; if (sent_version_id == FALSE) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "sent client version '%s'", client_version); sent_version_id = TRUE; } packet_client_seqno++; pr_trace_msg(trace_channel, 3, "sent %s (%d) packet (%d bytes)", proxy_ssh_packet_get_msg_type_desc(msg_type), msg_type, res); if (block_alarms == TRUE) { /* Now that we've written out the packet, we can be interrupted again. */ pr_alarms_unblock(); } reset_timers(); return 0; } int proxy_ssh_packet_write(conn_t *conn, struct proxy_ssh_packet *pkt) { /* Make sure that any frontend AAD ciphers/data are not leaked through to * the backend IO routines. */ if (pkt->aad_len > 0) { pkt->aad_len = 0; pkt->aad = NULL; } return proxy_ssh_packet_send(conn, pkt); } int proxy_ssh_packet_write_frontend(conn_t *conn, struct proxy_ssh_packet *pkt) { if (frontend_packet_write == NULL) { errno = ENOSYS; return -1; } /* Make sure that any backend AAD ciphers/data are not leaked through to * the frontend IO routines. */ if (pkt->aad_len > 0) { pkt->aad_len = 0; pkt->aad = NULL; } return (frontend_packet_write)(conn->wfd, pkt); } void proxy_ssh_packet_handle_debug(struct proxy_ssh_packet *pkt) { register unsigned int i; int always_display; char *text, *lang; uint32_t len; len = proxy_ssh_msg_read_bool(pkt->pool, &pkt->payload, &pkt->payload_len, &always_display); len = proxy_ssh_msg_read_string(pkt->pool, &pkt->payload, &pkt->payload_len, &text); /* Ignore the language tag. */ (void) proxy_ssh_msg_read_string(pkt->pool, &pkt->payload, &pkt->payload_len, &lang); /* Sanity-check the message for control (and other non-printable) * characters. */ for (i = 0; i < strlen(text); i++) { if (PR_ISCNTRL(text[i]) || !PR_ISPRINT(text[i])) { text[i] = '?'; } } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "server sent SSH_MSG_DEBUG message '%s'", text); if (always_display == TRUE) { pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": server sent SSH_MSG_DEBUG message '%s'", text); } destroy_pool(pkt->pool); } void proxy_ssh_packet_handle_disconnect(struct proxy_ssh_packet *pkt) { register unsigned int i; char *explain = NULL, *lang = NULL; const char *reason_text = NULL; uint32_t reason_code, len; len = proxy_ssh_msg_read_int(pkt->pool, &pkt->payload, &pkt->payload_len, &reason_code); reason_text = proxy_ssh_disconnect_get_text(reason_code); if (reason_text == NULL) { pr_trace_msg(trace_channel, 9, "server sent unknown disconnect reason code %lu", (unsigned long) reason_code); reason_text = "Unknown reason code"; } len = proxy_ssh_msg_read_string(pkt->pool, &pkt->payload, &pkt->payload_len, &explain); /* Not all clients send a language tag. */ if (pkt->payload_len > 0) { len = proxy_ssh_msg_read_string(pkt->pool, &pkt->payload, &pkt->payload_len, &lang); } /* Sanity-check the message for control characters. */ for (i = 0; i < strlen(explain); i++) { if (PR_ISCNTRL(explain[i])) { explain[i] = '?'; } } /* XXX Use the language tag somehow, if provided. */ if (lang != NULL) { pr_trace_msg(trace_channel, 19, "server sent DISCONNECT language tag '%s'", lang); } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "server at %s sent SSH_DISCONNECT message: %s (%s)", pr_netaddr_get_ipstr(session.c->remote_addr), explain, reason_text); pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_CLIENT_QUIT, explain); } void proxy_ssh_packet_handle_ext_info(struct proxy_ssh_packet *pkt) { register unsigned int i; unsigned char *buf; uint32_t buflen, ext_count = 0; buf = pkt->payload; buflen = pkt->payload_len; proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &ext_count); pr_trace_msg(trace_channel, 9, "server sent EXT_INFO with %lu %s", (unsigned long) ext_count, ext_count != 1 ? "extensions" : "extension"); for (i = 0; i < ext_count; i++) { unsigned char *ext_data = NULL; char *ext_name = NULL; uint32_t ext_datalen = 0; proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &ext_name); proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &ext_datalen); proxy_ssh_msg_read_data(pkt->pool, &buf, &buflen, ext_datalen, &ext_data); pr_trace_msg(trace_channel, 9, "server extension: %s (value %lu bytes)", ext_name, (unsigned long) ext_datalen); } destroy_pool(pkt->pool); } void proxy_ssh_packet_handle_ignore(struct proxy_ssh_packet *pkt) { char *text; size_t text_len; uint32_t len; len = proxy_ssh_msg_read_string(pkt->pool, &pkt->payload, &pkt->payload_len, &text); text_len = strlen(text); (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "server sent SSH_MSG_IGNORE message (%u bytes)", (unsigned int) text_len); destroy_pool(pkt->pool); } void proxy_ssh_packet_handle_unimplemented(struct proxy_ssh_packet *pkt) { uint32_t seqno, len; len = proxy_ssh_msg_read_int(pkt->pool, &pkt->payload, &pkt->payload_len, &seqno); pr_trace_msg(trace_channel, 7, "received SSH_MSG_UNIMPLEMENTED for " "packet #%lu", (unsigned long) seqno); destroy_pool(pkt->pool); } int proxy_ssh_packet_proxied(const struct proxy_session *proxy_sess, struct proxy_ssh_packet *pkt, int from_frontend) { int res, xerrno = 0; char msg_type; msg_type = proxy_ssh_packet_peek_msg_type(pkt); if (from_frontend == TRUE) { /* Write the packet to the backend. */ pr_trace_msg(trace_channel, 17, "proxying %s (%d) packet from frontend to backend", proxy_ssh_packet_get_msg_type_desc(msg_type), msg_type); res = proxy_ssh_packet_write(proxy_sess->backend_ctrl_conn, pkt); xerrno = errno; if (res < 0) { pr_trace_msg(trace_channel, 2, "error proxying packet from frontend to backend: %s", strerror(xerrno)); } } else { pr_trace_msg(trace_channel, 17, "proxying %s (%d) packet from backend to frontend", proxy_ssh_packet_get_msg_type_desc(msg_type), msg_type); res = proxy_ssh_packet_write_frontend(proxy_sess->frontend_ctrl_conn, pkt); xerrno = errno; if (res < 0) { if (xerrno != ENOSYS) { pr_trace_msg(trace_channel, 2, "error proxying packet from backend to frontend: %s", strerror(xerrno)); } else { /* Ignore the case where we are told not to write packets to the * frontend client. */ res = 0; xerrno = errno = 0; } } } errno = xerrno; return res; } int proxy_ssh_packet_handle(void *data) { const struct proxy_session *proxy_sess; struct proxy_ssh_packet *pkt; unsigned char msg_type; int from_frontend = FALSE; proxy_sess = pr_table_get(session.notes, "mod_proxy.proxy-session", NULL); if (proxy_sess == NULL) { /* Unlikely to occur. */ errno = EPERM; return -1; } pkt = data; /* We only peek at the message type here, so that we can properly proxy * the entire packet if needed. */ msg_type = proxy_ssh_packet_peek_msg_type(pkt); pr_trace_msg(trace_channel, 20, "received %s (%d) packet (from mod_%s.c)", proxy_ssh_packet_get_msg_type_desc(msg_type), msg_type, pkt->m->name); /* Note: Some of the SSH messages will be handled regardless of the * proxy_sess_state flags; this is intentional, and is the way that * the protocol is supposed to work. */ if (pkt->m == &proxy_module) { from_frontend = FALSE; } else { from_frontend = TRUE; } /* Create and dispatch cmd_recs for frontend/backend SSH packets, in order to * support ExtendedLog logging. */ proxy_ssh_packet_log_cmd(pkt, from_frontend); switch (msg_type) { case PROXY_SSH_MSG_DEBUG: if (proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_KEX) { proxy_ssh_packet_proxied(proxy_sess, pkt, from_frontend); } else { (void) proxy_ssh_packet_get_msg_type(pkt); proxy_ssh_packet_handle_debug(pkt); } break; case PROXY_SSH_MSG_DISCONNECT: if (proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_KEX) { proxy_ssh_packet_proxied(proxy_sess, pkt, from_frontend); } else { (void) proxy_ssh_packet_get_msg_type(pkt); proxy_ssh_packet_handle_disconnect(pkt); } break; case PROXY_SSH_MSG_GLOBAL_REQUEST: handle_global_request_msg(pkt, proxy_sess, from_frontend); break; case PROXY_SSH_MSG_REQUEST_SUCCESS: case PROXY_SSH_MSG_REQUEST_FAILURE: if (proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_KEX) { proxy_ssh_packet_proxied(proxy_sess, pkt, from_frontend); } else { (void) proxy_ssh_packet_get_msg_type(pkt); handle_server_alive_msg(pkt, msg_type); } break; case PROXY_SSH_MSG_IGNORE: if (proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_KEX) { proxy_ssh_packet_proxied(proxy_sess, pkt, from_frontend); } else { (void) proxy_ssh_packet_get_msg_type(pkt); proxy_ssh_packet_handle_ignore(pkt); } break; case PROXY_SSH_MSG_UNIMPLEMENTED: if (proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_KEX) { proxy_ssh_packet_proxied(proxy_sess, pkt, from_frontend); } else { (void) proxy_ssh_packet_get_msg_type(pkt); proxy_ssh_packet_handle_unimplemented(pkt); } break; case PROXY_SSH_MSG_KEXINIT: { uint64_t start_ms = 0; if (from_frontend == TRUE) { /* We should never see a frontend KEXINIT packet, except when the * frontend client has requested a rekey; we do NOT want to interact * with the backend anymore for this event. * * In addition, we need a way to get mod_sftp to handle this packet, * and the rest of the KEX. Fun. */ return handle_frontend_rekey(pkt, proxy_sess); } (void) proxy_ssh_packet_get_msg_type(pkt); if (pr_trace_get_level(timing_channel) > 0) { pr_gettimeofday_millis(&start_ms); } if (proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_KEX) { /* The server might be initiating a rekey; watch for this. We * should not be receiving frontend KEXINIT packets here. */ if (from_frontend == FALSE) { if (proxy_sess_state & PROXY_SESS_STATE_SSH_REKEYING) { pr_trace_msg(trace_channel, 17, "rekeying already in effect, ignoring rekey request"); break; } /* Reinitialize the KEX API for another rekeying. */ proxy_ssh_kex_init(session.pool, NULL, NULL); } } else { if (pr_trace_get_level(timing_channel)) { unsigned long elapsed_ms; uint64_t finish_ms; pr_gettimeofday_millis(&finish_ms); elapsed_ms = (unsigned long) (finish_ms - session.connect_time_ms); pr_trace_msg(timing_channel, 4, "Time before first SSH key exchange: %lu ms", elapsed_ms); } } proxy_sess_state |= PROXY_SESS_STATE_SSH_REKEYING; /* Clear any current "have KEX" state. */ proxy_sess_state &= ~PROXY_SESS_STATE_SSH_HAVE_KEX; if (proxy_ssh_kex_handle(pkt, proxy_sess) < 0) { PROXY_SSH_DISCONNECT_CONN(proxy_sess->backend_ctrl_conn, PROXY_SSH_DISCONNECT_BY_APPLICATION, NULL); } proxy_sess_state |= PROXY_SESS_STATE_SSH_HAVE_KEX; if (pr_trace_get_level(timing_channel)) { unsigned long elapsed_ms; uint64_t finish_ms; pr_gettimeofday_millis(&finish_ms); elapsed_ms = (unsigned long) (finish_ms - start_ms); pr_trace_msg(timing_channel, 4, "SSH key exchange duration: %lu ms", elapsed_ms); } if (proxy_sess_state & PROXY_SESS_STATE_SSH_REKEYING) { proxy_sess_state &= ~PROXY_SESS_STATE_SSH_REKEYING; } break; } case PROXY_SSH_MSG_EXT_INFO: /* We expect any possible EXT_INFO message after NEWKEYS, and before * anything else. */ if ((proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_KEX) && !(proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_SERVICE) && !(proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_EXT_INFO)) { (void) proxy_ssh_packet_get_msg_type(pkt); proxy_ssh_packet_handle_ext_info(pkt); proxy_sess_state |= PROXY_SESS_STATE_SSH_HAVE_EXT_INFO; break; } else { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to handle %s (%d) message: wrong message order", proxy_ssh_packet_get_msg_type_desc(msg_type), msg_type); PROXY_SSH_DISCONNECT_CONN(proxy_sess->backend_ctrl_conn, PROXY_SSH_DISCONNECT_BY_APPLICATION, "Unsupported protocol sequence"); } break; case PROXY_SSH_MSG_SERVICE_REQUEST: if (proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_KEX) { if (proxy_ssh_service_handle(pkt, proxy_sess) == 0) { proxy_sess_state |= PROXY_SESS_STATE_SSH_HAVE_SERVICE; } } else { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to handle %s (%d) message: Key exchange required", proxy_ssh_packet_get_msg_type_desc(msg_type), msg_type); PROXY_SSH_DISCONNECT_CONN(proxy_sess->backend_ctrl_conn, PROXY_SSH_DISCONNECT_BY_APPLICATION, "Unsupported protocol sequence"); } break; case PROXY_SSH_MSG_USER_AUTH_INFO_RESP: case PROXY_SSH_MSG_USER_AUTH_REQUEST: if (proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_SERVICE) { /* If the client has already authenticated this connection, then * silently ignore this additional auth request, per recommendation * in RFC4252. */ if (proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_AUTH) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "ignoring %s (%d) message: Connection already authenticated", proxy_ssh_packet_get_msg_type_desc(msg_type), msg_type); } else { int ok; ok = proxy_ssh_auth_handle(pkt, proxy_sess); if (ok == 1) { proxy_sess_state |= PROXY_SESS_STATE_SSH_HAVE_AUTH; } else if (ok < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error handling SSH authentication: %s", strerror(errno)); PROXY_SSH_DISCONNECT_CONN(proxy_sess->frontend_ctrl_conn, PROXY_SSH_DISCONNECT_BY_APPLICATION, NULL); } } } else { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to handle %s (%d) message: Service request required", proxy_ssh_packet_get_msg_type_desc(msg_type), msg_type); PROXY_SSH_DISCONNECT_CONN(proxy_sess->backend_ctrl_conn, PROXY_SSH_DISCONNECT_BY_APPLICATION, "Unsupported protocol sequence"); } break; case PROXY_SSH_MSG_CHANNEL_OPEN: case PROXY_SSH_MSG_CHANNEL_OPEN_CONFIRMATION: case PROXY_SSH_MSG_CHANNEL_OPEN_FAILURE: case PROXY_SSH_MSG_CHANNEL_REQUEST: case PROXY_SSH_MSG_CHANNEL_DATA: case PROXY_SSH_MSG_CHANNEL_EXTENDED_DATA: case PROXY_SSH_MSG_CHANNEL_EOF: case PROXY_SSH_MSG_CHANNEL_FAILURE: case PROXY_SSH_MSG_CHANNEL_SUCCESS: case PROXY_SSH_MSG_CHANNEL_WINDOW_ADJUST: if (proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_AUTH) { (void) pr_timer_reset(PR_TIMER_NOXFER, ANY_MODULE); proxy_ssh_packet_proxied(proxy_sess, pkt, from_frontend); } else { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to handle %s (%d) message: User authentication required", proxy_ssh_packet_get_msg_type_desc(msg_type), msg_type); PROXY_SSH_DISCONNECT_CONN(proxy_sess->backend_ctrl_conn, PROXY_SSH_DISCONNECT_BY_APPLICATION, "Unsupported protocol sequence"); } break; case PROXY_SSH_MSG_CHANNEL_CLOSE: if (proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_AUTH) { proxy_ssh_packet_proxied(proxy_sess, pkt, from_frontend); } else { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to handle %s (%d) message: User authentication required", proxy_ssh_packet_get_msg_type_desc(msg_type), msg_type); PROXY_SSH_DISCONNECT_CONN(proxy_sess->backend_ctrl_conn, PROXY_SSH_DISCONNECT_BY_APPLICATION, "Unsupported protocol sequence"); } break; default: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unhandled %s (%d) message, disconnecting", proxy_ssh_packet_get_msg_type_desc(msg_type), msg_type); PROXY_SSH_DISCONNECT_CONN(proxy_sess->backend_ctrl_conn, PROXY_SSH_DISCONNECT_BY_APPLICATION, "Unsupported protocol sequence"); } return 0; } int proxy_ssh_packet_process(pool *p, const struct proxy_session *proxy_sess) { struct proxy_ssh_packet *pkt; int res; pkt = proxy_ssh_packet_create(p); res = proxy_ssh_packet_read(proxy_sess->backend_ctrl_conn, pkt); if (res < 0) { /* An ETIMEDOUT error here usually means that the read poll timed out; * the backend host did not have any data for us, which is OK. Right? */ if (errno != ETIMEDOUT) { PROXY_SSH_DISCONNECT_CONN(proxy_sess->backend_ctrl_conn, PROXY_SSH_DISCONNECT_BY_APPLICATION, NULL); } } pr_response_clear(&resp_list); pr_response_clear(&resp_err_list); pr_response_set_pool(pkt->pool); proxy_ssh_packet_handle(pkt); pr_response_set_pool(NULL); return 0; } int proxy_ssh_packet_set_frontend_packet_handle(pool *p, int (*packet_handle)(void *pkt)) { const char *hook_symbol; cmdtable *sftp_cmdtab; cmd_rec *cmd; modret_t *result; hook_symbol = "sftp_set_packet_handler"; sftp_cmdtab = pr_stash_get_symbol2(PR_SYM_HOOK, hook_symbol, NULL, NULL, NULL); if (sftp_cmdtab == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to find SFTP hook symbol '%s'", hook_symbol); errno = ENOENT; return -1; } cmd = pr_cmd_alloc(p, 1, NULL); cmd->argv[0] = (void *) packet_handle; result = pr_module_call(sftp_cmdtab->m, sftp_cmdtab->handler, cmd); if (result == NULL || MODRET_ISERROR(result)) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error setting Proxy SSH packet handler"); errno = EPERM; return -1; } return 0; } void proxy_ssh_packet_set_frontend_packet_write(int (*packet_write)(int, void *)) { frontend_packet_write = packet_write; } int proxy_ssh_packet_send_version(conn_t *conn) { if (sent_version_id == FALSE) { int res; size_t version_len; version_len = strlen(version_id); res = write(conn->wfd, version_id, version_len); while (res < 0) { if (errno == EINTR) { pr_signals_handle(); res = write(conn->wfd, version_id, version_len); continue; } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error sending version to server wfd %d: %s", conn->wfd, strerror(errno)); return res; } sent_version_id = TRUE; session.total_raw_out += res; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "sent client version '%s'", client_version); } return 0; } uint32_t proxy_ssh_packet_get_server_seqno(void) { return packet_server_seqno; } void proxy_ssh_packet_reset_client_seqno(void) { packet_client_seqno = 0; } void proxy_ssh_packet_reset_server_seqno(void) { packet_server_seqno = 0; } int proxy_ssh_packet_set_version(const char *version) { if (client_version == NULL) { errno = EINVAL; return -1; } client_version = version; version_id = pstrcat(proxy_pool, version, "\r\n", NULL); return 0; } #endif /* PR_USE_OPENSSL */ proftpd-mod_proxy-0.9.5/lib/proxy/ssh/redis.c000066400000000000000000000170331475737016700212440ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH Redis implementation * Copyright (c) 2022 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "redis.h" #include "proxy/ssh.h" #include "proxy/ssh/crypto.h" #include "proxy/ssh/redis.h" #if defined(PR_USE_OPENSSL) #include extern xaset_t *server_list; static const char *trace_channel = "proxy.ssh.redis"; static void *redis_prefix = NULL; static size_t redis_prefixsz = 0; static unsigned long redis_opts = 0UL; static const char *redis_algo_field = "algo"; static const char *redis_blob_field = "blob"; static char *make_key(pool *p, const char *backend_uri) { char *key; size_t keysz; keysz = strlen(backend_uri) + 64; key = pcalloc(p, keysz + 1); snprintf(key, keysz, "proxy_ssh_hostkeys:%s", backend_uri); return key; } static int ssh_redis_update_hostkey(pool *p, void *dsh, unsigned int vhost_id, const char *backend_uri, const char *algo, const unsigned char *hostkey_data, uint32_t hostkey_datalen) { int res, xerrno = 0; pool *tmp_pool; pr_redis_t *redis; char *key, *data = NULL; long datalen = 0; size_t field_len; redis = dsh; tmp_pool = make_sub_pool(p); data = palloc(tmp_pool, (2 * hostkey_datalen) + 1); datalen = EVP_EncodeBlock((unsigned char *) data, hostkey_data, (int) hostkey_datalen); if (datalen == 0) { pr_trace_msg(trace_channel, 3, "error base640-encoding hostkey data: %s", proxy_ssh_crypto_get_errors()); destroy_pool(tmp_pool); return 0; } key = make_key(tmp_pool, backend_uri); field_len = strlen(algo); res = pr_redis_hash_set(redis, &proxy_module, key, redis_algo_field, (void *) algo, field_len); xerrno = errno; if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error setting value for field '%s' in Redis hash '%s': %s", redis_algo_field, key, strerror(xerrno)); destroy_pool(tmp_pool); errno = xerrno; return -1; } field_len = datalen; res = pr_redis_hash_set(redis, &proxy_module, key, redis_blob_field, (void *) data, field_len); xerrno = errno; if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error setting value for field '%s' in Redis hash '%s': %s", redis_blob_field, key, strerror(xerrno)); destroy_pool(tmp_pool); errno = xerrno; return -1; } destroy_pool(tmp_pool); return 0; } static const unsigned char *ssh_redis_get_hostkey(pool *p, void *dsh, unsigned int vhost_id, const char *backend_uri, const char **algo, uint32_t *hostkey_datalen) { int have_padding = FALSE, res, xerrno; pool *tmp_pool; pr_redis_t *redis; pr_table_t *hostkey_tab; char *key; void *data = NULL; const unsigned char *hostkey_data = NULL; size_t blocklen = 0, datalen = 0, rem; redis = dsh; tmp_pool = make_sub_pool(p); key = make_key(tmp_pool, backend_uri); res = pr_redis_hash_getall(tmp_pool, redis, &proxy_module, key, &hostkey_tab); xerrno = errno; if (res < 0) { if (xerrno != ENOENT) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error getting hash from Redis '%s': %s", key, strerror(xerrno)); } destroy_pool(tmp_pool); errno = xerrno; return NULL; } if (hostkey_tab == NULL) { destroy_pool(tmp_pool); errno = ENOENT; return NULL; } data = (void *) pr_table_kget(hostkey_tab, redis_algo_field, strlen(redis_algo_field), &datalen); if (data != NULL) { *algo = pstrndup(p, data, datalen); } data = (void *) pr_table_kget(hostkey_tab, redis_blob_field, strlen(redis_blob_field), &datalen); if (data == NULL) { pr_trace_msg(trace_channel, 3, "%s", "missing base64-decoding hostkey data from Redis, skipping"); destroy_pool(tmp_pool); errno = ENOENT; return NULL; } /* Due to Base64's padding, we need to detect if the last block was padded * with zeros; we do this by looking for '=' characters at the end of the * text being decoded. If we see these characters, then we will "trim" off * any trailing zero values in the decoded data, on the ASSUMPTION that they * are the auto-added padding bytes. */ if (((char *) data)[datalen-1] == '=') { have_padding = TRUE; } blocklen = datalen; /* Ensure that the output buffer is divisible by 4, per OpenSSL * requirements. */ rem = blocklen % 4; if (rem != 0) { blocklen += rem; } hostkey_data = pcalloc(p, blocklen); res = EVP_DecodeBlock((unsigned char *) hostkey_data, (unsigned char *) data, (int) datalen); if (res <= 0) { pr_trace_msg(trace_channel, 3, "error base64-decoding hostkey data: %s", proxy_ssh_crypto_get_errors()); destroy_pool(tmp_pool); errno = EINVAL; return NULL; } if (have_padding == TRUE) { /* Assume that only one or two zero bytes of padding were added. */ if (hostkey_data[res-1] == '\0') { res -= 1; if (hostkey_data[res-1] == '\0') { res -= 1; } } } *hostkey_datalen = res; pr_trace_msg(trace_channel, 19, "retrieved hostkey (algo '%s', %lu bytes) for vhost ID %u, URI '%s'", *algo, (unsigned long) *hostkey_datalen, vhost_id, backend_uri); return hostkey_data; } /* Initialization routines */ static int ssh_redis_init(pool *p, const char *tables_path, int flags) { /* We currently don't need to do anything, at init time, to any existing * SSH Redis keys. */ return 0; } static int ssh_redis_close(pool *p, void *redis) { if (redis != NULL) { if (pr_redis_conn_close(redis) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error closing Redis connection: %s", strerror(errno)); } } return 0; } static void *ssh_redis_open(pool *p, const char *tables_dir, unsigned long opts) { int xerrno = 0; pr_redis_t *redis; redis = pr_redis_conn_new(p, &proxy_module, 0); xerrno = errno; if (redis == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error opening Redis connection: %s", strerror(xerrno)); errno = xerrno; return NULL; } (void) pr_redis_conn_set_namespace(redis, &proxy_module, redis_prefix, redis_prefixsz); redis_opts = opts; return redis; } int proxy_ssh_redis_as_datastore(struct proxy_ssh_datastore *ds, void *ds_data, size_t ds_datasz) { if (ds == NULL) { errno = EINVAL; return -1; } redis_prefix = ds_data; redis_prefixsz = ds_datasz; ds->hostkey_add = ssh_redis_update_hostkey; ds->hostkey_get = ssh_redis_get_hostkey; ds->hostkey_update = ssh_redis_update_hostkey; ds->init = ssh_redis_init; ds->open = ssh_redis_open; ds->close = ssh_redis_close; return 0; } #endif /* PR_USE_OPENSSL */ proftpd-mod_proxy-0.9.5/lib/proxy/ssh/service.c000066400000000000000000000072201475737016700215730ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH service * Copyright (c) 2021-2022 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "proxy/ssh/ssh2.h" #include "proxy/ssh/packet.h" #include "proxy/ssh/service.h" #if defined(PR_USE_OPENSSL) static const char *trace_channel = "proxy.ssh.service"; int proxy_ssh_service_handle(struct proxy_ssh_packet *pkt, const struct proxy_session *proxy_sess) { int poll_timeout_secs, res, xerrno = 0; unsigned int poll_attempts; unsigned long poll_timeout_ms; char msg_type; res = proxy_ssh_packet_write(proxy_sess->backend_ctrl_conn, pkt); if (res < 0) { destroy_pool(pkt->pool); return -1; } destroy_pool(pkt->pool); proxy_ssh_packet_get_poll_attempts(&poll_attempts); proxy_ssh_packet_get_poll_timeout(&poll_timeout_secs, &poll_timeout_ms); proxy_ssh_packet_set_poll_attempts(3); proxy_ssh_packet_set_poll_timeout(0, 250); while (TRUE) { pr_signals_handle(); pkt = proxy_ssh_packet_create(proxy_pool); res = proxy_ssh_packet_read(proxy_sess->backend_ctrl_conn, pkt); if (res < 0) { xerrno = errno; destroy_pool(pkt->pool); proxy_ssh_packet_set_poll_attempts(poll_attempts); proxy_ssh_packet_set_poll_timeout(poll_timeout_secs, poll_timeout_ms); errno = xerrno; return -1; } msg_type = proxy_ssh_packet_peek_msg_type(pkt); pr_trace_msg(trace_channel, 3, "received %s (%d) packet (from mod_%s.c)", proxy_ssh_packet_get_msg_type_desc(msg_type), msg_type, pkt->m->name); /* Be sure to handle the messages that can come at any time as well. */ switch (msg_type) { case PROXY_SSH_MSG_SERVICE_ACCEPT: /* Expected */ break; case PROXY_SSH_MSG_DEBUG: case PROXY_SSH_MSG_DISCONNECT: case PROXY_SSH_MSG_EXT_INFO: case PROXY_SSH_MSG_IGNORE: case PROXY_SSH_MSG_UNIMPLEMENTED: proxy_ssh_packet_handle(pkt); continue; default: proxy_ssh_packet_set_poll_attempts(poll_attempts); proxy_ssh_packet_set_poll_timeout(poll_timeout_secs, poll_timeout_ms); destroy_pool(pkt->pool); /* Invalid protocol sequence */ (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "received unexpected %s packet during SSH service setup, failing", proxy_ssh_packet_get_msg_type_desc(msg_type)); errno = ENOSYS; return -1; } break; } proxy_ssh_packet_set_poll_attempts(poll_attempts); proxy_ssh_packet_set_poll_timeout(poll_timeout_secs, poll_timeout_ms); proxy_ssh_packet_log_cmd(pkt, FALSE); res = proxy_ssh_packet_proxied(proxy_sess, pkt, FALSE); xerrno = errno; destroy_pool(pkt->pool); errno = xerrno; return res; } #endif /* PR_USE_OPENSSL */ proftpd-mod_proxy-0.9.5/lib/proxy/ssh/session.c000066400000000000000000000041011475737016700216110ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH session * Copyright (c) 2021 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "proxy/ssh/session.h" #if defined(PR_USE_OPENSSL) #include static unsigned char *session_id = NULL; static uint32_t session_idlen = 0; uint32_t proxy_ssh_session_get_id(const unsigned char **buf) { if (session_id != NULL) { *buf = session_id; return session_idlen; } return 0; } int proxy_ssh_session_set_id(pool *p, const unsigned char *hash, uint32_t hashlen) { /* The session ID is only set once, regardless of how many times * (re)keying occurs during the course of a session. */ if (session_id == NULL) { session_id = palloc(p, hashlen); memcpy(session_id, hash, hashlen); session_idlen = hashlen; #if OPENSSL_VERSION_NUMBER >= 0x000905000L /* Since the session ID contains unknown information from the client, * it can be used as a source of additional entropy. The amount * of entropy is a rough guess. */ RAND_add(hash, hashlen, hashlen * 0.5); #endif return 0; } errno = EEXIST; return -1; } #endif /* PR_USE_OPENSSL */ proftpd-mod_proxy-0.9.5/lib/proxy/ssh/umac.c000066400000000000000000001316121475737016700210630ustar00rootroot00000000000000/* ----------------------------------------------------------------------- * * umac.c -- C Implementation UMAC Message Authentication * * Version 0.93b of rfc4418.txt -- 2006 July 18 * * For a full description of UMAC message authentication see the UMAC * world-wide-web page at http://www.cs.ucdavis.edu/~rogaway/umac * Please report bugs and suggestions to the UMAC webpage. * * Copyright (c) 1999-2006 Ted Krovetz * * Permission to use, copy, modify, and distribute this software and * its documentation for any purpose and with or without fee, is hereby * granted provided that the above copyright notice appears in all copies * and in supporting documentation, and that the name of the copyright * holder not be used in advertising or publicity pertaining to * distribution of the software without specific, written prior permission. * * Comments should be directed to Ted Krovetz (tdk@acm.org) * * ---------------------------------------------------------------------- */ /* ////////////////////// IMPORTANT NOTES ///////////////////////////////// * * 1) This version does not work properly on messages larger than 16MB * * 2) If you set the switch to use SSE2, then all data must be 16-byte * aligned * * 3) When calling the function umac(), it is assumed that msg is in * a writable buffer of length divisible by 32 bytes. The message itself * does not have to fill the entire buffer, but bytes beyond msg may be * zeroed. * * 4) Three free AES implementations are supported by this implementation of * UMAC. Paulo Barreto's version is in the public domain and can be found * at http://www.esat.kuleuven.ac.be/~rijmen/rijndael/ (search for * "Barreto"). The only two files needed are rijndael-alg-fst.c and * rijndael-alg-fst.h. Brian Gladman's version is distributed with the GNU * Public Licence at http://fp.gladman.plus.com/AES/index.htm. It * includes a fast IA-32 assembly version. The OpenSSL crypo library is * the third. * * 5) With FORCE_C_ONLY flags set to 0, incorrect results are sometimes * produced under gcc with optimizations set -O3 or higher. Dunno why. * /////////////////////////////////////////////////////////////////////// */ /* ---------------------------------------------------------------------- */ /* --- User Switches ---------------------------------------------------- */ /* ---------------------------------------------------------------------- */ #ifndef UMAC_OUTPUT_LEN # define UMAC_OUTPUT_LEN 8 /* Alowable: 4, 8, 12, 16 */ #endif #if UMAC_OUTPUT_LEN != 4 && UMAC_OUTPUT_LEN != 8 && \ UMAC_OUTPUT_LEN != 12 && UMAC_OUTPUT_LEN != 16 # error UMAC_OUTPUT_LEN must be defined to 4, 8, 12 or 16 #endif /* #define FORCE_C_ONLY 1 ANSI C and 64-bit integers req'd */ /* #define AES_IMPLEMENTAION 1 1 = OpenSSL, 2 = Barreto, 3 = Gladman */ /* #define SSE2 0 Is SSE2 is available? */ /* #define RUN_TESTS 0 Run basic correctness/speed tests */ /* #define UMAC_AE_SUPPORT 0 Enable auhthenticated encryption */ /* ---------------------------------------------------------------------- */ /* -- Global Includes --------------------------------------------------- */ /* ---------------------------------------------------------------------- */ #include "mod_proxy.h" #include "proxy/ssh/umac.h" #if defined(PR_USE_OPENSSL) # include # include #endif /* PR_USE_OPENSSL */ #include #include #include #if OPENSSL_VERSION_NUMBER > 0x000907000L /* ---------------------------------------------------------------------- */ /* --- Primitive Data Types --- */ /* ---------------------------------------------------------------------- */ /* The following assumptions may need change on your system */ typedef uint8_t UINT8; /* 1 byte */ typedef uint16_t UINT16; /* 2 byte */ typedef uint32_t UINT32; /* 4 byte */ typedef uint64_t UINT64; /* 8 bytes */ typedef unsigned int UWORD; /* Register */ /* ---------------------------------------------------------------------- */ /* --- Constants -------------------------------------------------------- */ /* ---------------------------------------------------------------------- */ #define UMAC_KEY_LEN 16 /* UMAC takes 16 bytes of external key */ /* Message "words" are read from memory in an endian-specific manner. */ /* For this implementation to behave correctly, __LITTLE_ENDIAN__ must */ /* be set true if the host computer is little-endian. */ #if BYTE_ORDER == LITTLE_ENDIAN #define __LITTLE_ENDIAN__ 1 #else #define __LITTLE_ENDIAN__ 0 #endif /* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */ /* ----- Architecture Specific ------------------------------------------ */ /* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */ /* ----- Primitive Routines --------------------------------------------- */ /* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */ /* --- 32-bit by 32-bit to 64-bit Multiplication ------------------------ */ /* ---------------------------------------------------------------------- */ #define MUL64(a,b) ((UINT64)((UINT64)(UINT32)(a) * (UINT64)(UINT32)(b))) /* ---------------------------------------------------------------------- */ /* --- Endian Conversion --- Forcing assembly on some platforms */ /* ---------------------------------------------------------------------- */ #if defined(HAVE_SWAP32) #define LOAD_UINT32_REVERSED(p) (swap32(*(UINT32 *)(p))) #define STORE_UINT32_REVERSED(p,v) (*(UINT32 *)(p) = swap32(v)) #else /* HAVE_SWAP32 */ static UINT32 LOAD_UINT32_REVERSED(void *ptr) { UINT32 temp = *(UINT32 *)ptr; temp = (temp >> 24) | ((temp & 0x00FF0000) >> 8 ) | ((temp & 0x0000FF00) << 8 ) | (temp << 24); return (UINT32)temp; } # if (__LITTLE_ENDIAN__) static void STORE_UINT32_REVERSED(void *ptr, UINT32 x) { UINT32 i = (UINT32)x; *(UINT32 *)ptr = (i >> 24) | ((i & 0x00FF0000) >> 8 ) | ((i & 0x0000FF00) << 8 ) | (i << 24); } # endif /* __LITTLE_ENDIAN */ #endif /* HAVE_SWAP32 */ /* The following definitions use the above reversal-primitives to do the right * thing on endian specific load and stores. */ #if (__LITTLE_ENDIAN__) #define LOAD_UINT32_LITTLE(ptr) (*(UINT32 *)(ptr)) #define STORE_UINT32_BIG(ptr,x) STORE_UINT32_REVERSED(ptr,x) #else #define LOAD_UINT32_LITTLE(ptr) LOAD_UINT32_REVERSED(ptr) #define STORE_UINT32_BIG(ptr,x) (*(UINT32 *)(ptr) = (UINT32)(x)) #endif /* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */ /* ----- Begin KDF & PDF Section ---------------------------------------- */ /* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */ /* UMAC uses AES with 16 byte block and key lengths */ #define AES_BLOCK_LEN 16 /* OpenSSL's AES */ typedef AES_KEY aes_int_key[1]; #define aes_encryption(in,out,int_key) \ AES_encrypt((unsigned char *)(in),(unsigned char *)(out),(AES_KEY *)int_key) #define aes_key_setup(key,int_key) \ AES_set_encrypt_key((unsigned char *)(key),UMAC_KEY_LEN*8,int_key) /* The user-supplied UMAC key is stretched using AES in a counter * mode to supply all random bits needed by UMAC. The kdf function takes * an AES internal key representation 'key' and writes a stream of * 'nbytes' bytes to the memory pointed at by 'bufp'. Each distinct * 'ndx' causes a distinct byte stream. */ static void kdf(void *bufp, aes_int_key key, UINT8 ndx, int nbytes) { UINT8 in_buf[AES_BLOCK_LEN] = {0}; UINT8 out_buf[AES_BLOCK_LEN]; UINT8 *dst_buf = (UINT8 *)bufp; int i; /* Setup the initial value */ in_buf[AES_BLOCK_LEN-9] = ndx; in_buf[AES_BLOCK_LEN-1] = i = 1; while (nbytes >= AES_BLOCK_LEN) { aes_encryption(in_buf, out_buf, key); memcpy(dst_buf,out_buf,AES_BLOCK_LEN); in_buf[AES_BLOCK_LEN-1] = ++i; nbytes -= AES_BLOCK_LEN; dst_buf += AES_BLOCK_LEN; } if (nbytes) { aes_encryption(in_buf, out_buf, key); memcpy(dst_buf,out_buf,nbytes); } #if defined(HAVE_MEMSET_S) memset_s(in_buf, sizeof(in_buf), 0, sizeof(in_buf)); memset_s(out_buf, sizeof(out_buf), 0, sizeof(out_buf)); #endif /* HAVE_MEMSET_S */ } /* The final UHASH result is XOR'd with the output of a pseudorandom * function. Here, we use AES to generate random output and * xor the appropriate bytes depending on the last bits of nonce. * This scheme is optimized for sequential, increasing big-endian nonces. */ typedef struct { UINT8 cache[AES_BLOCK_LEN]; /* Previous AES output is saved */ UINT8 nonce[AES_BLOCK_LEN]; /* The AES input making above cache */ aes_int_key prf_key; /* Expanded AES key for PDF */ } pdf_ctx; static void pdf_init(pdf_ctx *pc, aes_int_key prf_key) { UINT8 buf[UMAC_KEY_LEN]; kdf(buf, prf_key, 0, UMAC_KEY_LEN); aes_key_setup(buf, pc->prf_key); /* Initialize pdf and cache */ memset(pc->nonce, 0, sizeof(pc->nonce)); aes_encryption(pc->nonce, pc->cache, pc->prf_key); #if defined(HAVE_MEMSET_S) memset_s(buf, sizeof(buf), 0, sizeof(buf)); #endif /* HAVE_MEMSET_S */ } static void pdf_gen_xor(pdf_ctx *pc, const UINT8 nonce[8], UINT8 buf[8]) { /* 'ndx' indicates that we'll be using the 0th or 1st eight bytes * of the AES output. If last time around we returned the ndx-1st * element, then we may have the result in the cache already. */ #if (UMAC_OUTPUT_LEN == 4) #define LOW_BIT_MASK 3 #elif (UMAC_OUTPUT_LEN == 8) #define LOW_BIT_MASK 1 #elif (UMAC_OUTPUT_LEN > 8) #define LOW_BIT_MASK 0 #endif union { UINT8 tmp_nonce_lo[4]; UINT32 align; } t; #if LOW_BIT_MASK != 0 int ndx = nonce[7] & LOW_BIT_MASK; #endif /* ProFTPD Note: We've changed the original code where, which used explicit * typecasting to treat the nonce[8] as a UINT32, to memcpy(3)/memcmp(3), * to avoid strict aliasing gcc warnings when -O2 or higher is used. */ memcpy(t.tmp_nonce_lo, &(nonce[4]), sizeof(UINT32)); t.tmp_nonce_lo[3] &= ~LOW_BIT_MASK; /* zero last bit */ if ( (memcmp(t.tmp_nonce_lo, &(pc->nonce[4]), sizeof(UINT32)) != 0) || (memcmp(nonce, pc->nonce, sizeof(UINT32)) != 0) ) { memmove(pc->nonce, nonce, sizeof(UINT32)); memmove(&(pc->nonce[4]), t.tmp_nonce_lo, sizeof(UINT32)); aes_encryption(pc->nonce, pc->cache, pc->prf_key); } #if (UMAC_OUTPUT_LEN == 4) *((UINT32 *)buf) ^= ((UINT32 *)pc->cache)[ndx]; #elif (UMAC_OUTPUT_LEN == 8) *((UINT64 *)buf) ^= ((UINT64 *)pc->cache)[ndx]; #elif (UMAC_OUTPUT_LEN == 12) ((UINT64 *)buf)[0] ^= ((UINT64 *)pc->cache)[0]; ((UINT32 *)buf)[2] ^= ((UINT32 *)pc->cache)[2]; #elif (UMAC_OUTPUT_LEN == 16) ((UINT64 *)buf)[0] ^= ((UINT64 *)pc->cache)[0]; ((UINT64 *)buf)[1] ^= ((UINT64 *)pc->cache)[1]; #endif } /* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */ /* ----- Begin NH Hash Section ------------------------------------------ */ /* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */ /* The NH-based hash functions used in UMAC are described in the UMAC paper * and specification, both of which can be found at the UMAC website. * The interface to this implementation has two * versions, one expects the entire message being hashed to be passed * in a single buffer and returns the hash result immediately. The second * allows the message to be passed in a sequence of buffers. In the * muliple-buffer interface, the client calls the routine nh_update() as * many times as necessary. When there is no more data to be fed to the * hash, the client calls nh_final() which calculates the hash output. * Before beginning another hash calculation the nh_reset() routine * must be called. The single-buffer routine, nh(), is equivalent to * the sequence of calls nh_update() and nh_final(); however it is * optimized and should be preferred whenever the multiple-buffer interface * is not necessary. When using either interface, it is the client's * responsibility to pass no more than L1_KEY_LEN bytes per hash result. * * The routine nh_init() initializes the nh_ctx data structure and * must be called once, before any other PDF routine. */ /* The "nh_aux" routines do the actual NH hashing work. They * expect buffers to be multiples of L1_PAD_BOUNDARY. These routines * produce output for all STREAMS NH iterations in one call, * allowing the parallel implementation of the streams. */ #define STREAMS (UMAC_OUTPUT_LEN / 4) /* Number of times hash is applied */ #define L1_KEY_LEN 1024 /* Internal key bytes */ #define L1_KEY_SHIFT 16 /* Toeplitz key shift between streams */ #define L1_PAD_BOUNDARY 32 /* pad message to boundary multiple */ #define ALLOC_BOUNDARY 16 /* Keep buffers aligned to this */ #define HASH_BUF_BYTES 64 /* nh_aux_hb buffer multiple */ typedef struct { UINT8 nh_key [L1_KEY_LEN + L1_KEY_SHIFT * (STREAMS - 1)]; /* NH Key */ UINT8 data [HASH_BUF_BYTES]; /* Incoming data buffer */ int next_data_empty; /* Bookkeeping variable for data buffer. */ int bytes_hashed; /* Bytes (out of L1_KEY_LEN) incorporated. */ UINT64 state[STREAMS]; /* on-line state */ } nh_ctx; #if (UMAC_OUTPUT_LEN == 4) static void nh_aux(void *kp, const void *dp, void *hp, UINT32 dlen) /* NH hashing primitive. Previous (partial) hash result is loaded and * then stored via hp pointer. The length of the data pointed at by "dp", * "dlen", is guaranteed to be divisible by L1_PAD_BOUNDARY (32). Key * is expected to be endian compensated in memory at key setup. */ { UINT64 h; UWORD c = dlen / 32; UINT32 *k = (UINT32 *)kp; const UINT32 *d = (const UINT32 *)dp; UINT32 d0,d1,d2,d3,d4,d5,d6,d7; UINT32 k0,k1,k2,k3,k4,k5,k6,k7; h = *((UINT64 *)hp); do { d0 = LOAD_UINT32_LITTLE(d+0); d1 = LOAD_UINT32_LITTLE(d+1); d2 = LOAD_UINT32_LITTLE(d+2); d3 = LOAD_UINT32_LITTLE(d+3); d4 = LOAD_UINT32_LITTLE(d+4); d5 = LOAD_UINT32_LITTLE(d+5); d6 = LOAD_UINT32_LITTLE(d+6); d7 = LOAD_UINT32_LITTLE(d+7); k0 = *(k+0); k1 = *(k+1); k2 = *(k+2); k3 = *(k+3); k4 = *(k+4); k5 = *(k+5); k6 = *(k+6); k7 = *(k+7); h += MUL64((k0 + d0), (k4 + d4)); h += MUL64((k1 + d1), (k5 + d5)); h += MUL64((k2 + d2), (k6 + d6)); h += MUL64((k3 + d3), (k7 + d7)); d += 8; k += 8; } while (--c); *((UINT64 *)hp) = h; } #elif (UMAC_OUTPUT_LEN == 8) static void nh_aux(void *kp, const void *dp, void *hp, UINT32 dlen) /* Same as previous nh_aux, but two streams are handled in one pass, * reading and writing 16 bytes of hash-state per call. */ { UINT64 h1,h2; UWORD c = dlen / 32; UINT32 *k = (UINT32 *)kp; const UINT32 *d = (const UINT32 *)dp; UINT32 d0,d1,d2,d3,d4,d5,d6,d7; UINT32 k0,k1,k2,k3,k4,k5,k6,k7, k8,k9,k10,k11; h1 = *((UINT64 *)hp); h2 = *((UINT64 *)hp + 1); k0 = *(k+0); k1 = *(k+1); k2 = *(k+2); k3 = *(k+3); do { d0 = LOAD_UINT32_LITTLE(d+0); d1 = LOAD_UINT32_LITTLE(d+1); d2 = LOAD_UINT32_LITTLE(d+2); d3 = LOAD_UINT32_LITTLE(d+3); d4 = LOAD_UINT32_LITTLE(d+4); d5 = LOAD_UINT32_LITTLE(d+5); d6 = LOAD_UINT32_LITTLE(d+6); d7 = LOAD_UINT32_LITTLE(d+7); k4 = *(k+4); k5 = *(k+5); k6 = *(k+6); k7 = *(k+7); k8 = *(k+8); k9 = *(k+9); k10 = *(k+10); k11 = *(k+11); h1 += MUL64((k0 + d0), (k4 + d4)); h2 += MUL64((k4 + d0), (k8 + d4)); h1 += MUL64((k1 + d1), (k5 + d5)); h2 += MUL64((k5 + d1), (k9 + d5)); h1 += MUL64((k2 + d2), (k6 + d6)); h2 += MUL64((k6 + d2), (k10 + d6)); h1 += MUL64((k3 + d3), (k7 + d7)); h2 += MUL64((k7 + d3), (k11 + d7)); k0 = k8; k1 = k9; k2 = k10; k3 = k11; d += 8; k += 8; } while (--c); ((UINT64 *)hp)[0] = h1; ((UINT64 *)hp)[1] = h2; } #elif (UMAC_OUTPUT_LEN == 12) static void nh_aux(void *kp, const void *dp, void *hp, UINT32 dlen) /* Same as previous nh_aux, but two streams are handled in one pass, * reading and writing 24 bytes of hash-state per call. */ { UINT64 h1,h2,h3; UWORD c = dlen / 32; UINT32 *k = (UINT32 *)kp; const UINT32 *d = (const UINT32 *)dp; UINT32 d0,d1,d2,d3,d4,d5,d6,d7; UINT32 k0,k1,k2,k3,k4,k5,k6,k7, k8,k9,k10,k11,k12,k13,k14,k15; h1 = *((UINT64 *)hp); h2 = *((UINT64 *)hp + 1); h3 = *((UINT64 *)hp + 2); k0 = *(k+0); k1 = *(k+1); k2 = *(k+2); k3 = *(k+3); k4 = *(k+4); k5 = *(k+5); k6 = *(k+6); k7 = *(k+7); do { d0 = LOAD_UINT32_LITTLE(d+0); d1 = LOAD_UINT32_LITTLE(d+1); d2 = LOAD_UINT32_LITTLE(d+2); d3 = LOAD_UINT32_LITTLE(d+3); d4 = LOAD_UINT32_LITTLE(d+4); d5 = LOAD_UINT32_LITTLE(d+5); d6 = LOAD_UINT32_LITTLE(d+6); d7 = LOAD_UINT32_LITTLE(d+7); k8 = *(k+8); k9 = *(k+9); k10 = *(k+10); k11 = *(k+11); k12 = *(k+12); k13 = *(k+13); k14 = *(k+14); k15 = *(k+15); h1 += MUL64((k0 + d0), (k4 + d4)); h2 += MUL64((k4 + d0), (k8 + d4)); h3 += MUL64((k8 + d0), (k12 + d4)); h1 += MUL64((k1 + d1), (k5 + d5)); h2 += MUL64((k5 + d1), (k9 + d5)); h3 += MUL64((k9 + d1), (k13 + d5)); h1 += MUL64((k2 + d2), (k6 + d6)); h2 += MUL64((k6 + d2), (k10 + d6)); h3 += MUL64((k10 + d2), (k14 + d6)); h1 += MUL64((k3 + d3), (k7 + d7)); h2 += MUL64((k7 + d3), (k11 + d7)); h3 += MUL64((k11 + d3), (k15 + d7)); k0 = k8; k1 = k9; k2 = k10; k3 = k11; k4 = k12; k5 = k13; k6 = k14; k7 = k15; d += 8; k += 8; } while (--c); ((UINT64 *)hp)[0] = h1; ((UINT64 *)hp)[1] = h2; ((UINT64 *)hp)[2] = h3; } #elif (UMAC_OUTPUT_LEN == 16) static void nh_aux(void *kp, const void *dp, void *hp, UINT32 dlen) /* Same as previous nh_aux, but two streams are handled in one pass, * reading and writing 24 bytes of hash-state per call. */ { UINT64 h1,h2,h3,h4; UWORD c = dlen / 32; UINT32 *k = (UINT32 *)kp; const UINT32 *d = (const UINT32 *)dp; UINT32 d0,d1,d2,d3,d4,d5,d6,d7; UINT32 k0,k1,k2,k3,k4,k5,k6,k7, k8,k9,k10,k11,k12,k13,k14,k15, k16,k17,k18,k19; h1 = *((UINT64 *)hp); h2 = *((UINT64 *)hp + 1); h3 = *((UINT64 *)hp + 2); h4 = *((UINT64 *)hp + 3); k0 = *(k+0); k1 = *(k+1); k2 = *(k+2); k3 = *(k+3); k4 = *(k+4); k5 = *(k+5); k6 = *(k+6); k7 = *(k+7); do { d0 = LOAD_UINT32_LITTLE(d+0); d1 = LOAD_UINT32_LITTLE(d+1); d2 = LOAD_UINT32_LITTLE(d+2); d3 = LOAD_UINT32_LITTLE(d+3); d4 = LOAD_UINT32_LITTLE(d+4); d5 = LOAD_UINT32_LITTLE(d+5); d6 = LOAD_UINT32_LITTLE(d+6); d7 = LOAD_UINT32_LITTLE(d+7); k8 = *(k+8); k9 = *(k+9); k10 = *(k+10); k11 = *(k+11); k12 = *(k+12); k13 = *(k+13); k14 = *(k+14); k15 = *(k+15); k16 = *(k+16); k17 = *(k+17); k18 = *(k+18); k19 = *(k+19); h1 += MUL64((k0 + d0), (k4 + d4)); h2 += MUL64((k4 + d0), (k8 + d4)); h3 += MUL64((k8 + d0), (k12 + d4)); h4 += MUL64((k12 + d0), (k16 + d4)); h1 += MUL64((k1 + d1), (k5 + d5)); h2 += MUL64((k5 + d1), (k9 + d5)); h3 += MUL64((k9 + d1), (k13 + d5)); h4 += MUL64((k13 + d1), (k17 + d5)); h1 += MUL64((k2 + d2), (k6 + d6)); h2 += MUL64((k6 + d2), (k10 + d6)); h3 += MUL64((k10 + d2), (k14 + d6)); h4 += MUL64((k14 + d2), (k18 + d6)); h1 += MUL64((k3 + d3), (k7 + d7)); h2 += MUL64((k7 + d3), (k11 + d7)); h3 += MUL64((k11 + d3), (k15 + d7)); h4 += MUL64((k15 + d3), (k19 + d7)); k0 = k8; k1 = k9; k2 = k10; k3 = k11; k4 = k12; k5 = k13; k6 = k14; k7 = k15; k8 = k16; k9 = k17; k10 = k18; k11 = k19; d += 8; k += 8; } while (--c); ((UINT64 *)hp)[0] = h1; ((UINT64 *)hp)[1] = h2; ((UINT64 *)hp)[2] = h3; ((UINT64 *)hp)[3] = h4; } /* ---------------------------------------------------------------------- */ #endif /* UMAC_OUTPUT_LENGTH */ /* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */ static void nh_transform(nh_ctx *hc, const UINT8 *buf, UINT32 nbytes) /* This function is a wrapper for the primitive NH hash functions. It takes * as argument "hc" the current hash context and a buffer which must be a * multiple of L1_PAD_BOUNDARY. The key passed to nh_aux is offset * appropriately according to how much message has been hashed already. */ { UINT8 *key; key = hc->nh_key + hc->bytes_hashed; nh_aux(key, buf, hc->state, nbytes); } /* ---------------------------------------------------------------------- */ #if (__LITTLE_ENDIAN__) static void endian_convert(void *buf, UWORD bpw, UINT32 num_bytes) /* We endian convert the keys on little-endian computers to */ /* compensate for the lack of big-endian memory reads during hashing. */ { UWORD iters = num_bytes / bpw; if (bpw == 4) { UINT32 *p = (UINT32 *)buf; do { *p = LOAD_UINT32_REVERSED(p); p++; } while (--iters); } else if (bpw == 8) { UINT32 *p = (UINT32 *)buf; UINT32 t; do { t = LOAD_UINT32_REVERSED(p+1); p[1] = LOAD_UINT32_REVERSED(p); p[0] = t; p += 2; } while (--iters); } } #define endian_convert_if_le(x,y,z) endian_convert((x),(y),(z)) #else #define endian_convert_if_le(x,y,z) do{}while(0) /* Do nothing */ #endif /* ---------------------------------------------------------------------- */ static void nh_reset(nh_ctx *hc) /* Reset nh_ctx to ready for hashing of new data */ { hc->bytes_hashed = 0; hc->next_data_empty = 0; hc->state[0] = 0; #if (UMAC_OUTPUT_LEN >= 8) hc->state[1] = 0; #endif #if (UMAC_OUTPUT_LEN >= 12) hc->state[2] = 0; #endif #if (UMAC_OUTPUT_LEN == 16) hc->state[3] = 0; #endif } /* ---------------------------------------------------------------------- */ static void nh_init(nh_ctx *hc, aes_int_key prf_key) /* Generate nh_key, endian convert and reset to be ready for hashing. */ { kdf(hc->nh_key, prf_key, 1, sizeof(hc->nh_key)); endian_convert_if_le(hc->nh_key, 4, sizeof(hc->nh_key)); nh_reset(hc); } /* ---------------------------------------------------------------------- */ static void nh_update(nh_ctx *hc, const UINT8 *buf, UINT32 nbytes) /* Incorporate nbytes of data into a nh_ctx, buffer whatever is not an */ /* even multiple of HASH_BUF_BYTES. */ { UINT32 i,j; j = hc->next_data_empty; if ((j + nbytes) >= HASH_BUF_BYTES) { if (j) { i = HASH_BUF_BYTES - j; memcpy(hc->data+j, buf, i); nh_transform(hc,hc->data,HASH_BUF_BYTES); nbytes -= i; buf += i; hc->bytes_hashed += HASH_BUF_BYTES; } if (nbytes >= HASH_BUF_BYTES) { i = nbytes & ~(HASH_BUF_BYTES - 1); nh_transform(hc, buf, i); nbytes -= i; buf += i; hc->bytes_hashed += i; } j = 0; } memcpy(hc->data + j, buf, nbytes); hc->next_data_empty = j + nbytes; } /* ---------------------------------------------------------------------- */ static void zero_pad(UINT8 *p, int nbytes) { /* Write "nbytes" of zeroes, beginning at "p" */ if (nbytes >= (int)sizeof(UWORD)) { while ((ptrdiff_t)p % sizeof(UWORD)) { *p = 0; nbytes--; p++; } while (nbytes >= (int)sizeof(UWORD)) { *(UWORD *)p = 0; nbytes -= sizeof(UWORD); p += sizeof(UWORD); } } while (nbytes) { *p = 0; nbytes--; p++; } } /* ---------------------------------------------------------------------- */ static void nh_final(nh_ctx *hc, UINT8 *result) /* After passing some number of data buffers to nh_update() for integration * into an NH context, nh_final is called to produce a hash result. If any * bytes are in the buffer hc->data, incorporate them into the * NH context. Finally, add into the NH accumulation "state" the total number * of bits hashed. The resulting numbers are written to the buffer "result". * If nh_update was never called, L1_PAD_BOUNDARY zeroes are incorporated. */ { int nh_len, nbits; if (hc->next_data_empty != 0) { nh_len = ((hc->next_data_empty + (L1_PAD_BOUNDARY - 1)) & ~(L1_PAD_BOUNDARY - 1)); zero_pad(hc->data + hc->next_data_empty, nh_len - hc->next_data_empty); nh_transform(hc, hc->data, nh_len); hc->bytes_hashed += hc->next_data_empty; } else if (hc->bytes_hashed == 0) { nh_len = L1_PAD_BOUNDARY; zero_pad(hc->data, L1_PAD_BOUNDARY); nh_transform(hc, hc->data, nh_len); } nbits = (hc->bytes_hashed << 3); ((UINT64 *)result)[0] = ((UINT64 *)hc->state)[0] + nbits; #if (UMAC_OUTPUT_LEN >= 8) ((UINT64 *)result)[1] = ((UINT64 *)hc->state)[1] + nbits; #endif #if (UMAC_OUTPUT_LEN >= 12) ((UINT64 *)result)[2] = ((UINT64 *)hc->state)[2] + nbits; #endif #if (UMAC_OUTPUT_LEN == 16) ((UINT64 *)result)[3] = ((UINT64 *)hc->state)[3] + nbits; #endif nh_reset(hc); } /* ---------------------------------------------------------------------- */ static void nh(nh_ctx *hc, const UINT8 *buf, UINT32 padded_len, UINT32 unpadded_len, UINT8 *result) /* All-in-one nh_update() and nh_final() equivalent. * Assumes that padded_len is divisible by L1_PAD_BOUNDARY and result is * well aligned */ { UINT32 nbits; /* Initialize the hash state */ nbits = (unpadded_len << 3); ((UINT64 *)result)[0] = nbits; #if (UMAC_OUTPUT_LEN >= 8) ((UINT64 *)result)[1] = nbits; #endif #if (UMAC_OUTPUT_LEN >= 12) ((UINT64 *)result)[2] = nbits; #endif #if (UMAC_OUTPUT_LEN == 16) ((UINT64 *)result)[3] = nbits; #endif nh_aux(hc->nh_key, buf, result, padded_len); } /* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */ /* ----- Begin UHASH Section -------------------------------------------- */ /* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */ /* UHASH is a multi-layered algorithm. Data presented to UHASH is first * hashed by NH. The NH output is then hashed by a polynomial-hash layer * unless the initial data to be hashed is short. After the polynomial- * layer, an inner-product hash is used to produce the final UHASH output. * * UHASH provides two interfaces, one all-at-once and another where data * buffers are presented sequentially. In the sequential interface, the * UHASH client calls the routine uhash_update() as many times as necessary. * When there is no more data to be fed to UHASH, the client calls * uhash_final() which * calculates the UHASH output. Before beginning another UHASH calculation * the uhash_reset() routine must be called. The all-at-once UHASH routine, * uhash(), is equivalent to the sequence of calls uhash_update() and * uhash_final(); however it is optimized and should be * used whenever the sequential interface is not necessary. * * The routine uhash_init() initializes the uhash_ctx data structure and * must be called once, before any other UHASH routine. */ /* ---------------------------------------------------------------------- */ /* ----- Constants and uhash_ctx ---------------------------------------- */ /* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */ /* ----- Poly hash and Inner-Product hash Constants --------------------- */ /* ---------------------------------------------------------------------- */ /* Primes and masks */ #define p36 ((UINT64)0x0000000FFFFFFFFBull) /* 2^36 - 5 */ #define p64 ((UINT64)0xFFFFFFFFFFFFFFC5ull) /* 2^64 - 59 */ #define m36 ((UINT64)0x0000000FFFFFFFFFull) /* The low 36 of 64 bits */ /* ---------------------------------------------------------------------- */ typedef struct uhash_ctx { nh_ctx hash; /* Hash context for L1 NH hash */ UINT64 poly_key_8[STREAMS]; /* p64 poly keys */ UINT64 poly_accum[STREAMS]; /* poly hash result */ UINT64 ip_keys[STREAMS*4]; /* Inner-product keys */ UINT32 ip_trans[STREAMS]; /* Inner-product translation */ UINT32 msg_len; /* Total length of data passed */ /* to uhash */ } uhash_ctx; typedef struct uhash_ctx *uhash_ctx_t; /* ---------------------------------------------------------------------- */ /* The polynomial hashes use Horner's rule to evaluate a polynomial one * word at a time. As described in the specification, poly32 and poly64 * require keys from special domains. The following implementations exploit * the special domains to avoid overflow. The results are not guaranteed to * be within Z_p32 and Z_p64, but the Inner-Product hash implementation * patches any errant values. */ static UINT64 poly64(UINT64 cur, UINT64 key, UINT64 data) { UINT32 key_hi = (UINT32)(key >> 32), key_lo = (UINT32)key, cur_hi = (UINT32)(cur >> 32), cur_lo = (UINT32)cur, x_lo, x_hi; UINT64 X,T,res; X = MUL64(key_hi, cur_lo) + MUL64(cur_hi, key_lo); x_lo = (UINT32)X; x_hi = (UINT32)(X >> 32); res = (MUL64(key_hi, cur_hi) + x_hi) * 59 + MUL64(key_lo, cur_lo); T = ((UINT64)x_lo << 32); res += T; if (res < T) res += 59; res += data; if (res < data) res += 59; return res; } /* Although UMAC is specified to use a ramped polynomial hash scheme, this * implementation does not handle all ramp levels. Because we don't handle * the ramp up to p128 modulus in this implementation, we are limited to * 2^14 poly_hash() invocations per stream (for a total capacity of 2^24 * bytes input to UMAC per tag, ie. 16MB). */ static void poly_hash(uhash_ctx_t hc, UINT32 data_in[]) { int i; UINT64 *data=(UINT64*)data_in; for (i = 0; i < STREAMS; i++) { if ((UINT32)(data[i] >> 32) == 0xfffffffful) { hc->poly_accum[i] = poly64(hc->poly_accum[i], hc->poly_key_8[i], p64 - 1); hc->poly_accum[i] = poly64(hc->poly_accum[i], hc->poly_key_8[i], (data[i] - 59)); } else { hc->poly_accum[i] = poly64(hc->poly_accum[i], hc->poly_key_8[i], data[i]); } } } /* ---------------------------------------------------------------------- */ /* The final step in UHASH is an inner-product hash. The poly hash * produces a result not necessarily WORD_LEN bytes long. The inner- * product hash breaks the polyhash output into 16-bit chunks and * multiplies each with a 36 bit key. */ static UINT64 ip_aux(UINT64 t, UINT64 *ipkp, UINT64 data) { t = t + ipkp[0] * (UINT64)(UINT16)(data >> 48); t = t + ipkp[1] * (UINT64)(UINT16)(data >> 32); t = t + ipkp[2] * (UINT64)(UINT16)(data >> 16); t = t + ipkp[3] * (UINT64)(UINT16)(data); return t; } static UINT32 ip_reduce_p36(UINT64 t) { /* Divisionless modular reduction */ UINT64 ret; ret = (t & m36) + 5 * (t >> 36); if (ret >= p36) ret -= p36; /* return least significant 32 bits */ return (UINT32)(ret); } /* If the data being hashed by UHASH is no longer than L1_KEY_LEN, then * the polyhash stage is skipped and ip_short is applied directly to the * NH output. */ static void ip_short(uhash_ctx_t ahc, UINT8 *nh_res, unsigned char *res) { UINT64 t; UINT64 *nhp = (UINT64 *)nh_res; t = ip_aux(0,ahc->ip_keys, nhp[0]); STORE_UINT32_BIG((UINT32 *)res+0, ip_reduce_p36(t) ^ ahc->ip_trans[0]); #if (UMAC_OUTPUT_LEN >= 8) t = ip_aux(0,ahc->ip_keys+4, nhp[1]); STORE_UINT32_BIG((UINT32 *)res+1, ip_reduce_p36(t) ^ ahc->ip_trans[1]); #endif #if (UMAC_OUTPUT_LEN >= 12) t = ip_aux(0,ahc->ip_keys+8, nhp[2]); STORE_UINT32_BIG((UINT32 *)res+2, ip_reduce_p36(t) ^ ahc->ip_trans[2]); #endif #if (UMAC_OUTPUT_LEN == 16) t = ip_aux(0,ahc->ip_keys+12, nhp[3]); STORE_UINT32_BIG((UINT32 *)res+3, ip_reduce_p36(t) ^ ahc->ip_trans[3]); #endif } /* If the data being hashed by UHASH is longer than L1_KEY_LEN, then * the polyhash stage is not skipped and ip_long is applied to the * polyhash output. */ static void ip_long(uhash_ctx_t ahc, unsigned char *res) { int i; UINT64 t; for (i = 0; i < STREAMS; i++) { /* fix polyhash output not in Z_p64 */ if (ahc->poly_accum[i] >= p64) ahc->poly_accum[i] -= p64; t = ip_aux(0,ahc->ip_keys+(i*4), ahc->poly_accum[i]); STORE_UINT32_BIG((UINT32 *)res+i, ip_reduce_p36(t) ^ ahc->ip_trans[i]); } } /* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */ /* Reset uhash context for next hash session */ static int uhash_reset(uhash_ctx_t pc) { nh_reset(&pc->hash); pc->msg_len = 0; pc->poly_accum[0] = 1; #if (UMAC_OUTPUT_LEN >= 8) pc->poly_accum[1] = 1; #endif #if (UMAC_OUTPUT_LEN >= 12) pc->poly_accum[2] = 1; #endif #if (UMAC_OUTPUT_LEN == 16) pc->poly_accum[3] = 1; #endif return 1; } /* ---------------------------------------------------------------------- */ /* Given a pointer to the internal key needed by kdf() and a uhash context, * initialize the NH context and generate keys needed for poly and inner- * product hashing. All keys are endian adjusted in memory so that native * loads cause correct keys to be in registers during calculation. */ static void uhash_init(uhash_ctx_t ahc, aes_int_key prf_key) { int i; UINT8 buf[(8*STREAMS+4)*sizeof(UINT64)]; /* Zero the entire uhash context */ memset(ahc, 0, sizeof(uhash_ctx)); /* Initialize the L1 hash */ nh_init(&ahc->hash, prf_key); /* Setup L2 hash variables */ kdf(buf, prf_key, 2, sizeof(buf)); /* Fill buffer with index 1 key */ for (i = 0; i < STREAMS; i++) { /* Fill keys from the buffer, skipping bytes in the buffer not * used by this implementation. Endian reverse the keys if on a * little-endian computer. */ memcpy(ahc->poly_key_8+i, buf+24*i, 8); endian_convert_if_le(ahc->poly_key_8+i, 8, 8); /* Mask the 64-bit keys to their special domain */ ahc->poly_key_8[i] &= ((UINT64)0x01ffffffu << 32) + 0x01ffffffu; ahc->poly_accum[i] = 1; /* Our polyhash prepends a non-zero word */ } /* Setup L3-1 hash variables */ kdf(buf, prf_key, 3, sizeof(buf)); /* Fill buffer with index 2 key */ for (i = 0; i < STREAMS; i++) memcpy(ahc->ip_keys+4*i, buf+(8*i+4)*sizeof(UINT64), 4*sizeof(UINT64)); endian_convert_if_le(ahc->ip_keys, sizeof(UINT64), sizeof(ahc->ip_keys)); for (i = 0; i < STREAMS*4; i++) ahc->ip_keys[i] %= p36; /* Bring into Z_p36 */ /* Setup L3-2 hash variables */ /* Fill buffer with index 4 key */ kdf(ahc->ip_trans, prf_key, 4, STREAMS * sizeof(UINT32)); endian_convert_if_le(ahc->ip_trans, sizeof(UINT32), STREAMS * sizeof(UINT32)); #if defined(HAVE_MEMSET_S) memset_s(buf, sizeof(buf), 0, sizeof(buf)); #endif /* HAVE_MEMSET_S */ } /* ---------------------------------------------------------------------- */ static int uhash_update(uhash_ctx_t ctx, const unsigned char *input, long len) /* Given len bytes of data, we parse it into L1_KEY_LEN chunks and * hash each one with NH, calling the polyhash on each NH output. */ { UWORD bytes_hashed, bytes_remaining; UINT64 result_buf[STREAMS]; UINT8 *nh_result = (UINT8 *)&result_buf; if (ctx->msg_len + len <= L1_KEY_LEN) { nh_update(&ctx->hash, (const UINT8 *)input, len); ctx->msg_len += len; } else { bytes_hashed = ctx->msg_len % L1_KEY_LEN; if (ctx->msg_len == L1_KEY_LEN) bytes_hashed = L1_KEY_LEN; if (bytes_hashed + len >= L1_KEY_LEN) { /* If some bytes have been passed to the hash function */ /* then we want to pass at most (L1_KEY_LEN - bytes_hashed) */ /* bytes to complete the current nh_block. */ if (bytes_hashed) { bytes_remaining = (L1_KEY_LEN - bytes_hashed); nh_update(&ctx->hash, (const UINT8 *)input, bytes_remaining); nh_final(&ctx->hash, nh_result); ctx->msg_len += bytes_remaining; poly_hash(ctx,(UINT32 *)nh_result); len -= bytes_remaining; input += bytes_remaining; } /* Hash directly from input stream if enough bytes */ while (len >= L1_KEY_LEN) { nh(&ctx->hash, (const UINT8 *)input, L1_KEY_LEN, L1_KEY_LEN, nh_result); ctx->msg_len += L1_KEY_LEN; len -= L1_KEY_LEN; input += L1_KEY_LEN; poly_hash(ctx,(UINT32 *)nh_result); } } /* pass remaining < L1_KEY_LEN bytes of input data to NH */ if (len) { nh_update(&ctx->hash, (const UINT8 *)input, len); ctx->msg_len += len; } } return (1); } /* ---------------------------------------------------------------------- */ static int uhash_final(uhash_ctx_t ctx, unsigned char *res) /* Incorporate any pending data, pad, and generate tag */ { UINT64 result_buf[STREAMS]; UINT8 *nh_result = (UINT8 *)&result_buf; if (ctx->msg_len > L1_KEY_LEN) { if (ctx->msg_len % L1_KEY_LEN) { nh_final(&ctx->hash, nh_result); poly_hash(ctx,(UINT32 *)nh_result); } ip_long(ctx, res); } else { nh_final(&ctx->hash, nh_result); ip_short(ctx,nh_result, res); } uhash_reset(ctx); return (1); } /* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */ /* ----- Begin UMAC Section --------------------------------------------- */ /* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */ /* The UMAC interface has two interfaces, an all-at-once interface where * the entire message to be authenticated is passed to UMAC in one buffer, * and a sequential interface where the message is presented a little at a * time. The all-at-once is more optimaized than the sequential version and * should be preferred when the sequential interface is not required. */ struct umac_ctx { uhash_ctx hash; /* Hash function for message compression */ pdf_ctx pdf; /* PDF for hashed output */ void *free_ptr; /* Address to free this struct via */ }; /* ---------------------------------------------------------------------- */ int proxy_ssh_umac_reset(struct umac_ctx *ctx) /* Reset the hash function to begin a new authentication. */ { uhash_reset(&ctx->hash); return (1); } /* ---------------------------------------------------------------------- */ int proxy_ssh_umac_delete(struct umac_ctx *ctx) /* Deallocate the ctx structure */ { if (ctx) { if (ALLOC_BOUNDARY) ctx = (struct umac_ctx *)ctx->free_ptr; free(ctx); } return (1); } /* ---------------------------------------------------------------------- */ struct umac_ctx *proxy_ssh_umac_alloc(void) { struct umac_ctx *ctx, *octx; size_t bytes_to_add; octx = ctx = malloc(sizeof(struct umac_ctx) + ALLOC_BOUNDARY); if (ctx) { if (ALLOC_BOUNDARY) { bytes_to_add = ALLOC_BOUNDARY - ((ptrdiff_t)ctx & (ALLOC_BOUNDARY - 1)); ctx = (struct umac_ctx *)((unsigned char *)ctx + bytes_to_add); } ctx->free_ptr = octx; } return (ctx); } void proxy_ssh_umac_init(struct umac_ctx *ctx, const unsigned char key[]) { aes_int_key prf_key; aes_key_setup(key, prf_key); pdf_init(&ctx->pdf, prf_key); uhash_init(&ctx->hash, prf_key); } struct umac_ctx *proxy_ssh_umac_new(const unsigned char key[]) /* Dynamically allocate a umac_ctx struct, initialize variables, * generate subkeys from key. Align to 16-byte boundary. */ { struct umac_ctx *ctx; ctx = proxy_ssh_umac_alloc(); if (ctx) { proxy_ssh_umac_init(ctx, key); } return (ctx); } /* ---------------------------------------------------------------------- */ int proxy_ssh_umac_final(struct umac_ctx *ctx, unsigned char tag[], const unsigned char nonce[8]) /* Incorporate any pending data, pad, and generate tag */ { uhash_final(&ctx->hash, (unsigned char *)tag); pdf_gen_xor(&ctx->pdf, (const UINT8 *)nonce, (UINT8 *)tag); return (1); } /* ---------------------------------------------------------------------- */ int proxy_ssh_umac_update(struct umac_ctx *ctx, const unsigned char *input, long len) /* Given len bytes of data, we parse it into L1_KEY_LEN chunks and */ /* hash each one, calling the PDF on the hashed output whenever the hash- */ /* output buffer is full. */ { uhash_update(&ctx->hash, input, len); return (1); } #else struct umac_ctx { void *free_ptr; /* Address to free this struct via */ }; int proxy_ssh_umac_reset(struct umac_ctx *ctx) /* Reset the hash function to begin a new authentication. */ { return (0); } /* ---------------------------------------------------------------------- */ int proxy_ssh_umac_delete(struct umac_ctx *ctx) /* Deallocate the ctx structure */ { return (0); } /* ---------------------------------------------------------------------- */ struct umac_ctx *proxy_ssh_umac_alloc(void) { return (NULL); } void proxy_ssh_umac_init(struct umac_ctx *ctx, const unsigned char key[]) { } struct umac_ctx *proxy_ssh_umac_new(const unsigned char key[]) /* Dynamically allocate a umac_ctx struct, initialize variables, * generate subkeys from key. Align to 16-byte boundary. */ { return (NULL); } /* ---------------------------------------------------------------------- */ int proxy_ssh_umac_final(struct umac_ctx *ctx, unsigned char tag[], const unsigned char nonce[8]) { return (0); } /* ---------------------------------------------------------------------- */ int proxy_ssh_umac_update(struct umac_ctx *ctx, const unsigned char *input, long len) /* Given len bytes of data, we parse it into L1_KEY_LEN chunks and */ /* hash each one, calling the PDF on the hashed output whenever the hash- */ /* output buffer is full. */ { return (0); } #endif /* OpenSSL-0.9.7 or later */ /* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */ /* ----- End UMAC Section ----------------------------------------------- */ /* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */ proftpd-mod_proxy-0.9.5/lib/proxy/ssh/umac128.c000066400000000000000000000030361475737016700213140ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH UMAC * Copyright (c) 2022 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "proxy/ssh/umac.h" #define UMAC_OUTPUT_LEN 16 #define proxy_ssh_umac_alloc proxy_ssh_umac128_alloc #define proxy_ssh_umac_init proxy_ssh_umac128_init #define proxy_ssh_umac_new proxy_ssh_umac128_new #define proxy_ssh_umac_update proxy_ssh_umac128_update #define proxy_ssh_umac_reset proxy_ssh_umac128_reset #define proxy_ssh_umac_final proxy_ssh_umac128_final #define proxy_ssh_umac_delete proxy_ssh_umac128_delete #define proxy_ssh_umac_ctx proxy_ssh_umac128_ctx #include "umac.c" proftpd-mod_proxy-0.9.5/lib/proxy/ssh/utf8.c000066400000000000000000000216011475737016700210200ustar00rootroot00000000000000/* * ProFTPD - mod_proxy SSH UTF8 encoding * Copyright (c) 2021 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "proxy/ssh/utf8.h" #if defined(HAVE_ICONV_H) # include #endif #if defined(HAVE_LANGINFO_H) # include #endif static const char *local_charset = NULL; static const char *trace_channel = "proxy.ssh.utf8"; #if defined(PR_USE_NLS) && defined(HAVE_ICONV_H) static iconv_t decode_conv = (iconv_t) -1; static iconv_t encode_conv = (iconv_t) -1; static int utf8_convert(iconv_t conv, const char *inbuf, size_t *inbuflen, char *outbuf, size_t *outbuflen) { # ifdef HAVE_ICONV /* Reset the state machine before each conversion. */ (void) iconv(conv, NULL, NULL, NULL, NULL); while (*inbuflen > 0) { size_t nconv; pr_signals_handle(); /* Solaris/FreeBSD's iconv(3) takes a const char ** for the input buffer, * whereas Linux/Mac OSX iconv(3) use char ** for the input buffer. */ #if defined(LINUX) || defined(DARWIN6) || defined(DARWIN7) || \ defined(DARWIN8) || defined(DARWIN9) || defined(DARWIN10) || \ defined(DARWIN11) || defined(DARWIN12) nconv = iconv(conv, (char **) &inbuf, inbuflen, &outbuf, outbuflen); #else nconv = iconv(conv, &inbuf, inbuflen, &outbuf, outbuflen); #endif if (nconv == (size_t) -1) { /* Note: an errno of EILSEQ here can indicate badly encoded strings OR * (more likely) that the source character set used in the iconv_open(3) * call for this iconv_t descriptor does not accurately describe the * character encoding of the given string. E.g. a filename may use * the ISO8859-1 character set, but iconv_open(3) was called using * US-ASCII. */ return -1; } /* XXX We should let the loop condition work, rather than breaking out * of the loop here. */ break; } return 0; # else errno = ENOSYS; return -1; # endif /* HAVE_ICONV */ } #endif /* !PR_USE_NLS && !HAVE_ICONV_H */ #if defined(PR_USE_OPENSSL) int proxy_ssh_utf8_set_charset(const char *charset) { int res; if (charset == NULL) { errno = EINVAL; return -1; } if (local_charset) { pr_trace_msg(trace_channel, 5, "attempting to switch local charset from %s to %s", local_charset, charset); } else { pr_trace_msg(trace_channel, 5, "attempting to use %s as local charset", charset); } (void) proxy_ssh_utf8_free(); local_charset = pstrdup(permanent_pool, charset); res = proxy_ssh_utf8_init(); if (res < 0) { pr_trace_msg(trace_channel, 1, "failed to initialize encoding for local charset %s", charset); local_charset = NULL; return -1; } return res; } int proxy_ssh_utf8_free(void) { # if defined(PR_USE_NLS) && defined(HAVE_ICONV) int res = 0; /* Close the iconv handles. */ if (encode_conv != (iconv_t) -1) { res = iconv_close(encode_conv); if (res < 0) { pr_trace_msg(trace_channel, 1, "error closing encoding conversion handle from '%s' to '%s': %s", local_charset, "UTF-8", strerror(errno)); res = -1; } encode_conv = (iconv_t) -1; } if (decode_conv != (iconv_t) -1) { res = iconv_close(decode_conv); if (res < 0) { pr_trace_msg(trace_channel, 1, "error closing decoding conversion handle from '%s' to '%s': %s", "UTF-8", local_charset, strerror(errno)); res = -1; } decode_conv = (iconv_t) -1; } return res; # else errno = ENOSYS; return -1; # endif } int proxy_ssh_utf8_init(void) { #if defined(PR_USE_NLS) && defined(HAVE_ICONV) if (local_charset == NULL) { local_charset = pr_encode_get_local_charset(); } else { pr_trace_msg(trace_channel, 3, "using '%s' as local charset for UTF8 conversion", local_charset); } /* Get the iconv handles. */ encode_conv = iconv_open("UTF-8", local_charset); if (encode_conv == (iconv_t) -1) { pr_trace_msg(trace_channel, 1, "error opening conversion handle from '%s' " "to '%s': %s", local_charset, "UTF-8", strerror(errno)); return -1; } decode_conv = iconv_open(local_charset, "UTF-8"); if (decode_conv == (iconv_t) -1) { int xerrno = errno; pr_trace_msg(trace_channel, 1, "error opening conversion handle from '%s' " "to '%s': %s", "UTF-8", local_charset, strerror(errno)); (void) iconv_close(encode_conv); encode_conv = (iconv_t) -1; errno = xerrno; return -1; } return 0; # else errno = ENOSYS; return -1; #endif /* HAVE_ICONV */ } char *proxy_ssh_utf8_decode_text(pool *p, const char *text) { #if defined(PR_USE_NLS) && defined(HAVE_ICONV_H) size_t inlen, inbuflen, outlen, outbuflen; char *inbuf, outbuf[PR_TUNABLE_PATH_MAX*2], *res = NULL; if (p == NULL || text == NULL) { errno = EINVAL; return NULL; } if (decode_conv == (iconv_t) -1) { pr_trace_msg(trace_channel, 1, "decoding conversion handle is invalid, unable to decode UTF8 text"); return (char *) text; } /* If the local charset matches the remote charset (i.e. local_charset is * "UTF-8"), then there's no point in converting; the charsets are the * same. Indeed, on some libiconv implementations, attempting to * convert between the same charsets results in a tightly spinning CPU * (see Bug#3272). */ if (strcasecmp(local_charset, "UTF-8") == 0) { return (char *) text; } inlen = strlen(text) + 1; inbuf = pcalloc(p, inlen); memcpy(inbuf, text, inlen); inbuflen = inlen; outbuflen = sizeof(outbuf); if (utf8_convert(decode_conv, inbuf, &inbuflen, outbuf, &outbuflen) < 0) { pr_trace_msg(trace_channel, 1, "error decoding text: %s", strerror(errno)); if (pr_trace_get_level(trace_channel) >= 14) { /* Write out the text we tried (and failed) to decode, in hex. */ register unsigned int i; unsigned char *raw_text; size_t len, raw_len; len = strlen(text); raw_len = (len * 5) + 1; raw_text = pcalloc(p, raw_len + 1); for (i = 0; i < len; i++) { pr_snprintf((char *) (raw_text + (i * 5)), (raw_len - 1) - (i * 5), "0x%02x ", (unsigned char) text[i]); } pr_trace_msg(trace_channel, 14, "unable to decode text (raw bytes): %s", raw_text); } return (char *) text; } outlen = sizeof(outbuf) - outbuflen; res = pcalloc(p, outlen); memcpy(res, outbuf, outlen); return res; #else return pstrdup(p, text); #endif /* !PR_USE_NLS && !HAVE_ICONV_H */ } char *proxy_ssh_utf8_encode_text(pool *p, const char *text) { #if defined(PR_USE_NLS) && defined(HAVE_ICONV_H) size_t inlen, inbuflen, outlen, outbuflen; char *inbuf, outbuf[PR_TUNABLE_PATH_MAX*2], *res; if (p == NULL || text == NULL) { errno = EINVAL; return NULL; } if (encode_conv == (iconv_t) -1) { pr_trace_msg(trace_channel, 1, "encoding conversion handle is invalid, unable to encode UTF8 text"); return (char *) text; } inlen = strlen(text) + 1; inbuf = pcalloc(p, inlen); memcpy(inbuf, text, inlen); inbuflen = inlen; outbuflen = sizeof(outbuf); if (utf8_convert(encode_conv, inbuf, &inbuflen, outbuf, &outbuflen) < 0) { pr_trace_msg(trace_channel, 1, "error encoding text: %s", strerror(errno)); if (pr_trace_get_level(trace_channel) >= 14) { /* Write out the text we tried (and failed) to encode, in hex. */ register unsigned int i; unsigned char *raw_text; size_t len, raw_len; len = strlen(text); raw_len = (len * 5) + 1; raw_text = pcalloc(p, raw_len + 1); for (i = 0; i < len; i++) { pr_snprintf((char *) (raw_text + (i * 5)), (raw_len - 1) - (i * 5), "0x%02x ", (unsigned char) text[i]); } pr_trace_msg(trace_channel, 14, "unable to encode text (raw bytes): %s", raw_text); } return (char *) text; } outlen = sizeof(outbuf) - outbuflen; res = pcalloc(p, outlen); memcpy(res, outbuf, outlen); return res; #else return pstrdup(p, text); #endif /* !PR_USE_NLS && !HAVE_ICONV_H */ } #endif /* PR_USE_OPENSSL */ proftpd-mod_proxy-0.9.5/lib/proxy/str.c000066400000000000000000000034661475737016700201560ustar00rootroot00000000000000/* * ProFTPD - mod_proxy String implementation * Copyright (c) 2020 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "proxy/str.h" char *proxy_strnstr(const char *s1, const char *s2, size_t len) { #if defined(HAVE_STRNSTR) if (s1 == NULL || s2 == NULL || len == 0) { return NULL; } /* strnstr(3) does not check this, but it should. */ if (s2[0] == '\0') { return NULL; } return strnstr(s1, s2, len); #else register unsigned int i; size_t s2_len; if (s1 == NULL || s2 == NULL || len == 0) { return NULL; } s2_len = strlen(s2); if (s2_len == 0 || s2_len > len) { return NULL; } for (i = 0; i <= (unsigned int) (len - s2_len); i++) { if (s1[0] == s2[0] && strncmp(s1, s2, s2_len) == 0) { return (char *) s1; } s1++; } return NULL; #endif /* HAVE_STRNSTR */ } proftpd-mod_proxy-0.9.5/lib/proxy/tls.c000066400000000000000000004257371475737016700201610ustar00rootroot00000000000000/* * ProFTPD - mod_proxy TLS implementation * Copyright (c) 2015-2024 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "proxy/conn.h" #include "proxy/netio.h" #include "proxy/session.h" #include "proxy/tls.h" #include "proxy/tls/db.h" #include "proxy/tls/redis.h" /* Define if you have the LibreSSL library. */ #if defined(LIBRESSL_VERSION_NUMBER) # define HAVE_LIBRESSL 1 #endif static const char *trace_channel = "proxy.tls"; #if defined(PR_USE_OPENSSL) extern xaset_t *server_list; static unsigned long tls_opts = 0UL; static const char *tls_tables_path = NULL; static struct proxy_tls_datastore tls_ds; static int tls_engine = PROXY_TLS_ENGINE_AUTO; static int tls_need_data_prot = TRUE; static int tls_verify_server = TRUE; #if defined(PSK_MAX_PSK_LEN) static const char *tls_psk_name = NULL; static BIGNUM *tls_psk_bn = NULL; static int tls_psk_used = FALSE; # define PROXY_TLS_MIN_PSK_LEN 20 #endif /* PSK support */ /* The SSL_set_session_ticket_ext() API was not fixed until OpenSSL-1.0.2e. * Thus mod_proxy's caching/reuse of session tickets will not work properly * before that version. */ #if defined(SSL_CTRL_SET_TLSEXT_TICKET_KEYS) # define PROXY_TLS_USE_SESSION_TICKETS 1 #endif static const char *timing_channel = "timing"; #define PROXY_TLS_DEFAULT_CIPHER_SUITE "DEFAULT:!ADH:!EXPORT:!DES" #define PROXY_TLS_NEXT_PROTO "ftp" /* SSL record/buffer sizes */ #define PROXY_TLS_HANDSHAKE_WRITE_BUFFER_SIZE 1400 /* SSL adaptive buffer sizes/values */ #define PROXY_TLS_DATA_ADAPTIVE_WRITE_MIN_BUFFER_SIZE (4 * 1024) #define PROXY_TLS_DATA_ADAPTIVE_WRITE_MAX_BUFFER_SIZE (16 * 1024) #define PROXY_TLS_DATA_ADAPTIVE_WRITE_BOOST_THRESHOLD (1024 * 1024) #define PROXY_TLS_DATA_ADAPTIVE_WRITE_BOOST_INTERVAL_MS 1000 #if defined(SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) static int tls_ssl_opts = (SSL_OP_ALL|SSL_OP_NO_SSLv2|SSL_OP_SINGLE_DH_USE)^SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; #else /* OpenSSL-0.9.6 and earlier (yes, it appears people still have these versions * installed) does not define the DONT_INSERT_EMPTY_FRAGMENTS option. */ static int tls_ssl_opts = SSL_OP_ALL|SSL_OP_NO_SSLv2|SSL_OP_SINGLE_DH_USE; #endif static const char *tls_cipher_suite = NULL; #if OPENSSL_VERSION_NUMBER >= 0x10101000L && \ defined(TLS1_3_VERSION) static const char *tlsv13_cipher_suite = NULL; #endif /* TLS1_3_VERSION */ #define PROXY_TLS_VERIFY_DEPTH 9 /* ProxyTLSTimeoutHandshake */ static unsigned int handshake_timeout = 30; static int handshake_timer_id = -1; static int handshake_timed_out = FALSE; #define PROXY_TLS_SHUTDOWN_BIDIRECTIONAL 0x001 /* Stream notes */ #define PROXY_TLS_NETIO_NOTE "mod_proxy.SSL" #define PROXY_TLS_ADAPTIVE_BYTES_COUNT_KEY "mod_proxy.SSL.adaptive.bytes" #define PROXY_TLS_ADAPTIVE_BYTES_MS_KEY "mod_proxy.SSL.adaptive.ms" /* Session caching */ #define PROXY_TLS_MAX_SESSION_AGE 86400 #define PROXY_TLS_MAX_SESSION_COUNT 1000 static SSL_CTX *ssl_ctx = NULL; static pr_netio_t *tls_ctrl_netio = NULL; static pr_netio_t *tls_data_netio = NULL; static SSL *tls_ctrl_ssl = NULL; static int netio_install_ctrl(void); static int netio_install_data(void); /* Indices for data stashed in SSL objects */ #define PROXY_TLS_IDX_TICKET_KEY 2 #define PROXY_TLS_IDX_HAD_TICKET 3 #if !defined(OPENSSL_NO_TLSEXT) static void tls_tlsext_cb(SSL *, int, int, unsigned char *, int, void *); #endif /* !OPENSSL_NO_TLSEXT */ static int handshake_timeout_cb(CALLBACK_FRAME) { handshake_timed_out = TRUE; return 0; } const char *proxy_tls_get_errors(void) { unsigned int count = 0; unsigned long error_code; BIO *bio = NULL; char *data = NULL; long datalen; const char *error_data = NULL, *str = "(unknown)"; int error_flags = 0; /* Use ERR_print_errors() and a memory BIO to build up a string with * all of the error messages from the error queue. */ error_code = ERR_get_error_line_data(NULL, NULL, &error_data, &error_flags); if (error_code) { bio = BIO_new(BIO_s_mem()); } while (error_code) { pr_signals_handle(); if (error_flags & ERR_TXT_STRING) { BIO_printf(bio, "\n (%u) %s [%s]", ++count, ERR_error_string(error_code, NULL), error_data); } else { BIO_printf(bio, "\n (%u) %s", ++count, ERR_error_string(error_code, NULL)); } error_data = NULL; error_flags = 0; error_code = ERR_get_error_line_data(NULL, NULL, &error_data, &error_flags); } datalen = BIO_get_mem_data(bio, &data); if (data) { data[datalen] = '\0'; str = pstrdup(session.pool, data); } if (bio != NULL) { BIO_free(bio); } return str; } static char *tls_x509_name_oneline(X509_NAME *x509_name) { static char buf[1024] = {'\0'}; /* If we are using OpenSSL 0.9.6 or newer, we want to use * X509_NAME_print_ex() instead of X509_NAME_oneline(). */ #if OPENSSL_VERSION_NUMBER < 0x000906000L memset(&buf, '\0', sizeof(buf)); return X509_NAME_oneline(x509_name, buf, sizeof(buf)-1); #else /* Sigh...do it the hard way. */ BIO *mem = BIO_new(BIO_s_mem()); char *data = NULL; long datalen = 0; int ok; ok = X509_NAME_print_ex(mem, x509_name, 0, XN_FLAG_ONELINE); if (ok) { datalen = BIO_get_mem_data(mem, &data); if (data != NULL) { memset(&buf, '\0', sizeof(buf)); if ((size_t) datalen >= sizeof(buf)) { datalen = sizeof(buf)-1; } memcpy(buf, data, datalen); buf[datalen] = '\0'; buf[sizeof(buf)-1] = '\0'; BIO_free(mem); return buf; } } BIO_free(mem); return NULL; #endif /* OPENSSL_VERSION_NUMBER >= 0x000906000 */ } static char *tls_get_subj_name(SSL *ssl) { X509 *cert; cert = SSL_get_peer_certificate(ssl); if (cert != NULL) { char *subj_name; subj_name = tls_x509_name_oneline(X509_get_subject_name(cert)); X509_free(cert); return subj_name; } errno = ENOENT; return NULL; } static int tls_get_block(conn_t *conn) { int flags; flags = fcntl(conn->rfd, F_GETFL); if (flags & O_NONBLOCK) { return FALSE; } return TRUE; } static void tls_fatal(long error, int lineno) { switch (error) { case SSL_ERROR_NONE: return; case SSL_ERROR_SSL: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "panic: SSL_ERROR_SSL, line %d: %s", lineno, proxy_tls_get_errors()); break; case SSL_ERROR_WANT_READ: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "panic: SSL_ERROR_WANT_READ, line %d", lineno); break; case SSL_ERROR_WANT_WRITE: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "panic: SSL_ERROR_WANT_WRITE, line %d", lineno); break; case SSL_ERROR_WANT_X509_LOOKUP: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "panic: SSL_ERROR_WANT_X509_LOOKUP, line %d", lineno); break; case SSL_ERROR_SYSCALL: { long xerrcode = ERR_get_error(); if (errno == ECONNRESET) { pr_trace_msg(trace_channel, 17, "SSL_ERROR_SYSCALL error (errcode %ld) occurred on line %d; ignoring " "ECONNRESET (%s)", xerrcode, lineno, strerror(errno)); return; } /* Check to see if the OpenSSL error queue has info about this. */ if (xerrcode == 0) { /* The OpenSSL error queue doesn't have any more info, so we'll * examine the error value itself. */ if (errno == EOF) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "panic: SSL_ERROR_SYSCALL, line %d: EOF that violates protocol", lineno); } else { /* Check errno */ (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "panic: SSL_ERROR_SYSCALL, line %d: system error: %s", lineno, strerror(errno)); } } else { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "panic: SSL_ERROR_SYSCALL, line %d: %s", lineno, proxy_tls_get_errors()); } break; } case SSL_ERROR_ZERO_RETURN: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "panic: SSL_ERROR_ZERO_RETURN, line %d", lineno); break; case SSL_ERROR_WANT_CONNECT: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "panic: SSL_ERROR_WANT_CONNECT, line %d", lineno); break; default: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "panic: SSL_ERROR %ld, line %d", error, lineno); break; } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unexpected OpenSSL error, disconnecting"); pr_log_pri(PR_LOG_WARNING, "%s", MOD_PROXY_VERSION ": unexpected OpenSSL error, disconnecting"); pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION, NULL); } static void tls_end_sess(SSL *ssl, int strms, int flags) { int lineno = 0, res = 0; int shutdown_state; BIO *rbio, *wbio; int bread, bwritten; unsigned long rbio_rbytes, rbio_wbytes, wbio_rbytes, wbio_wbytes; if (ssl == NULL) { return; } rbio = SSL_get_rbio(ssl); rbio_rbytes = BIO_number_read(rbio); rbio_wbytes = BIO_number_written(rbio); wbio = SSL_get_wbio(ssl); wbio_rbytes = BIO_number_read(wbio); wbio_wbytes = BIO_number_written(wbio); /* A 'close_notify' alert (SSL shutdown message) may have been previously * sent to the server via netio_shutdown_cb(). */ shutdown_state = SSL_get_shutdown(ssl); if (!(shutdown_state & SSL_SENT_SHUTDOWN)) { errno = 0; /* 'close_notify' not already sent; send it now. */ pr_trace_msg(trace_channel, 17, "shutting down TLS session, 'close_notify' not already sent; " "sending now"); lineno = __LINE__ + 1; res = SSL_shutdown(ssl); } if (res == 0) { /* Now call SSL_shutdown() again, but only if necessary. */ if (flags & PROXY_TLS_SHUTDOWN_BIDIRECTIONAL) { shutdown_state = SSL_get_shutdown(ssl); res = 1; if (!(shutdown_state & SSL_RECEIVED_SHUTDOWN)) { errno = 0; pr_trace_msg(trace_channel, 17, "shutting down TLS session, 'close_notify' not received; try again"); lineno = __LINE__ + 1; res = SSL_shutdown(ssl); } } /* If SSL_shutdown() returned -1 here, an error occurred during the * shutdown. */ if (res < 0) { long err_code; err_code = SSL_get_error(ssl, res); switch (err_code) { case SSL_ERROR_WANT_READ: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "SSL_shutdown error: WANT_READ"); pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": SSL_shutdown error: WANT_READ"); break; case SSL_ERROR_WANT_WRITE: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "SSL_shutdown error: WANT_WRITE"); pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": SSL_shutdown error: WANT_WRITE"); break; case SSL_ERROR_ZERO_RETURN: /* Clean shutdown, nothing we need to do. */ break; case SSL_ERROR_SYSCALL: if (errno != 0 && errno != EOF && errno != EBADF && errno != EPIPE && errno != EPERM && errno != ENOSYS) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "SSL_shutdown syscall error: %s", strerror(errno)); } break; default: { const char *errors; errors = proxy_tls_get_errors(); (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "SSL_shutdown error [%ld]: %s", err_code, errors); pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": SSL_shutdown error [%ld]: %s", err_code, errors); break; } } } } else if (res < 0) { long err_code; err_code = SSL_get_error(ssl, res); switch (err_code) { case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: case SSL_ERROR_ZERO_RETURN: /* Clean shutdown, nothing we need to do. The WANT_READ/WANT_WRITE * error codes crept into OpenSSL 0.9.8m, with changes to make * SSL_shutdown() work properly for non-blocking sockets. And * handling these error codes for older OpenSSL versions won't break * things. */ break; case SSL_ERROR_SYSCALL: if (errno != 0 && errno != EOF && errno != EBADF && errno != EPIPE && errno != EPERM && errno != ENOSYS) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "SSL_shutdown syscall error: %s", strerror(errno)); } break; default: if (lineno == 0) { lineno = __LINE__; } tls_fatal(err_code, lineno); break; } } bread = (BIO_number_read(rbio) - rbio_rbytes) + (BIO_number_read(wbio) - wbio_rbytes); bwritten = (BIO_number_written(rbio) - rbio_wbytes) + (BIO_number_written(wbio) - wbio_wbytes); /* Manually update session.total_raw_in/out, in order to have %I/%O be * accurately represented for the raw traffic. */ if (bread > 0) { session.total_raw_in += bread; } if (bwritten > 0) { session.total_raw_out += bwritten; } SSL_free(ssl); if (res >= 0) { pr_trace_msg(trace_channel, 17, "TLS session cleanly shut down"); } } static int tls_readmore(int rfd) { fd_set rfds; struct timeval tv; FD_ZERO(&rfds); FD_SET(rfd, &rfds); /* Use a timeout of 15 seconds */ tv.tv_sec = 15; tv.tv_usec = 0; return select(rfd + 1, &rfds, NULL, NULL, &tv); } static int tls_writemore(int wfd) { fd_set wfds; struct timeval tv; FD_ZERO(&wfds); FD_SET(wfd, &wfds); /* Use a timeout of 15 seconds */ tv.tv_sec = 15; tv.tv_usec = 0; return select(wfd + 1, NULL, &wfds, NULL, &tv); } static ssize_t tls_read(SSL *ssl, void *buf, size_t len, int nstrm_type, pr_table_t *notes) { int lineno; ssize_t count; read_retry: pr_signals_handle(); lineno = __LINE__ + 1; count = SSL_read(ssl, buf, len); if (count < 0) { long err; int fd; err = SSL_get_error(ssl, count); fd = SSL_get_fd(ssl); /* read(2) returns only the generic error number -1 */ count = -1; switch (err) { case SSL_ERROR_WANT_READ: /* OpenSSL needs more data from the wire to finish the current block, * so we wait a little while for it. */ pr_trace_msg(trace_channel, 17, "WANT_READ encountered while reading SSL data on fd %d, " "waiting to read data", fd); err = tls_readmore(fd); if (err > 0) { goto read_retry; } else if (err == 0) { /* Still missing data after timeout. Simulate an EINTR and return. */ errno = EINTR; /* If err < 0, i.e. some error from the select(), everything is * already in place; errno is properly set and this function * returns -1. */ break; } case SSL_ERROR_WANT_WRITE: /* OpenSSL needs to write more data to the wire to finish the current * block, so we wait a little while for it. */ pr_trace_msg(trace_channel, 17, "WANT_WRITE encountered while writing SSL data on fd %d, " "waiting to send data", fd); err = tls_writemore(fd); if (err > 0) { goto read_retry; } else if (err == 0) { /* Still missing data after timeout. Simulate an EINTR and return. */ errno = EINTR; /* If err < 0, i.e. some error from the select(), everything is * already in place; errno is properly set and this function * returns -1. */ break; } case SSL_ERROR_ZERO_RETURN: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "read EOF from client during TLS"); break; default: tls_fatal(err, lineno); break; } } return count; } static ssize_t tls_write(SSL *ssl, const void *buf, size_t len, int nstrm_type, pr_table_t *notes) { ssize_t count; int lineno, xerrno = 0; /* Help ensure we have a clean/trustable errno value. */ errno = 0; lineno = __LINE__ + 1; count = SSL_write(ssl, buf, len); xerrno = errno; if (count < 0) { long err = SSL_get_error(ssl, count); /* write(2) returns only the generic error number -1 */ count = -1; switch (err) { case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: /* Simulate an EINTR in case OpenSSL wants to write more. */ xerrno = EINTR; break; default: tls_fatal(err, lineno); break; } } if (nstrm_type == PR_NETIO_STRM_DATA) { BIO *wbio; uint64_t *adaptive_bytes_written_ms = NULL, now; off_t *adaptive_bytes_written_count = NULL; const void *v; v = pr_table_get(notes, PROXY_TLS_ADAPTIVE_BYTES_COUNT_KEY, NULL); if (v != NULL) { adaptive_bytes_written_count = (off_t *) v; } v = pr_table_get(notes, PROXY_TLS_ADAPTIVE_BYTES_MS_KEY, NULL); if (v != NULL) { adaptive_bytes_written_ms = (uint64_t *) v; } (void) pr_gettimeofday_millis(&now); wbio = SSL_get_wbio(ssl); if (adaptive_bytes_written_count != NULL) { (*adaptive_bytes_written_count) += count; if (*adaptive_bytes_written_count >= PROXY_TLS_DATA_ADAPTIVE_WRITE_BOOST_THRESHOLD) { /* Boost the buffer size if we've written more than the "boost" * threshold. */ (void) BIO_set_write_buf_size(wbio, PROXY_TLS_DATA_ADAPTIVE_WRITE_MAX_BUFFER_SIZE); } if (now > (*adaptive_bytes_written_ms + PROXY_TLS_DATA_ADAPTIVE_WRITE_BOOST_INTERVAL_MS)) { /* If it's been longer than the boost interval since our last write, * then reset the buffer size to the smaller version, assuming * congestion (and thus closing of the TCP congestion window). */ (void) BIO_set_write_buf_size(wbio, PROXY_TLS_DATA_ADAPTIVE_WRITE_MIN_BUFFER_SIZE); *adaptive_bytes_written_count = 0; } *adaptive_bytes_written_ms = now; } } errno = xerrno; return count; } static const char *get_printable_san(pool *p, const char *data, size_t datalen) { register unsigned int i; char *ptr, *res; size_t reslen = 0; /* First, calculate the length of the resulting printable string we'll * be generating. */ for (i = 0; i < datalen; i++) { if (PR_ISPRINT(data[i])) { reslen++; } else { reslen += 4; } } /* Leave one space in the allocated string for the terminating NUL. */ ptr = res = pcalloc(p, reslen + 1); for (i = 0; i < datalen; i++) { if (PR_ISPRINT(data[i])) { *(ptr++) = data[i]; } else { snprintf(ptr, reslen - (ptr - res), "\\x%02x", data[i]); ptr += 4; } } return res; } static int cert_match_wildcard(pool *p, const char *host_name, const char *cert_name, size_t cert_namelen) { register unsigned int i; char *ptr, *ptr2; unsigned int host_label_count = 1, cert_label_count = 1; int res; size_t host_namelen; /* To be a valid wildcard pattern, we need a '*', a '.', and a top-level * domain. Thus if the length is less than 4 characters, it CANNOT be * a wildcard match. */ if (cert_namelen < 4) { return FALSE; } /* If no '.', FALSE. */ ptr = strchr(cert_name, '.'); if (ptr == NULL) { return FALSE; } /* If no '*', FALSE. */ ptr2 = strchr(cert_name, '*'); if (ptr2 == NULL) { return FALSE; } /* If more than one '*', FALSE. */ if (strchr(ptr2 + 1, '*') != NULL) { pr_trace_msg(trace_channel, 17, "multiple '*' characters found in '%s', unable to use for wildcard " "matching", cert_name); return FALSE; } /* If not in leftmost label, FALSE. */ if (ptr2 > ptr) { pr_trace_msg(trace_channel, 17, "wildcard character in '%s' is NOT in the leftmost label", cert_name); return FALSE; } /* If not the same number of labels, FALSE */ host_namelen = strlen(host_name); for (i = 0; i < host_namelen; i++) { if (host_name[i] == '.') { host_label_count++; } } for (i = 0; i < cert_namelen; i++) { if (cert_name[i] == '.') { cert_label_count++; } } if (host_label_count != cert_label_count) { pr_trace_msg(trace_channel, 17, "cert name '%s' label count (%d) does not match host name '%s' " "label count (%d)", cert_name, cert_label_count, host_name, host_label_count); return FALSE; } res = pr_fnmatch(cert_name, host_name, PR_FNM_NOESCAPE); if (res == 0) { return TRUE; } pr_trace_msg(trace_channel, 17, "certificate name with wildcard '%s' did not match host name '%s'", cert_name, host_name); return FALSE; } static int cert_match_dns_san(pool *p, X509 *cert, const char *dns_name, int allow_wildcards) { int matched = FALSE; #if OPENSSL_VERSION_NUMBER > 0x000907000L STACK_OF(GENERAL_NAME) *sans; sans = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); if (sans != NULL) { register int i; int nsans = sk_GENERAL_NAME_num(sans); for (i = 0; i < nsans; i++) { GENERAL_NAME *alt_name = sk_GENERAL_NAME_value(sans, i); if (alt_name->type == GEN_DNS) { char *dns_san; size_t dns_sanlen; dns_san = (char *) ASN1_STRING_data(alt_name->d.ia5); dns_sanlen = strlen(dns_san); /* Check for subjectAltName values which contain embedded NULs. * This can cause verification problems (spoofing), e.g. if the * string is "www.goodguy.com\0www.badguy.com"; the use of strcmp() * only checks "www.goodguy.com". */ if ((size_t) ASN1_STRING_length(alt_name->d.ia5) != dns_sanlen) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "cert dNSName SAN contains embedded NULs, " "rejecting as possible spoof attempt"); (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "suspicious dNSName SAN value: '%s'", get_printable_san(p, dns_san, ASN1_STRING_length(alt_name->d.dNSName))); GENERAL_NAME_free(alt_name); sk_GENERAL_NAME_free(sans); return 0; } if (strncasecmp(dns_name, dns_san, dns_sanlen + 1) == 0) { pr_trace_msg(trace_channel, 8, "found cert dNSName SAN matching '%s'", dns_name); matched = TRUE; } else { if (allow_wildcards) { matched = cert_match_wildcard(p, dns_name, dns_san, dns_sanlen); } } if (matched == FALSE) { pr_trace_msg(trace_channel, 9, "cert dNSName SAN '%s' did not match '%s'", dns_san, dns_name); } } GENERAL_NAME_free(alt_name); if (matched == TRUE) { break; } } sk_GENERAL_NAME_free(sans); } #endif /* OpenSSL-0.9.7 or later */ return matched; } static int cert_match_ip_san(pool *p, X509 *cert, const char *ipstr) { int matched = FALSE; STACK_OF(GENERAL_NAME) *sans; sans = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); if (sans != NULL) { register int i; int nsans = sk_GENERAL_NAME_num(sans); for (i = 0; i < nsans; i++) { GENERAL_NAME *alt_name = sk_GENERAL_NAME_value(sans, i); if (alt_name->type == GEN_IPADD) { unsigned char *san_data = NULL; int have_ipstr = FALSE, san_datalen; #if defined(PR_USE_IPV6) char san_ipstr[INET6_ADDRSTRLEN + 1] = {'\0'}; #else char san_ipstr[INET_ADDRSTRLEN + 1] = {'\0'}; #endif /* PR_USE_IPV6 */ san_data = ASN1_STRING_data(alt_name->d.ip); memset(san_ipstr, '\0', sizeof(san_ipstr)); san_datalen = ASN1_STRING_length(alt_name->d.ip); if (san_datalen == 4) { /* IPv4 address */ snprintf(san_ipstr, sizeof(san_ipstr)-1, "%u.%u.%u.%u", san_data[0], san_data[1], san_data[2], san_data[3]); have_ipstr = TRUE; #if defined(PR_USE_IPV6) } else if (san_datalen == 16) { /* IPv6 address */ if (pr_inet_ntop(AF_INET6, san_data, san_ipstr, sizeof(san_ipstr)-1) == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to convert cert iPAddress SAN value (length %d) " "to IPv6 representation: %s", san_datalen, strerror(errno)); } else { have_ipstr = TRUE; } #endif /* PR_USE_IPV6 */ } else { pr_trace_msg(trace_channel, 3, "unsupported cert SAN ipAddress length (%d), ignoring", san_datalen); continue; } if (have_ipstr) { size_t san_ipstrlen; san_ipstrlen = strlen(san_ipstr); if (strncmp(ipstr, san_ipstr, san_ipstrlen + 1) == 0) { pr_trace_msg(trace_channel, 8, "found cert iPAddress SAN matching '%s'", ipstr); matched = TRUE; } else { if (san_datalen == 16) { /* We need to handle the case where the iPAddress SAN might * have contained an IPv4-mapped IPv6 adress, and we're * comparing against an IPv4 address. */ if (san_ipstrlen > 7 && strncasecmp(san_ipstr, "::ffff:", 7) == 0) { if (strncmp(ipstr, san_ipstr + 7, san_ipstrlen - 6) == 0) { pr_trace_msg(trace_channel, 8, "found cert iPAddress SAN '%s' matching '%s'", san_ipstr, ipstr); matched = TRUE; } } } else { pr_trace_msg(trace_channel, 9, "cert iPAddress SAN '%s' did not match '%s'", san_ipstr, ipstr); } } } } GENERAL_NAME_free(alt_name); if (matched == TRUE) { break; } } sk_GENERAL_NAME_free(sans); } return matched; } static int cert_match_cn(pool *p, X509 *cert, const char *name, int allow_wildcards) { int matched = FALSE, idx = -1; X509_NAME *subj_name = NULL; X509_NAME_ENTRY *cn_entry = NULL; ASN1_STRING *cn_asn1 = NULL; char *cn_str = NULL; size_t cn_len = 0; /* Find the position of the CommonName (CN) field within the Subject of * the cert. */ subj_name = X509_get_subject_name(cert); if (subj_name == NULL) { pr_trace_msg(trace_channel, 12, "unable to check certificate CommonName against '%s': " "unable to get Subject", name); return 0; } idx = X509_NAME_get_index_by_NID(subj_name, NID_commonName, -1); if (idx < 0) { pr_trace_msg(trace_channel, 12, "unable to check certificate CommonName against '%s': " "no CommonName attribute found", name); return 0; } cn_entry = X509_NAME_get_entry(subj_name, idx); if (cn_entry == NULL) { pr_trace_msg(trace_channel, 12, "unable to check certificate CommonName against '%s': " "error obtaining CommonName attribute found: %s", name, proxy_tls_get_errors()); return 0; } /* Convert the CN field to a string, by way of an ASN1 object. */ cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry); if (cn_asn1 == NULL) { pr_trace_msg(trace_channel, 12, "unable to check certificate CommonName against '%s': " "error converting CommonName attribute to ASN.1: %s", name, proxy_tls_get_errors()); return 0; } cn_str = (char *) ASN1_STRING_data(cn_asn1); /* Check for CommonName values which contain embedded NULs. This can cause * verification problems (spoofing), e.g. if the string is * "www.goodguy.com\0www.badguy.com"; the use of strcmp() only checks * "www.goodguy.com". */ cn_len = strlen(cn_str); if ((size_t) ASN1_STRING_length(cn_asn1) != cn_len) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "cert CommonName contains embedded NULs, rejecting as possible spoof " "attempt"); (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "suspicious CommonName value: '%s'", get_printable_san(p, (const char *) cn_str, ASN1_STRING_length(cn_asn1))); return 0; } /* Yes, this is deliberately a case-insensitive comparison. Most CNs * contain a hostname (case-insensitive); if they contain an IP address, * the case-insensitivity won't hurt anything. In fact, it's needed for * e.g. IPv6 addresses. */ if (strncasecmp(name, cn_str, cn_len + 1) == 0) { pr_trace_msg(trace_channel, 12, "cert CommonName '%s' matches '%s'", cn_str, name); matched = TRUE; } else { if (allow_wildcards) { matched = cert_match_wildcard(p, name, cn_str, cn_len); } } if (matched == FALSE) { pr_trace_msg(trace_channel, 12, "cert CommonName '%s' does NOT match '%s'", cn_str, name); } return matched; } static int check_server_cert(SSL *ssl, conn_t *conn, const char *host_name) { X509 *cert = NULL; int ok = -1; long verify_result; /* Only perform these more stringent checks if asked to verify servers. */ if (tls_verify_server == FALSE) { return 0; } /* Check SSL_get_verify_result. */ verify_result = SSL_get_verify_result(ssl); if (verify_result != X509_V_OK) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to verify '%s' server certificate: %s", conn->remote_name, X509_verify_cert_error_string(verify_result)); return -1; } cert = SSL_get_peer_certificate(ssl); if (cert == NULL) { /* This can be null in the case where some anonymous (insecure) * cipher suite was used. */ (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to verify '%s': server did not provide certificate", conn->remote_name); # if defined(PSK_MAX_PSK_LEN) if (tls_psk_used) { return 0; } # endif /* PSK support */ return -1; } /* XXX If using OpenSSL-1.0.2/1.1.0, we might be able to use: * X509_match_host() and X509_match_ip()/X509_match_ip_asc(). */ ok = cert_match_ip_san(conn->pool, cert, pr_netaddr_get_ipstr(conn->remote_addr)); if (ok == 0) { ok = cert_match_cn(conn->pool, cert, pr_netaddr_get_ipstr(conn->remote_addr), FALSE); } if (ok == 0) { ok = cert_match_dns_san(conn->pool, cert, host_name, TRUE); if (ok == 0) { ok = cert_match_cn(conn->pool, cert, host_name, TRUE); } } X509_free(cert); return ok; } static void stash_stream_ssl(pr_netio_stream_t *nstrm, SSL *ssl) { if (pr_table_add(nstrm->notes, pstrdup(nstrm->strm_pool, PROXY_TLS_NETIO_NOTE), ssl, sizeof(SSL *)) < 0) { if (errno != EEXIST) { pr_trace_msg(trace_channel, 4, "error stashing '%s' note on %s %s stream: %s", PROXY_TLS_NETIO_NOTE, nstrm->strm_type == PR_NETIO_STRM_CTRL ? "control" : "data", nstrm->strm_mode == PR_NETIO_IO_RD ? "read" : "write", strerror(errno)); } } } static int tls_verify_cb(int ok, X509_STORE_CTX *ctx) { X509 *cert; cert = X509_STORE_CTX_get_current_cert(ctx); if (!ok) { int verify_depth, verify_error; verify_depth = X509_STORE_CTX_get_error_depth(ctx); (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error: unable to verify server certificate at depth %d", verify_depth); (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error: cert subject: %s", tls_x509_name_oneline(X509_get_subject_name(cert))); (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error: cert issuer: %s", tls_x509_name_oneline(X509_get_issuer_name(cert))); /* Catch a too long certificate chain here. */ if (verify_depth > PROXY_TLS_VERIFY_DEPTH) { X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_CHAIN_TOO_LONG); } #if OPENSSL_VERSION_NUMBER >= 0x10100000L verify_error = X509_STORE_CTX_get_error(ctx); #else verify_error = ctx->error; #endif /* OpenSSL-1.1.x and later */ switch (verify_error) { case X509_V_ERR_CERT_CHAIN_TOO_LONG: case X509_V_ERR_CERT_NOT_YET_VALID: case X509_V_ERR_CERT_HAS_EXPIRED: case X509_V_ERR_CERT_REVOKED: case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE: case X509_V_ERR_APPLICATION_VERIFICATION: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "server certificate failed verification: %s", X509_verify_cert_error_string(verify_error)); ok = 0; break; case X509_V_ERR_INVALID_PURPOSE: { register int i; int count = X509_PURPOSE_get_count(); (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "server certificate failed verification: %s", X509_verify_cert_error_string(verify_error)); for (i = 0; i < count; i++) { X509_PURPOSE *purp = X509_PURPOSE_get0(i); (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, " purpose #%d: %s", i+1, X509_PURPOSE_get0_name(purp)); } ok = 0; break; } default: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error verifying server certificate: [%d] %s", verify_error, X509_verify_cert_error_string(verify_error)); ok = 0; break; } if (tls_verify_server == FALSE) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "ProxyTLSVerifyServer off, ignoring failed certificate verification"); ok = 1; } } else { if (tls_opts & PROXY_TLS_OPT_ENABLE_DIAGS) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.verify]: cert subject: %s", tls_x509_name_oneline(X509_get_subject_name(cert))); (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.verify]: cert issuer: %s", tls_x509_name_oneline(X509_get_issuer_name(cert))); } } return ok; } static int tls_get_cached_sess(pool *p, SSL *ssl, const char *host, int port) { char port_str[32], *sess_key = NULL; SSL_SESSION *sess = NULL; if (tls_opts & PROXY_TLS_OPT_NO_SESSION_CACHE) { if (tls_opts & PROXY_TLS_OPT_NO_SESSION_TICKETS) { pr_trace_msg(trace_channel, 19, "NoSessionCache and NoSessionTickets ProxyTLSOptions in effect, " "not using cached SSL sessions"); SSL_set_options(ssl, SSL_OP_NO_TICKET); return 0; } } memset(port_str, '\0', sizeof(port_str)); snprintf(port_str, sizeof(port_str)-1, "%d", port); sess_key = pstrcat(p, "ftp://", host, ":", port_str, NULL); pr_trace_msg(trace_channel, 19, "looking for cached SSL session using key '%s'", sess_key); sess = (tls_ds.get_sess)(p, tls_ds.dsh, sess_key); if (sess != NULL) { long sess_age; time_t now; now = time(NULL); sess_age = now - SSL_SESSION_get_time(sess); if (sess_age >= PROXY_TLS_MAX_SESSION_AGE) { pr_trace_msg(trace_channel, 9, "cached SSL session expired, removing"); (void) (tls_ds.remove_sess)(p, tls_ds.dsh, sess_key); SSL_SESSION_free(sess); errno = ENOENT; sess = NULL; } } if (sess == NULL) { if (errno == ENOENT) { pr_trace_msg(trace_channel, 19, "no cached sessions found for key '%s'", sess_key); } else { pr_trace_msg(trace_channel, 9, "error getting cached session using key '%s': %s", sess_key, strerror(errno)); } return 0; } pr_trace_msg(trace_channel, 12, "found cached SSL session using key '%s'", sess_key); SSL_set_session(ssl, sess); SSL_SESSION_free(sess); return 0; } static int tls_add_cached_sess(pool *p, SSL *ssl, const char *host, int port) { char port_str[32], *sess_key = NULL; SSL_SESSION *sess = NULL; int res, sess_count, xerrno = 0; time_t now, sess_age; if (tls_opts & PROXY_TLS_OPT_NO_SESSION_CACHE) { if (tls_opts & PROXY_TLS_OPT_NO_SESSION_TICKETS) { pr_trace_msg(trace_channel, 19, "NoSessionCache and NoSessionTickets ProxyTLSOptions in effect, " "not caching SSL sessions"); return 0; } } sess_count = (tls_ds.count_sess)(p, tls_ds.dsh); if (sess_count < 0) { return -1; } if (sess_count >= PROXY_TLS_MAX_SESSION_COUNT) { pr_trace_msg(trace_channel, 14, "Maximum number of cached sessions (%d) reached, not caching SSL session", PROXY_TLS_MAX_SESSION_COUNT); return 0; } sess = SSL_get1_session(ssl); /* If this session is already past our expiration policy, ignore it. */ now = time(NULL); sess_age = now - SSL_SESSION_get_time(sess); if (sess_age >= PROXY_TLS_MAX_SESSION_AGE) { pr_trace_msg(trace_channel, 9, "SSL session has already expired, not caching"); SSL_SESSION_free(sess); return 0; } memset(port_str, '\0', sizeof(port_str)); snprintf(port_str, sizeof(port_str)-1, "%d", port); sess_key = pstrcat(p, "ftp://", host, ":", port_str, NULL); pr_trace_msg(trace_channel, 19, "caching SSL session using key '%s'", sess_key); res = (tls_ds.add_sess)(p, tls_ds.dsh, sess_key, sess); xerrno = errno; SSL_SESSION_free(sess); if (res < 0) { pr_trace_msg(trace_channel, 9, "error storing cached SSL session using key '%s': %s", sess_key, strerror(xerrno)); } else { pr_trace_msg(trace_channel, 19, "successfully cached SSL session using key '%s'", sess_key); } return 0; } static int tls_connect(conn_t *conn, const char *host_name, pr_netio_stream_t *nstrm) { int blocking, res = 0, xerrno = 0; char *subj = NULL; SSL *ssl = NULL; BIO *rbio = NULL, *wbio = NULL; if (ssl_ctx == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to start session: null SSL_CTX"); errno = EPERM; return -1; } ssl = SSL_new(ssl_ctx); if (ssl == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error: unable to allocate SSL session: %s", proxy_tls_get_errors()); return -2; } SSL_set_verify(ssl, SSL_VERIFY_PEER, tls_verify_cb); /* This works with either rfd or wfd (I hope). */ rbio = BIO_new_socket(conn->rfd, FALSE); wbio = BIO_new_socket(conn->rfd, FALSE); SSL_set_bio(ssl, rbio, wbio); #if !defined(OPENSSL_NO_TLSEXT) SSL_set_tlsext_debug_callback(ssl, tls_tlsext_cb); /* We should be a well-behaved TLS client, and NOT send an SNI value * that is an IP address. Per RFC 6066, literal IPv4/IPv6 addresses are NOT * permitted in the SNI. */ if (pr_netaddr_is_v4(host_name) != TRUE && pr_netaddr_is_v6(host_name) != TRUE) { pr_trace_msg(trace_channel, 9, "sending SNI '%s'", host_name); SSL_set_tlsext_host_name(ssl, host_name); } else { pr_trace_msg(trace_channel, 9, "skipping sending of IP address SNI '%s'", host_name); } # if defined(TLSEXT_STATUSTYPE_ocsp) pr_trace_msg(trace_channel, 9, "requesting stapled OCSP response"); SSL_set_tlsext_status_type(ssl, TLSEXT_STATUSTYPE_ocsp); # endif /* OCSP support */ #endif /* OPENSSL_NO_TLSEXT */ if (nstrm->strm_type == PR_NETIO_STRM_DATA) { /* If we're opening a data connection, reuse the SSL data from the * session on the control connection (including any session IDs and/or * tickets). */ SSL_copy_session_id(ssl, tls_ctrl_ssl); } else if (nstrm->strm_type == PR_NETIO_STRM_CTRL) { tls_get_cached_sess(nstrm->strm_pool, ssl, host_name, conn->remote_port); # if !defined(PROXY_TLS_USE_SESSION_TICKETS) SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_TICKET); # endif /* Session ticket support */ } /* If configured, set a timer for the handshake. */ if (handshake_timeout) { handshake_timer_id = pr_timer_add(handshake_timeout, -1, &proxy_module, handshake_timeout_cb, "SSL/TLS handshake"); } /* Make sure that TCP_NODELAY is enabled for the handshake. */ (void) pr_inet_set_proto_nodelay(conn->pool, conn, 1); /* Make sure that TCP_CORK (aka TCP_NOPUSH) is DISABLED for the handshake. */ if (pr_inet_set_proto_cork(conn->wfd, 0) < 0) { pr_trace_msg(trace_channel, 9, "error disabling TCP_CORK on %s conn: %s", nstrm->strm_type == PR_NETIO_STRM_CTRL ? "control" : "data", strerror(errno)); } connect_retry: blocking = tls_get_block(conn); if (blocking) { /* Put the connection in non-blocking mode for the duration of the * SSL handshake. This lets us handle EAGAIN/retries better (i.e. * without spinning in a tight loop and consuming the CPU). */ if (pr_inet_set_nonblock(conn->pool, conn) < 0) { pr_trace_msg(trace_channel, 3, "error making connection nonblocking: %s", strerror(errno)); } } pr_signals_handle(); res = SSL_connect(ssl); if (res == -1) { xerrno = errno; } if (blocking) { /* Return the connection to blocking mode. */ if (pr_inet_set_block(conn->pool, conn) < 0) { pr_trace_msg(trace_channel, 3, "error making connection blocking: %s", strerror(errno)); } } if (res < 1) { const char *msg = "unable to connect using TLS"; int errcode, shutdown_ssl; errcode = SSL_get_error(ssl, res); shutdown_ssl = TRUE; pr_signals_handle(); if (handshake_timed_out) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "TLS negotiation timed out (%u seconds)", handshake_timeout); tls_end_sess(ssl, nstrm->strm_type, 0); return -4; } switch (errcode) { case SSL_ERROR_WANT_READ: pr_trace_msg(trace_channel, 17, "WANT_READ encountered while connecting on fd %d, " "waiting to read data", conn->rfd); tls_readmore(conn->rfd); goto connect_retry; case SSL_ERROR_WANT_WRITE: pr_trace_msg(trace_channel, 17, "WANT_WRITE encountered while connecting on fd %d, " "waiting to read data", conn->rfd); tls_writemore(conn->rfd); goto connect_retry; case SSL_ERROR_ZERO_RETURN: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "%s: TLS connection closed", msg); break; case SSL_ERROR_WANT_X509_LOOKUP: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "%s: needs X509 lookup", msg); break; case SSL_ERROR_SYSCALL: { /* Check to see if the OpenSSL error queue has info about this. */ int xerrcode = ERR_get_error(); if (xerrcode == 0) { /* The OpenSSL error queue doesn't have any more info, so we'll * examine the SSL_connect() return value itself. */ if (res == 0) { /* EOF */ (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "%s: received EOF that violates protocol", msg); } else if (res == -1) { /* Check errno */ (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "%s: system call error: [%d] %s", msg, xerrno, strerror(xerrno)); } } else { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "%s: system call error: %s", msg, proxy_tls_get_errors()); } shutdown_ssl = FALSE; break; } case SSL_ERROR_SSL: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "%s: protocol error: %s", msg, proxy_tls_get_errors()); shutdown_ssl = FALSE; break; } if (nstrm->strm_type == PR_NETIO_STRM_CTRL) { pr_event_generate("mod_proxy.tls-ctrl-handshake-failed", &errcode); } else if (nstrm->strm_type == PR_NETIO_STRM_DATA) { pr_event_generate("mod_proxy.tls-data-handshake-failed", &errcode); } if (shutdown_ssl == TRUE) { tls_end_sess(ssl, nstrm->strm_type, 0); } return -3; } /* Disable the handshake timer. */ pr_timer_remove(handshake_timer_id, &proxy_module); /* Disable TCP_NODELAY, now that the handshake is done. */ (void) pr_inet_set_proto_nodelay(conn->pool, conn, 0); if (nstrm->strm_type == PR_NETIO_STRM_DATA) { /* Reenable TCP_CORK (aka TCP_NOPUSH), now that the handshake is done. */ if (pr_inet_set_proto_cork(conn->wfd, 1) < 0) { pr_trace_msg(trace_channel, 9, "error re-enabling TCP_CORK on data conn: %s", strerror(errno)); } } else if (nstrm->strm_type == PR_NETIO_STRM_CTRL) { int reused; /* Only try to cache the new SSL session if we actually did create a * new session. Otherwise, leave the previously cached session as is. */ reused = SSL_session_reused(ssl); if (reused == 0) { tls_add_cached_sess(nstrm->strm_pool, ssl, host_name, conn->remote_port); } } /* Manually update the raw bytes counters with the network IO from the * SSL handshake. */ session.total_raw_in += (BIO_number_read(rbio) + BIO_number_read(wbio)); session.total_raw_out += (BIO_number_written(rbio) + BIO_number_written(wbio)); /* Stash the SSL pointer in BOTH input and output streams for this * connection. */ stash_stream_ssl(conn->instrm, ssl); stash_stream_ssl(conn->outstrm, ssl); if (nstrm->strm_type == PR_NETIO_STRM_DATA) { pr_buffer_t *strm_buf; /* Clear any data from the NetIO stream buffers which may have been read * in before the SSL/TLS handshake occurred (Bug#3624). */ strm_buf = nstrm->strm_buf; if (strm_buf != NULL) { strm_buf->current = NULL; strm_buf->remaining = strm_buf->buflen; } } else { /* Stash a pointer to the control connection SSL object. */ tls_ctrl_ssl = ssl; } subj = tls_get_subj_name(ssl); if (subj != NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "Server: %s", subj); } if (check_server_cert(ssl, conn, host_name) < 0) { tls_end_sess(ssl, nstrm->strm_type, 0); return -1; } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "%s connection created, using cipher %s (%d bits)", SSL_get_version(ssl), SSL_get_cipher_name(ssl), SSL_get_cipher_bits(ssl, NULL)); return 0; } static int tls_seed_prng(void) { char *heapdata, stackdata[1024]; FILE *fp = NULL; pid_t pid; struct timeval tv; #if OPENSSL_VERSION_NUMBER >= 0x00905100L if (RAND_status() == 1) { /* PRNG already well-seeded. */ return 0; } #endif pr_log_debug(DEBUG9, MOD_PROXY_VERSION ": PRNG not seeded with enough data, looking for entropy sources"); /* If the device '/dev/urandom' is present, OpenSSL uses it by default. * Check if it's present, else we have to make random data ourselves. */ fp = fopen("/dev/urandom", "r"); if (fp != NULL) { fclose(fp); pr_log_debug(DEBUG9, MOD_PROXY_VERSION ": device /dev/urandom is present, assuming OpenSSL will use that " "for PRNG data"); return 0; } /* Not enough entropy; trying providing some. */ gettimeofday(&tv, NULL); RAND_seed(&(tv.tv_sec), sizeof(tv.tv_sec)); RAND_seed(&(tv.tv_usec), sizeof(tv.tv_usec)); pid = getpid(); RAND_seed(&pid, sizeof(pid_t)); RAND_seed(stackdata, sizeof(stackdata)); heapdata = malloc(sizeof(stackdata)); if (heapdata != NULL) { RAND_seed(heapdata, sizeof(stackdata)); free(heapdata); } #if OPENSSL_VERSION_NUMBER >= 0x00905100L if (RAND_status() == 0) { /* PRNG still badly seeded. */ errno = EPERM; return -1; } #endif return 0; } /* NetIO callbacks */ static void netio_abort_cb(pr_netio_stream_t *nstrm) { nstrm->strm_flags |= PR_NETIO_SESS_ABORT; } static int netio_close_cb(pr_netio_stream_t *nstrm) { int res = 0; SSL *ssl = NULL; ssl = (SSL *) pr_table_get(nstrm->notes, PROXY_TLS_NETIO_NOTE, NULL); if (ssl != NULL) { if (nstrm->strm_type == PR_NETIO_STRM_CTRL && nstrm->strm_mode == PR_NETIO_IO_WR) { const struct proxy_session *proxy_sess; const char *host_name; int remote_port; proxy_sess = pr_table_get(session.notes, "mod_proxy.proxy-session", NULL); if (proxy_sess == NULL) { /* Unlikely to occur. */ pr_trace_msg(trace_channel, 1, "missing proxy session unexpectedly"); errno = EINVAL; return -1; } host_name = proxy_conn_get_host(proxy_sess->dst_pconn); remote_port = proxy_conn_get_port(proxy_sess->dst_pconn); /* Cache the SSL session here, as it may have changed (e.g. due to * renegotiations) during the lifetime of the control connection. */ tls_add_cached_sess(nstrm->strm_pool, ssl, host_name, remote_port); pr_table_remove(nstrm->notes, PROXY_TLS_NETIO_NOTE, NULL); tls_end_sess(ssl, nstrm->strm_type, 0); proxy_sess_state &= ~PROXY_SESS_STATE_BACKEND_HAS_CTRL_TLS; } if (nstrm->strm_type == PR_NETIO_STRM_DATA && nstrm->strm_mode == PR_NETIO_IO_WR) { pr_table_remove(nstrm->notes, PROXY_TLS_NETIO_NOTE, NULL); } } res = close(nstrm->strm_fd); nstrm->strm_fd = -1; return res; } static pr_netio_stream_t *netio_open_cb(pr_netio_stream_t *nstrm, int fd, int mode) { nstrm->strm_fd = fd; nstrm->strm_mode = mode; return nstrm; } static int netio_poll_cb(pr_netio_stream_t *nstrm) { fd_set rfds, wfds; struct timeval tval; FD_ZERO(&rfds); FD_ZERO(&wfds); if (nstrm->strm_mode == PR_NETIO_IO_RD) { FD_SET(nstrm->strm_fd, &rfds); } else { FD_SET(nstrm->strm_fd, &wfds); } tval.tv_sec = (nstrm->strm_flags & PR_NETIO_SESS_INTR) ? nstrm->strm_interval : 10; tval.tv_usec = 0; return select(nstrm->strm_fd + 1, &rfds, &wfds, NULL, &tval); } static int netio_postopen_cb(pr_netio_stream_t *nstrm) { /* If this stream is for writing, and TLS is wanted/required, then perform * a TLS handshake. */ if (tls_engine == PROXY_TLS_ENGINE_OFF) { return 0; } if (nstrm->strm_mode == PR_NETIO_IO_WR) { const struct proxy_session *proxy_sess; uint64_t *adaptive_ms = NULL, start_ms; off_t *adaptive_bytes = NULL; conn_t *conn = NULL; const char *host_name; if (nstrm->strm_type == PR_NETIO_STRM_DATA) { if (tls_need_data_prot == FALSE) { pr_trace_msg(trace_channel, 17, "data connection does not require SSL/TLS, skipping handshake"); return 0; } /* This callback may be invoked by `pr_netio_postopen` for a frontend * data conn, as when we already have a backend data conn using TLS * already established. * * This happens when the frontend is NOT using TLS, and is using passive * data transfers, but the backed IS using TLS, and is using active * data transfers. * * NOTE: This is still a bug; mod_proxy is calling pr_netio_postopen(), * which should not be invoking this callback. */ if (proxy_sess_state & PROXY_SESS_STATE_BACKEND_HAS_DATA_TLS) { pr_trace_msg(trace_channel, 27, "data connection already using SSL/TLS, skipping handshake"); return 0; } } proxy_sess = pr_table_get(session.notes, "mod_proxy.proxy-session", NULL); if (proxy_sess == NULL) { /* Unlikely to occur. */ pr_trace_msg(trace_channel, 1, "missing proxy session unexpectedly"); errno = EINVAL; return -1; } /* TODO: Handle SSCN_MODE CLIENT (connect), SERVER (accept) */ pr_gettimeofday_millis(&start_ms); (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "starting TLS negotiation on %s connection", nstrm->strm_type == PR_NETIO_STRM_CTRL ? "control" : "data"); if (nstrm->strm_type == PR_NETIO_STRM_CTRL) { conn = proxy_sess->backend_ctrl_conn; } else { conn = proxy_sess->backend_data_conn; } host_name = proxy_conn_get_host(proxy_sess->dst_pconn); if (tls_connect(conn, host_name, nstrm) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to open %s connection to %s: TLS negotiation failed", nstrm->strm_type == PR_NETIO_STRM_CTRL ? "control" : "data", host_name); errno = EPERM; return -1; } if (pr_trace_get_level(timing_channel) >= 4) { unsigned long elapsed_ms; uint64_t finish_ms; pr_gettimeofday_millis(&finish_ms); elapsed_ms = (unsigned long) (finish_ms - start_ms); pr_trace_msg(timing_channel, 4, "TLS %s handshake duration: %lu ms", nstrm->strm_type == PR_NETIO_STRM_CTRL ? "control" : "data", elapsed_ms); } adaptive_ms = pcalloc(nstrm->strm_pool, sizeof(uint64_t)); if (pr_table_add(nstrm->notes, PROXY_TLS_ADAPTIVE_BYTES_MS_KEY, adaptive_ms, sizeof(uint64_t)) < 0) { pr_trace_msg(trace_channel, 3, "error stashing '%s' stream note: %s", PROXY_TLS_ADAPTIVE_BYTES_MS_KEY, strerror(errno)); } adaptive_bytes = pcalloc(nstrm->strm_pool, sizeof(off_t)); if (pr_table_add(nstrm->notes, PROXY_TLS_ADAPTIVE_BYTES_COUNT_KEY, adaptive_bytes, sizeof(off_t)) < 0) { pr_trace_msg(trace_channel, 3, "error stashing '%s' stream note: %s", PROXY_TLS_ADAPTIVE_BYTES_COUNT_KEY, strerror(errno)); } if (netio_install_data() < 0) { pr_trace_msg(trace_channel, 1, "error installing data connection NetIO: %s", strerror(errno)); } if (nstrm->strm_type == PR_NETIO_STRM_CTRL) { proxy_sess_state |= PROXY_SESS_STATE_BACKEND_HAS_CTRL_TLS; } else if (nstrm->strm_type == PR_NETIO_STRM_DATA) { proxy_sess_state |= PROXY_SESS_STATE_BACKEND_HAS_DATA_TLS; } } return 0; } static int netio_read_cb(pr_netio_stream_t *nstrm, char *buf, size_t buflen) { SSL *ssl = NULL; ssl = (SSL *) pr_table_get(nstrm->notes, PROXY_TLS_NETIO_NOTE, NULL); if (ssl != NULL) { BIO *rbio, *wbio; int bread = 0, bwritten = 0; ssize_t res = 0; unsigned long rbio_rbytes, rbio_wbytes, wbio_rbytes, wbio_wbytes; rbio = SSL_get_rbio(ssl); rbio_rbytes = BIO_number_read(rbio); rbio_wbytes = BIO_number_written(rbio); wbio = SSL_get_wbio(ssl); wbio_rbytes = BIO_number_read(wbio); wbio_wbytes = BIO_number_written(wbio); res = tls_read(ssl, buf, buflen, nstrm->strm_type, nstrm->notes); bread = (BIO_number_read(rbio) - rbio_rbytes) + (BIO_number_read(wbio) - wbio_rbytes); bwritten = (BIO_number_written(rbio) - rbio_wbytes) + (BIO_number_written(wbio) - wbio_wbytes); /* Manually update session.total_raw_in with the difference between * the raw bytes read in versus the non-SSL bytes read in, in order to * have %I be accurately represented for the raw traffic. */ if (res > 0) { session.total_raw_in += (bread - res); } /* Manually update session.total_raw_out, in order to have %O be * accurately represented for the raw traffic. */ if (bwritten > 0) { session.total_raw_out += bwritten; } return res; } return read(nstrm->strm_fd, buf, buflen); } static pr_netio_stream_t *netio_reopen_cb(pr_netio_stream_t *nstrm, int fd, int mode) { if (nstrm->strm_fd != -1) { (void) close(nstrm->strm_fd); } nstrm->strm_fd = fd; nstrm->strm_mode = mode; return nstrm; } static int netio_shutdown_cb(pr_netio_stream_t *nstrm, int how) { if (how == 1 || how == 2) { /* Closing this stream for writing; we need to send the 'close_notify' * alert first, so that the client knows, at the application layer, * that the SSL/TLS session is shutting down. */ if (nstrm->strm_mode == PR_NETIO_IO_WR && (nstrm->strm_type == PR_NETIO_STRM_CTRL || nstrm->strm_type == PR_NETIO_STRM_DATA)) { SSL *ssl; /* If we do not have TLS on this connection, then don't try to do * a TLS shutdown. */ if (nstrm->strm_type == PR_NETIO_STRM_CTRL) { if (!(proxy_sess_state & PROXY_SESS_STATE_BACKEND_HAS_CTRL_TLS)) { return shutdown(nstrm->strm_fd, how); } } else if (nstrm->strm_type == PR_NETIO_STRM_DATA) { if (!(proxy_sess_state & PROXY_SESS_STATE_BACKEND_HAS_DATA_TLS)) { return shutdown(nstrm->strm_fd, how); } } ssl = (SSL *) pr_table_get(nstrm->notes, PROXY_TLS_NETIO_NOTE, NULL); if (ssl != NULL) { BIO *rbio, *wbio; int bread = 0, bwritten = 0; unsigned long rbio_rbytes, rbio_wbytes, wbio_rbytes, wbio_wbytes; rbio = SSL_get_rbio(ssl); rbio_rbytes = BIO_number_read(rbio); rbio_wbytes = BIO_number_written(rbio); wbio = SSL_get_wbio(ssl); wbio_rbytes = BIO_number_read(wbio); wbio_wbytes = BIO_number_written(wbio); if (!(SSL_get_shutdown(ssl) & SSL_SENT_SHUTDOWN)) { /* We haven't sent a 'close_notify' alert yet; do so now. */ SSL_shutdown(ssl); } if (nstrm->strm_type == PR_NETIO_STRM_DATA) { proxy_sess_state &= ~PROXY_SESS_STATE_BACKEND_HAS_DATA_TLS; } bread = (BIO_number_read(rbio) - rbio_rbytes) + (BIO_number_read(wbio) - wbio_rbytes); bwritten = (BIO_number_written(rbio) - rbio_wbytes) + (BIO_number_written(wbio) - wbio_wbytes); /* Manually update session.total_raw_in/out, in order to have %I/%O be * accurately represented for the raw traffic. */ if (bread > 0) { session.total_raw_in += bread; } if (bwritten > 0) { session.total_raw_out += bwritten; } } } } return shutdown(nstrm->strm_fd, how); } static int netio_write_cb(pr_netio_stream_t *nstrm, char *buf, size_t buflen) { SSL *ssl; ssl = (SSL *) pr_table_get(nstrm->notes, PROXY_TLS_NETIO_NOTE, NULL); if (ssl != NULL) { BIO *rbio, *wbio; int bread = 0, bwritten = 0; ssize_t res = 0; unsigned long rbio_rbytes, rbio_wbytes, wbio_rbytes, wbio_wbytes; rbio = SSL_get_rbio(ssl); rbio_rbytes = BIO_number_read(rbio); rbio_wbytes = BIO_number_written(rbio); wbio = SSL_get_wbio(ssl); wbio_rbytes = BIO_number_read(wbio); wbio_wbytes = BIO_number_written(wbio); res = tls_write(ssl, buf, buflen, nstrm->strm_type, nstrm->notes); bread = (BIO_number_read(rbio) - rbio_rbytes) + (BIO_number_read(wbio) - wbio_rbytes); bwritten = (BIO_number_written(rbio) - rbio_wbytes) + (BIO_number_written(wbio) - wbio_wbytes); /* Manually update session.total_raw_in, in order to have %I be * accurately represented for the raw traffic. */ if (bread > 0) { session.total_raw_in += bread; } /* Manually update session.total_raw_out with the difference between * the raw bytes written out versus the non-SSL bytes written out, * in order to have %O be accurately represented for the raw traffic. */ if (res > 0) { session.total_raw_out += (bwritten - res); } return res; } return write(nstrm->strm_fd, buf, buflen); } static int netio_install_ctrl(void) { pr_netio_t *netio; if (tls_ctrl_netio != NULL) { /* If we already have our ctrl netio, then it's been registered, and * we don't need to do anything more. */ return 0; } netio = pr_alloc_netio2(permanent_pool, &proxy_module, "proxy.tls"); netio->abort = netio_abort_cb; netio->close = netio_close_cb; netio->open = netio_open_cb; netio->poll = netio_poll_cb; netio->postopen = netio_postopen_cb; netio->read = netio_read_cb; netio->reopen = netio_reopen_cb; netio->shutdown = netio_shutdown_cb; netio->write = netio_write_cb; if (proxy_netio_use(PR_NETIO_STRM_CTRL, netio) < 0) { return -1; } tls_ctrl_netio = netio; return 0; } static int netio_install_data(void) { pr_netio_t *netio; if (tls_data_netio != NULL) { pr_netio_t *using_netio = NULL; if (proxy_netio_using(PR_NETIO_STRM_DATA, &using_netio) == 0) { if (using_netio == NULL) { if (proxy_netio_use(PR_NETIO_STRM_DATA, tls_data_netio) < 0) { return -1; } } } return 0; } netio = pr_alloc_netio2(permanent_pool, &proxy_module, "proxy.tls"); netio->abort = netio_abort_cb; netio->close = netio_close_cb; netio->open = netio_open_cb; netio->poll = netio_poll_cb; netio->postopen = netio_postopen_cb; netio->read = netio_read_cb; netio->reopen = netio_reopen_cb; netio->shutdown = netio_shutdown_cb; netio->write = netio_write_cb; if (proxy_netio_use(PR_NETIO_STRM_DATA, netio) < 0) { return -1; } tls_data_netio = netio; return 0; } /* Initialization routines */ #if defined(PSK_MAX_PSK_LEN) static unsigned int tls_psk_cb(SSL *ssl, const char *psk_hint, char *identity, unsigned int max_identity_len, unsigned char *psk, unsigned int max_psklen) { int res, bn_len; unsigned int psklen; if (psk_hint != NULL) { pr_trace_msg(trace_channel, 7, "received PSK identity hint: '%s'", psk_hint); } else { pr_trace_msg(trace_channel, 17, "received no PSK identity hint"); } res = snprintf(identity, max_identity_len, "%s", tls_psk_name); if (res < 0 || res > (int) max_identity_len) { pr_trace_msg(trace_channel, 6, "error setting PSK identity to '%s'", tls_psk_name); return 0; } bn_len = BN_num_bytes(tls_psk_bn); if (bn_len > (int) max_psklen) { pr_trace_msg(trace_channel, 6, "warning: unable to use '%s' PSK: max buffer size (%u bytes) " "too small for key (%d bytes)", tls_psk_name, max_psklen, bn_len); return 0; } psklen = BN_bn2bin(tls_psk_bn, psk); if (psklen == 0) { pr_trace_msg(trace_channel, 6, "error converting '%s' PSK to binary: %s", tls_psk_name, proxy_tls_get_errors()); return 0; } if (tls_opts & PROXY_TLS_OPT_ENABLE_DIAGS) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.psk] used PSK identity '%s'", tls_psk_name); } tls_psk_used = TRUE; return psklen; } #endif /* PSK support */ #if !defined(OPENSSL_NO_TLSEXT) && defined(TLSEXT_STATUSTYPE_ocsp) /* TODO: Do more than just log the received stapled OCSP response. */ static int tls_ocsp_response_cb(SSL *ssl, void *user_data) { BIO *bio = NULL; char *data = NULL; long datalen; const unsigned char *ptr; int len, res = 1; bio = BIO_new(BIO_s_mem()); len = SSL_get_tlsext_status_ocsp_resp(ssl, &ptr); BIO_puts(bio, "OCSP response: "); if (ptr == NULL) { BIO_puts(bio, "no response sent\n"); } else { OCSP_RESPONSE *resp; resp = d2i_OCSP_RESPONSE(NULL, &ptr, len); if (resp == NULL) { BIO_puts(bio, "response parse error\n"); BIO_dump_indent(bio, (char *) ptr, len, 4); res = 0; } else { BIO_puts(bio, "\n======================================\n"); OCSP_RESPONSE_print(bio, resp, 0); BIO_puts(bio, "======================================\n"); OCSP_RESPONSE_free(resp); } } datalen = BIO_get_mem_data(bio, &data); if (data) { data[datalen] = '\0'; } pr_trace_msg(trace_channel, 1, "%s", "stapled OCSP response:"); pr_trace_msg(trace_channel, 1, "%s", data); BIO_free(bio); return res; } #endif /* OCSP support */ #if !defined(OPENSSL_NO_TLSEXT) struct proxy_tls_next_proto { const char *proto; unsigned char *encoded_proto; unsigned int encoded_protolen; }; #if defined(PR_USE_OPENSSL_NPN) static int tls_npn_cb(SSL *ssl, unsigned char **npn_out, unsigned char *npn_outlen, const unsigned char *npn_in, unsigned int npn_inlen, void *data) { struct proxy_tls_next_proto *next_proto; next_proto = data; if (pr_trace_get_level(trace_channel) >= 12) { register unsigned int i; int res; pr_trace_msg(trace_channel, 12, "NPN protocols advertised by server:"); for (i = 0; i < npn_inlen; i++) { pr_trace_msg(trace_channel, 12, " %.*s", (int) npn_in[i], (char *) &(npn_in[i+1])); i += npn_in[i]; } res = SSL_select_next_proto(npn_out, npn_outlen, npn_in, npn_inlen, next_proto->encoded_proto, next_proto->encoded_protolen); if (res != OPENSSL_NPN_NEGOTIATED) { pr_trace_msg(trace_channel, 12, "failed to negotiate NPN protocol '%s': %s", PROXY_TLS_NEXT_PROTO, res == OPENSSL_NPN_UNSUPPORTED ? "NPN unsupported by server" : "No overlap with server protocols"); } } return SSL_TLSEXT_ERR_OK; } #endif /* PR_USE_OPENSSL_NPN */ static int set_next_protocol(SSL_CTX *ctx) { register unsigned int i; const char *proto = PROXY_TLS_NEXT_PROTO; struct proxy_tls_next_proto *next_proto; unsigned char *encoded_proto; size_t encoded_protolen, proto_len; proto_len = strlen(proto); encoded_protolen = proto_len + 1; encoded_proto = palloc(proxy_pool, encoded_protolen); encoded_proto[0] = proto_len; for (i = 0; i < proto_len; i++) { encoded_proto[i+1] = proto[i]; } next_proto = palloc(proxy_pool, sizeof(struct proxy_tls_next_proto)); next_proto->proto = pstrdup(proxy_pool, proto); next_proto->encoded_proto = encoded_proto; next_proto->encoded_protolen = encoded_protolen; # if defined(PR_USE_OPENSSL_NPN) SSL_CTX_set_next_proto_select_cb(ctx, tls_npn_cb, next_proto); # endif /* NPN */ # if defined(PR_USE_OPENSSL_ALPN) SSL_CTX_set_alpn_protos(ctx, next_proto->encoded_proto, next_proto->encoded_protolen); # endif /* ALPN */ return 0; } #endif /* OPENSSL_NO_TLSEXT */ static int init_ssl_ctx(void) { long ssl_mode = 0; int ssl_opts = tls_ssl_opts; if (ssl_ctx != NULL) { SSL_CTX_free(ssl_ctx); ssl_ctx = NULL; } ssl_ctx = SSL_CTX_new(SSLv23_client_method()); if (ssl_ctx == NULL) { pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": error creating SSL_CTX: %s", proxy_tls_get_errors()); errno = EPERM; return -1; } /* Note that we explicitly do NOT use OpenSSL's internal cache for * client session caching; we'll use our own. */ SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_OFF); #if OPENSSL_VERSION_NUMBER > 0x000906000L /* The SSL_MODE_AUTO_RETRY mode was added in 0.9.6. */ ssl_mode |= SSL_MODE_AUTO_RETRY; #endif #if OPENSSL_VERSION_NUMBER >= 0x1000001fL /* The SSL_MODE_RELEASE_BUFFERS mode was added in 1.0.0a. */ ssl_mode |= SSL_MODE_RELEASE_BUFFERS; #endif if (ssl_mode != 0) { SSL_CTX_set_mode(ssl_ctx, ssl_mode); } /* If using OpenSSL-0.9.7 or greater, prevent session resumptions on * renegotiations (more secure). */ #if OPENSSL_VERSION_NUMBER > 0x000907000L ssl_opts |= SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION; #endif /* Disable SSL compression. */ #if defined(SSL_OP_NO_COMPRESSION) ssl_opts |= SSL_OP_NO_COMPRESSION; #endif /* SSL_OP_NO_COMPRESSION */ #if defined(PR_USE_OPENSSL_ECC) # if defined(SSL_OP_SINGLE_ECDH_USE) ssl_opts |= SSL_OP_SINGLE_ECDH_USE; # endif # if defined(SSL_OP_SAFARI_ECDHE_ECDSA_BUG) ssl_opts |= SSL_OP_SAFARI_ECDHE_ECDSA_BUG; # endif #endif /* ECC support */ SSL_CTX_set_options(ssl_ctx, ssl_opts); #if !defined(OPENSSL_NO_TLSEXT) if (set_next_protocol(ssl_ctx) < 0) { pr_trace_msg(trace_channel, 4, "error setting TLS next protocol: %s", strerror(errno)); } #endif /* OPENSSL_NO_TLSEXT */ if (tls_seed_prng() < 0) { pr_log_debug(DEBUG1, MOD_PROXY_VERSION ": unable to properly seed PRNG"); } return 0; } /* Event listeners */ static void proxy_tls_shutdown_ev(const void *event_data, void *user_data) { RAND_cleanup(); } #endif /* PR_USE_OPENSSL */ int proxy_tls_set_data_prot(int data_prot) { int old_data_prot; #if defined(PR_USE_OPENSSL) old_data_prot = tls_need_data_prot; tls_need_data_prot = data_prot; #else old_data_prot = FALSE; #endif /* PR_USE_OPENSSL */ return old_data_prot; } int proxy_tls_set_tls(int engine) { #if defined(PR_USE_OPENSSL) if (engine != PROXY_TLS_ENGINE_ON && engine != PROXY_TLS_ENGINE_OFF && engine != PROXY_TLS_ENGINE_AUTO && engine != PROXY_TLS_ENGINE_IMPLICIT) { errno = EINVAL; return -1; } tls_engine = engine; #endif /* PR_USE_OPENSSL */ return 0; } int proxy_tls_using_tls(void) { #if defined(PR_USE_OPENSSL) return tls_engine; #else return PROXY_TLS_ENGINE_OFF; #endif /* PR_USE_OPENSSL */ } int proxy_tls_match_client_tls(void) { int client_using_tls = FALSE, res = 0; /* Is the client using TLS right now? */ if (session.rfc2228_mech != NULL && strcasecmp(session.rfc2228_mech, "TLS") == 0) { client_using_tls = TRUE; } if (client_using_tls == TRUE) { int client_using_implicit_ftps = FALSE; config_rec *c; unsigned long mod_tls_opts = 0UL; /* Is the client using implicit FTPS? */ c = find_config(main_server->conf, CONF_PARAM, "TLSOptions", FALSE); while (c != NULL) { unsigned long opts; pr_signals_handle(); opts = *((unsigned long *) c->argv[0]); mod_tls_opts |= opts; c = find_config_next(c, c->next, CONF_PARAM, "TLSOptions", FALSE); } /* We cheat, and duplicate/check for the UseImplicitTLS TLSOption * (TLS_OPT_USE_IMPLICIT_SSL) from the mod_tls.c implementation. */ if (mod_tls_opts & 0x0200) { client_using_implicit_ftps = TRUE; } if (client_using_implicit_ftps == TRUE) { pr_trace_msg(trace_channel, 17, "setting implicit FTPS due to ProxyTLSEngine MatchClient"); res = proxy_tls_set_tls(PROXY_TLS_ENGINE_IMPLICIT); } else { /* Using explicit FTPS. */ pr_trace_msg(trace_channel, 17, "setting explicit FTPS due to ProxyTLSEngine MatchClient"); res = proxy_tls_set_tls(PROXY_TLS_ENGINE_ON); } } else { pr_trace_msg(trace_channel, 17, "disabling FTPS due to ProxyTLSEngine MatchClient"); res = proxy_tls_set_tls(PROXY_TLS_ENGINE_OFF); } return res; } int proxy_tls_init(pool *p, const char *tables_path, int flags) { #if defined(PR_USE_OPENSSL) int res; memset(&tls_ds, 0, sizeof(tls_ds)); switch (proxy_datastore) { case PROXY_DATASTORE_SQLITE: res = proxy_tls_db_as_datastore(&tls_ds, proxy_datastore_data, proxy_datastore_datasz); break; case PROXY_DATASTORE_REDIS: res = proxy_tls_redis_as_datastore(&tls_ds, proxy_datastore_data, proxy_datastore_datasz); break; default: res = -1; errno = EINVAL; break; } if (res < 0) { return -1; } res = (tls_ds.init)(p, tables_path, flags); if (res < 0) { return -1; } if (pr_module_exists("mod_sftp.c") == FALSE && pr_module_exists("mod_tls.c") == FALSE) { #if OPENSSL_VERSION_NUMBER < 0x10100000L OPENSSL_config(NULL); #endif /* prior to OpenSSL-1.1.x */ SSL_load_error_strings(); SSL_library_init(); ERR_load_crypto_strings(); OpenSSL_add_all_algorithms(); } res = init_ssl_ctx(); if (res < 0) { return -1; } tls_tables_path = pstrdup(proxy_pool, tables_path); pr_event_register(&proxy_module, "core.shutdown", proxy_tls_shutdown_ev, NULL); #endif /* PR_USE_OPENSSL */ return 0; } int proxy_tls_free(pool *p) { if (p == NULL) { errno = EINVAL; return -1; } #if defined(PR_USE_OPENSSL) if (ssl_ctx != NULL) { SSL_CTX_free(ssl_ctx); ssl_ctx = NULL; } if (tls_ds.dsh != NULL) { int res; res = (tls_ds.close)(p, tls_ds.dsh); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error closing datastore: %s", strerror(errno)); } tls_ds.dsh = NULL; } #endif /* PR_USE_OPENSSL */ return 0; } #if defined(PR_USE_OPENSSL) /* Construct the options value that disables all unsupported protocols. */ static int get_disabled_protocols(unsigned int supported_protocols) { int disabled_protocols; /* First, create an options value where ALL protocols are disabled. */ disabled_protocols = (SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_TLSv1); # if defined(SSL_OP_NO_TLSv1_1) disabled_protocols |= SSL_OP_NO_TLSv1_1; # endif # if defined(SSL_OP_NO_TLSv1_2) disabled_protocols |= SSL_OP_NO_TLSv1_2; # endif # if defined(SSL_OP_NO_TLSv1_3) disabled_protocols |= SSL_OP_NO_TLSv1_3; # endif /* Now, based on the given bitset of supported protocols, clear the * necessary bits. */ if (supported_protocols & PROXY_TLS_PROTO_SSL_V3) { disabled_protocols &= ~SSL_OP_NO_SSLv3; } if (supported_protocols & PROXY_TLS_PROTO_TLS_V1) { disabled_protocols &= ~SSL_OP_NO_TLSv1; } # if OPENSSL_VERSION_NUMBER >= 0x10001000L if (supported_protocols & PROXY_TLS_PROTO_TLS_V1_1) { disabled_protocols &= ~SSL_OP_NO_TLSv1_1; } if (supported_protocols & PROXY_TLS_PROTO_TLS_V1_2) { disabled_protocols &= ~SSL_OP_NO_TLSv1_2; } # endif /* OpenSSL-1.0.1 or later */ #if defined(SSL_OP_NO_TLSv1_3) if (supported_protocols & PROXY_TLS_PROTO_TLS_V1_3) { disabled_protocols &= ~SSL_OP_NO_TLSv1_3; } #endif /* OpenSSL 1.1.1 or later */ return disabled_protocols; } static const char *get_enabled_protocols_str(pool *p, unsigned int protos, unsigned int *count) { char *proto_str = ""; unsigned int nproto = 0; if (protos & PROXY_TLS_PROTO_SSL_V3) { proto_str = pstrcat(p, proto_str, *proto_str ? ", " : "", "SSLv3", NULL); nproto++; } if (protos & PROXY_TLS_PROTO_TLS_V1) { proto_str = pstrcat(p, proto_str, *proto_str ? ", " : "", "TLSv1", NULL); nproto++; } if (protos & PROXY_TLS_PROTO_TLS_V1_1) { proto_str = pstrcat(p, proto_str, *proto_str ? ", " : "", "TLSv1.1", NULL); nproto++; } if (protos & PROXY_TLS_PROTO_TLS_V1_2) { proto_str = pstrcat(p, proto_str, *proto_str ? ", " : "", "TLSv1.2", NULL); nproto++; } *count = nproto; return proto_str; } # if defined(PSK_MAX_PSK_LEN) static int tls_load_psk(const char *identity, const char *path) { register int i; char key_buf[PR_TUNABLE_BUFFER_SIZE]; int fd, key_len, valid_hex = TRUE, res, xerrno; struct stat st; BIGNUM *bn = NULL; PRIVS_ROOT fd = open(path, O_RDONLY); xerrno = errno; PRIVS_RELINQUISH if (fd < 0) { pr_trace_msg(trace_channel, 6, "error opening ProxyTLSPreSharedKey file '%s': %s", path, strerror(xerrno)); errno = xerrno; return -1; } if (fstat(fd, &st) < 0) { xerrno = errno; pr_trace_msg(trace_channel, 6, "error checking ProxyTLSPreSharedKey file '%s': %s", path, strerror(xerrno)); (void) close(fd); errno = xerrno; return -1; } /* Check on the permissions of the file; skip it if the permissions * are too permissive, e.g. file is world-read/writable. */ if (st.st_mode & S_IROTH) { pr_trace_msg(trace_channel, 6, "unable to use ProxyTLSPreSharedKey file '%s': " "file is world-readable", path); (void) close(fd); errno = EPERM; return -1; } if (st.st_mode & S_IWOTH) { pr_trace_msg(trace_channel, 6, "unable to use ProxyTLSPreSharedKey file '%s': " "file is world-writable", path); (void) close(fd); errno = EPERM; return -1; } if (st.st_size == 0) { pr_trace_msg(trace_channel, 6, "unable to use ProxyTLSPreSharedKey file '%s': " "file is zero length", path); (void) close(fd); errno = ENOENT; return -1; } /* Read the entire key into memory. */ memset(key_buf, '\0', sizeof(key_buf)); key_len = read(fd, key_buf, sizeof(key_buf)-1); xerrno = errno; (void) close(fd); if (key_len < 0) { pr_trace_msg(trace_channel, 6, ": error reading ProxyTLSPreSharedKey file '%s': %s", path, strerror(xerrno)); errno = xerrno; return -1; } if (key_len < PROXY_TLS_MIN_PSK_LEN) { pr_trace_msg(trace_channel, 6, "read %d bytes from ProxyTLSPreSharedKey file '%s', need at least %d " "bytes of key data, ignoring", key_len, path, PROXY_TLS_MIN_PSK_LEN); errno = ENOENT; return -1; } key_buf[key_len] = '\0'; key_buf[sizeof(key_buf)-1] = '\0'; /* Ignore any trailing newlines. */ if (key_buf[key_len-1] == '\n') { key_buf[key_len-1] = '\0'; key_len--; } if (key_buf[key_len-1] == '\r') { key_buf[key_len-1] = '\0'; key_len--; } /* Ensure that it is all hex encoded data */ for (i = 0; i < key_len; i++) { if (isxdigit((int) key_buf[i]) == 0) { valid_hex = FALSE; break; } } if (valid_hex == FALSE) { pr_trace_msg(trace_channel, 6, "unable to use '%s' data from ProxyTLSPreSharedKey file '%s': " "not a hex number", key_buf, path); errno = EINVAL; return -1; } res = BN_hex2bn(&bn, key_buf); if (res == 0) { pr_trace_msg(trace_channel, 6, "failed to convert '%s' data from ProxyTLSPreSharedKey file '%s' " "to BIGNUM: %s", key_buf, path, proxy_tls_get_errors()); if (bn != NULL) { BN_free(bn); } errno = EINVAL; return -1; } tls_psk_name = identity; tls_psk_bn = bn; return 0; } # endif /* PSK support */ static void tls_info_cb(const SSL *ssl, int where, int ret) { const char *str = "(unknown)"; int w; pr_signals_handle(); w = where & ~SSL_ST_MASK; if (w & SSL_ST_CONNECT) { str = "connecting"; } else if (w & SSL_ST_ACCEPT) { str = "accepting"; } else { int ssl_state; ssl_state = SSL_get_state(ssl); switch (ssl_state) { # if defined(SSL_ST_BEFORE) case SSL_ST_BEFORE: str = "before"; break; # endif #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(HAVE_LIBRESSL) case TLS_ST_OK: #else case SSL_ST_OK: #endif /* OpenSSL-1.1.x and later */ str = "ok"; break; # if defined(SSL_ST_RENEGOTIATE) case SSL_ST_RENEGOTIATE: str = "renegotiating"; break; # endif default: break; } } if (where & SSL_CB_CONNECT_LOOP) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.info] %s: %s", str, SSL_state_string_long(ssl)); } else if (where & SSL_CB_HANDSHAKE_START) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.info] %s: %s", str, SSL_state_string_long(ssl)); } else if (where & SSL_CB_HANDSHAKE_DONE) { if (pr_trace_get_level(trace_channel) >= 9) { int reused; reused = SSL_session_reused((SSL *) ssl); if (reused > 0) { pr_trace_msg(trace_channel, 9, "RESUMED SSL/TLS session: %s using cipher %s (%d bits)", SSL_get_version(ssl), SSL_get_cipher_name(ssl), SSL_get_cipher_bits(ssl, NULL)); } else { pr_trace_msg(trace_channel, 9, "negotiated NEW SSL/TLS session"); } } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.info] %s: %s", str, SSL_state_string_long(ssl)); } else if (where & SSL_CB_LOOP) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.info] %s: %s", str, SSL_state_string_long(ssl)); } else if (where & SSL_CB_ALERT) { str = (where & SSL_CB_READ) ? "reading" : "writing"; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.info] %s: SSL/TLS alert %s: %s", str, SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret)); } else if (where & SSL_CB_EXIT) { if (ret == 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.info] %s: failed in %s: %s", str, SSL_state_string_long(ssl), proxy_tls_get_errors()); } else if (ret < 0 && errno != 0 && errno != EAGAIN) { /* Ignore EAGAIN errors */ (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.info] %s: error in %s (errno %d: %s)", str, SSL_state_string_long(ssl), errno, strerror(errno)); } } } # if OPENSSL_VERSION_NUMBER > 0x000907000L /* Borrowed from mod_tls. Would be nice to share this code somehow. */ struct tls_label { int labelno; const char *label_name; }; /* SSL record types */ static struct tls_label tls_record_type_labels[] = { { 20, "ChangeCipherSpec" }, { 21, "Alert" }, { 22, "Handshake" }, { 23, "ApplicationData" }, { 0, NULL } }; /* SSL versions */ static struct tls_label tls_version_labels[] = { { 0x0002, "SSL 2.0" }, { 0x0300, "SSL 3.0" }, { 0x0301, "TLS 1.0" }, { 0x0302, "TLS 1.1" }, { 0x0303, "TLS 1.2" }, { 0x0304, "TLS 1.3" }, { 0, NULL } }; /* Cipher suites. These values come from: * http://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-4 */ static struct tls_label tls_ciphersuite_labels[] = { { 0x0000, "SSL_NULL_WITH_NULL_NULL" }, { 0x0001, "SSL_RSA_WITH_NULL_MD5" }, { 0x0002, "SSL_RSA_WITH_NULL_SHA" }, { 0x0003, "SSL_RSA_EXPORT_WITH_RC4_40_MD5" }, { 0x0004, "SSL_RSA_WITH_RC4_128_MD5" }, { 0x0005, "SSL_RSA_WITH_RC4_128_SHA" }, { 0x0006, "SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5" }, { 0x0007, "SSL_RSA_WITH_IDEA_CBC_SHA" }, { 0x0008, "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA" }, { 0x0009, "SSL_RSA_WITH_DES_CBC_SHA" }, { 0x000A, "SSL_RSA_WITH_3DES_EDE_CBC_SHA" }, { 0x000B, "SSL_DH_DSS_EXPORT_WITH_DES40_CBC_SHA" }, { 0x000C, "SSL_DH_DSS_WITH_DES_CBC_SHA" }, { 0x000D, "SSL_DH_DSS_WITH_3DES_EDE_CBC_SHA" }, { 0x000E, "SSL_DH_RSA_EXPORT_WITH_DES40_CBC_SHA" }, { 0x000F, "SSL_DH_RSA_WITH_DES_CBC_SHA" }, { 0x0010, "SSL_DH_RSA_WITH_3DES_EDE_CBC_SHA" }, { 0x0011, "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA" }, { 0x0012, "SSL_DHE_DSS_WITH_DES_CBC_SHA" }, { 0x0013, "SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA" }, { 0x0014, "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA" }, { 0x0015, "SSL_DHE_RSA_WITH_DES_CBC_SHA" }, { 0x0016, "SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA" }, { 0x0017, "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5" }, { 0x0018, "SSL_DH_anon_WITH_RC4_128_MD5" }, { 0x0019, "SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA" }, { 0x001A, "SSL_DH_anon_WITH_DES_CBC_SHA" }, { 0x001B, "SSL_DH_anon_WITH_3DES_EDE_CBC_SHA" }, { 0x001D, "SSL_FORTEZZA_KEA_WITH_FORTEZZA_CBC_SHA" }, { 0x001E, "SSL_FORTEZZA_KEA_WITH_RC4_128_SHA" }, { 0x001F, "TLS_KRB5_WITH_3DES_EDE_CBC_SHA" }, { 0x0020, "TLS_KRB5_WITH_RC4_128_SHA" }, { 0x0021, "TLS_KRB5_WITH_IDEA_CBC_SHA" }, { 0x0022, "TLS_KRB5_WITH_DES_CBC_MD5" }, { 0x0023, "TLS_KRB5_WITH_3DES_EDE_CBC_MD5" }, { 0x0024, "TLS_KRB5_WITH_RC4_128_MD5" }, { 0x0025, "TLS_KRB5_WITH_IDEA_CBC_MD5" }, { 0x0026, "TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA" }, { 0x0027, "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA" }, { 0x0028, "TLS_KRB5_EXPORT_WITH_RC4_40_SHA" }, { 0x0029, "TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5" }, { 0x002A, "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5" }, { 0x002B, "TLS_KRB5_EXPORT_WITH_RC4_40_MD5" }, { 0x002F, "TLS_RSA_WITH_AES_128_CBC_SHA" }, { 0x0030, "TLS_DH_DSS_WITH_AES_128_CBC_SHA" }, { 0x0031, "TLS_DH_RSA_WITH_AES_128_CBC_SHA" }, { 0x0032, "TLS_DHE_DSS_WITH_AES_128_CBC_SHA" }, { 0x0033, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA" }, { 0x0034, "TLS_DH_anon_WITH_AES_128_CBC_SHA" }, { 0x0035, "TLS_RSA_WITH_AES_256_CBC_SHA" }, { 0x0036, "TLS_DH_DSS_WITH_AES_256_CBC_SHA" }, { 0x0037, "TLS_DH_RSA_WITH_AES_256_CBC_SHA" }, { 0x0038, "TLS_DHE_DSS_WITH_AES_256_CBC_SHA" }, { 0x0039, "TLS_DHE_RSA_WITH_AES_256_CBC_SHA" }, { 0x003A, "TLS_DH_anon_WITH_AES_256_CBC_SHA" }, { 0x003B, "TLS_RSA_WITH_NULL_SHA256" }, { 0x003C, "TLS_RSA_WITH_AES_128_CBC_SHA256" }, { 0x003D, "TLS_RSA_WITH_AES_256_CBC_SHA256" }, { 0x003E, "TLS_DH_DSS_WITH_AES_128_CBC_SHA256" }, { 0x003F, "TLS_DH_RSA_WITH_AES_128_CBC_SHA256" }, { 0x0040, "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256" }, { 0x0041, "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA" }, { 0x0042, "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA" }, { 0x0043, "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA" }, { 0x0044, "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA" }, { 0x0045, "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA" }, { 0x0046, "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA" }, { 0x0067, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256" }, { 0x0068, "TLS_DH_DSS_WITH_AES_256_CBC_SHA256" }, { 0x0069, "TLS_DH_RSA_WITH_AES_256_CBC_SHA256" }, { 0x006A, "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256" }, { 0x006B, "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256" }, { 0x006C, "TLS_DH_anon_WITH_AES_128_CBC_SHA256" }, { 0x006D, "TLS_DH_anon_WITH_AES_256_CBC_SHA256" }, { 0x0084, "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA" }, { 0x0085, "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA" }, { 0x0086, "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA" }, { 0x0087, "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA" }, { 0x0088, "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA" }, { 0x0089, "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA" }, { 0x008A, "TLS_PSK_WITH_RC4_128_SHA" }, { 0x008B, "TLS_PSK_WITH_3DES_EDE_CBC_SHA" }, { 0x008C, "TLS_PSK_WITH_AES_128_CBC_SHA" }, { 0x008D, "TLS_PSK_WITH_AES_256_CBC_SHA" }, { 0x008E, "TLS_DHE_PSK_WITH_RC4_128_SHA" }, { 0x008F, "TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA" }, { 0x0090, "TLS_DHE_PSK_WITH_AES_128_CBC_SHA" }, { 0x0091, "TLS_DHE_PSK_WITH_AES_256_CBC_SHA" }, { 0x0092, "TLS_RSA_PSK_WITH_RC4_128_SHA" }, { 0x0093, "TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA" }, { 0x0094, "TLS_RSA_PSK_WITH_AES_128_CBC_SHA" }, { 0x0095, "TLS_RSA_PSK_WITH_AES_256_CBC_SHA" }, { 0x0096, "TLS_RSA_WITH_SEED_CBC_SHA" }, { 0x0097, "TLS_DH_DSS_WITH_SEED_CBC_SHA" }, { 0x0098, "TLS_DH_RSA_WITH_SEED_CBC_SHA" }, { 0x0099, "TLS_DHE_DSS_WITH_SEED_CBC_SHA" }, { 0x009A, "TLS_DHE_RSA_WITH_SEED_CBC_SHA" }, { 0x009B, "TLS_DH_anon_WITH_SEED_CBC_SHA" }, { 0x009C, "TLS_RSA_WITH_AES_128_GCM_SHA256" }, { 0x009D, "TLS_RSA_WITH_AES_256_GCM_SHA384" }, { 0x009E, "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256" }, { 0x009F, "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384" }, { 0x00A0, "TLS_DH_RSA_WITH_AES_128_GCM_SHA256" }, { 0x00A1, "TLS_DH_RSA_WITH_AES_256_GCM_SHA384" }, { 0x00A2, "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256" }, { 0x00A3, "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384" }, { 0x00A4, "TLS_DH_DSS_WITH_AES_128_GCM_SHA256" }, { 0x00A5, "TLS_DH_DSS_WITH_AES_256_GCM_SHA384" }, { 0x00A6, "TLS_DH_anon_WITH_AES_128_GCM_SHA256" }, { 0x00A7, "TLS_DH_anon_WITH_AES_256_GCM_SHA384" }, { 0x00A8, "TLS_PSK_WITH_AES_128_GCM_SHA256" }, { 0x00A9, "TLS_PSK_WITH_AES_256_GCM_SHA384" }, { 0x00AA, "TLS_DHE_PSK_WITH_AES_128_GCM_SHA256" }, { 0x00AB, "TLS_DHE_PSK_WITH_AES_256_GCM_SHA384" }, { 0x00AC, "TLS_RSA_PSK_WITH_AES_128_GCM_SHA256" }, { 0x00AD, "TLS_RSA_PSK_WITH_AES_256_GCM_SHA384" }, { 0x00AE, "TLS_PSK_WITH_AES_128_CBC_SHA256" }, { 0x00AF, "TLS_PSK_WITH_AES_256_CBC_SHA384" }, { 0x00B0, "TLS_PSK_WITH_NULL_SHA256" }, { 0x00B1, "TLS_PSK_WITH_NULL_SHA384" }, { 0x00B2, "TLS_DHE_PSK_WITH_AES_128_CBC_SHA256" }, { 0x00B3, "TLS_DHE_PSK_WITH_AES_256_CBC_SHA384"}, { 0x00B4, "TLS_DHE_PSK_WITH_NULL_SHA256" }, { 0x00B5, "TLS_DHE_PSK_WITH_NULL_SHA384" }, { 0x00B6, "TLS_RSA_PSK_WITH_AES_128_CBC_SHA256" }, { 0x00B7, "TLS_RSA_PSK_WITH_AES_256_CBC_SHA384" }, { 0x00B8, "TLS_RSA_PSK_WITH_NULL_SHA256" }, { 0x00B9, "TLS_RSA_PSK_WITH_NULL_SHA384" }, { 0x00BA, "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256" }, { 0x00BB, "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256" }, { 0x00BC, "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256" }, { 0x00BD, "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256" }, { 0x00BE, "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256" }, { 0x00BF, "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256" }, { 0x00C0, "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256" }, { 0x00C1, "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256" }, { 0x00C2, "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256" }, { 0x00C3, "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256" }, { 0x00C4, "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256" }, { 0x00C5, "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256" }, { 0x00FF, "TLS_EMPTY_RENEGOTIATION_INFO_SCSV" }, { 0x1301, "TLS_AES_128_GCM_SHA256" }, { 0x1302, "TLS_AES_256_GCM_SHA384" }, { 0x1303, "TLS_CHACHA20_POLY1305_SHA256" }, { 0xC001, "TLS_ECDH_ECDSA_WITH_NULL_SHA" }, { 0xC002, "TLS_ECDH_ECDSA_WITH_RC4_128_SHA" }, { 0xC003, "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA" }, { 0xC004, "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA" }, { 0xC005, "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA" }, { 0xC006, "TLS_ECDHE_ECDSA_WITH_NULL_SHA" }, { 0xC007, "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA" }, { 0xC008, "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA" }, { 0xC009, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA" }, { 0xC00A, "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA" }, { 0xC00B, "TLS_ECDH_RSA_WITH_NULL_SHA" }, { 0xC00C, "TLS_ECDH_RSA_WITH_RC4_128_SHA" }, { 0xC00D, "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA" }, { 0xC00E, "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA" }, { 0xC00F, "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA" }, { 0xC010, "TLS_ECDHE_RSA_WITH_NULL_SHA" }, { 0xC011, "TLS_ECDHE_RSA_WITH_RC4_128_SHA" }, { 0xC012, "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA" }, { 0xC013, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA" }, { 0xC014, "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA" }, { 0xC015, "TLS_ECDH_anon_WITH_NULL_SHA" }, { 0xC016, "TLS_ECDH_anon_WITH_RC4_128_SHA" }, { 0xC017, "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA" }, { 0xC018, "TLS_ECDH_anon_WITH_AES_128_CBC_SHA" }, { 0xC019, "TLS_ECDH_anon_WITH_AES_256_CBC_SHA" }, { 0xC01A, "TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA" }, { 0xC01B, "TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA" }, { 0xC01C, "TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA" }, { 0xC01D, "TLS_SRP_SHA_WITH_AES_128_CBC_SHA" }, { 0xC01E, "TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA" }, { 0xC01F, "TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA" }, { 0xC020, "TLS_SRP_SHA_WITH_AES_256_CBC_SHA" }, { 0xC021, "TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA" }, { 0xC022, "TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA" }, { 0xC023, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256" }, { 0xC024, "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384" }, { 0xC025, "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256" }, { 0xC026, "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384" }, { 0xC027, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256" }, { 0xC028, "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384" }, { 0xC029, "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256" }, { 0xC02A, "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384" }, { 0xC02B, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" }, { 0xC02C, "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384" }, { 0xC02D, "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256" }, { 0xC02E, "TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384" }, { 0xC02F, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" }, { 0xC030, "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384" }, { 0xC031, "TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256" }, { 0xC032, "TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384" }, { 0xC07A, "TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256" }, { 0xC07B, "TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384" }, { 0xC086, "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256" }, { 0xC087, "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384" }, { 0xC08A, "TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256" }, { 0xC08B, "TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384" }, { 0xC09C, "TLS_RSA_WITH_AES_128_CCM" }, { 0xC09D, "TLS_RSA_WITH_AES_256_CCM" }, { 0xC0AC, "TLS_ECDHE_ECDSA_WITH_AES_128_CCM" }, { 0xC0AD, "TLS_ECDHE_ECDSA_WITH_AES_256_CCM" }, { 0xC0AE, "TLS_DHE_RSA_WITH_AES_128_CCM" }, { 0xC0AF, "TLS_DHE_RSA_WITH_AES_256_CCM" }, { 0xCCA8, "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256" }, { 0xCCA9, "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256" }, { 0xCCAA, "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256" }, { 0xCCAB, "TLS_PSK_WITH_CHACHA20_POLY1305_SHA256" }, { 0xCCAC, "TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256" }, { 0xCCAD, "TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256" }, { 0xCCAE, "TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256" }, { 0xFEFE, "SSL_RSA_FIPS_WITH_DES_CBC_SHA" }, { 0xFEFF, "SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA" }, { 0, NULL } }; /* Compressions */ static struct tls_label tls_compression_labels[] = { { 0x0000, "None" }, { 0x0001, "Zlib" }, { 0, NULL } }; /* Extensions */ static struct tls_label tls_extension_labels[] = { { 0, "server_name" }, { 1, "max_fragment_length" }, { 2, "client_certificate_url" }, { 3, "trusted_ca_keys" }, { 4, "truncated_hmac" }, { 5, "status_request" }, { 6, "user_mapping" }, { 7, "client_authz" }, { 8, "server_authz" }, { 9, "cert_type" }, { 10, "elliptic_curves" }, { 11, "ec_point_formats" }, { 12, "srp" }, { 13, "signature_algorithms" }, { 14, "use_srtp" }, { 15, "heartbeat" }, { 16, "application_layer_protocol_negotiation" }, { 18, "signed_certificate_timestamp" }, { 21, "padding" }, { 22, "encrypt_then_mac" }, { 23, "extended_master_secret" }, { 35, "session_ticket" }, { 41, "psk" }, { 42, "early_data" }, { 43, "supported_versions" }, { 44, "cookie" }, { 45, "psk_kex_modes" }, { 47, "certificate_authorities" }, { 49, "post_handshake_auth" }, { 50, "signature_algorithms_cert" }, { 51, "key_share" }, { 0xFF01, "renegotiate" }, { 13172, "next_proto_neg" }, { 0, NULL } }; # if defined(TLSEXT_TYPE_signature_algorithms) /* Signature Algorithms. These values come from: * https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-16 */ static struct tls_label tls_sigalgo_labels[] = { { 0x0201, "rsa_pkcs1_sha1" }, { 0x0202, "dsa_sha1" }, { 0x0203, "ecdsa_sha1" }, { 0x0301, "rsa_pkcs1_sha224" }, { 0x0302, "dsa_sha224" }, { 0x0303, "ecdsa_sha224" }, { 0x0401, "rsa_pkcs1_sha256" }, { 0x0402, "dsa_sha256" }, { 0x0403, "ecdsa_secp256r1_sha256" }, { 0x0501, "rsa_pkcs1_sha384" }, { 0x0502, "dsa_sha384" }, { 0x0503, "ecdsa_secp384r1_sha384" }, { 0x0601, "rsa_pkcs1_sha512" }, { 0x0602, "dsa_sha512" }, { 0x0603, "ecdsa_secp521r1_sha512" }, { 0x0804, "rsa_pss_rsae_sha256" }, { 0x0805, "rsa_pss_rsae_sha384" }, { 0x0806, "rsa_pss_rsae_sha512" }, { 0x0807, "ed25519" }, { 0x0808, "ed448" }, { 0x0809, "rsa_pss_pss_sha256" }, { 0x080A, "rsa_pss_pss_sha384" }, { 0x080B, "rsa_pss_pss_sha512" }, { 0, NULL } }; # endif /* TLSEXT_TYPE_signature_algorithms */ # if defined(TLSEXT_TYPE_psk_kex_modes) /* PSK KEX modes. These values come from: * https://tools.ietf.org/html/rfc8446#section-4.2.9 */ static struct tls_label tls_psk_kex_labels[] = { { 0, "psk_ke" }, { 1, "psk_dhe_key" }, { 0, NULL } }; # endif /* TLSEXT_TYPE_psk_kex_modes */ static const char *tls_get_label(int labelno, struct tls_label *labels) { register unsigned int i; for (i = 0; labels[i].label_name != NULL; i++) { if (labels[i].labelno == labelno) { return labels[i].label_name; } } return "[unknown/unsupported]"; } static void tls_print_ssl_version(BIO *bio, const char *name, const unsigned char **msg, size_t *msglen, int *pversion) { int version; if (*msglen < 2) { return; } version = ((*msg)[0] << 8) | (*msg)[1]; BIO_printf(bio, " %s = %s\n", name, tls_get_label(version, tls_version_labels)); *msg += 2; *msglen -= 2; if (pversion != NULL) { *pversion = version; } } static void tls_print_hex(BIO *bio, const char *indent, const char *name, const unsigned char *msg, size_t msglen) { BIO_printf(bio, "%s (%lu %s)\n", name, (unsigned long) msglen, msglen != 1 ? "bytes" : "byte"); if (msglen > 0) { register unsigned int i; BIO_puts(bio, indent); for (i = 0; i < msglen; i++) { BIO_printf(bio, "%02x", msg[i]); } BIO_puts(bio, "\n"); } } static void tls_print_hexbuf(BIO *bio, const char *indent, const char *name, size_t namelen, const unsigned char **msg, size_t *msglen) { size_t buflen; const unsigned char *ptr; if (*msglen < namelen) { return; } ptr = *msg; buflen = ptr[0]; if (namelen > 1) { buflen = (buflen << 8) | ptr[1]; } if (*msglen < namelen + buflen) { return; } ptr += namelen; tls_print_hex(bio, indent, name, ptr, buflen); *msg += (buflen + namelen); *msglen -= (buflen + namelen); } static void tls_print_random(BIO *bio, const unsigned char **msg, size_t *msglen) { time_t ts; const unsigned char *ptr; if (*msglen < 32) { return; } ptr = *msg; ts = ((ptr[0] << 24) | (ptr[1] << 16) | (ptr[2] << 8) | ptr[3]); ptr += 4; BIO_puts(bio, " random:\n"); BIO_printf(bio, " gmt_unix_time = %s (not guaranteed to be accurate)\n", pr_strtime2(ts, TRUE)); tls_print_hex(bio, " ", " random_bytes", ptr, 28); *msg += 32; *msglen -= 32; } static void tls_print_session_id(BIO *bio, const unsigned char **msg, size_t *msglen) { tls_print_hexbuf(bio, " ", " session_id", 1, msg, msglen); } static void tls_print_ciphersuites(BIO *bio, const char *name, const unsigned char **msg, size_t *msglen) { size_t len; len = ((*msg[0]) << 8) | (*msg)[1]; *msg += 2; *msglen -= 2; BIO_printf(bio, " %s (%lu %s)\n", name, (unsigned long) len, len != 1 ? "bytes" : "byte"); if (*msglen < len || (len & 1)) { return; } while (len > 0) { unsigned int suiteno; pr_signals_handle(); suiteno = ((*msg[0]) << 8) | (*msg)[1]; BIO_printf(bio, " %s (0x%x)\n", tls_get_label(suiteno, tls_ciphersuite_labels), suiteno); *msg += 2; *msglen -= 2; len -= 2; } } static void tls_print_compressions(BIO *bio, const char *name, const unsigned char **msg, size_t *msglen) { size_t len; len = (*msg)[0]; *msg += 1; *msglen -= 1; if (*msglen < len) { return; } BIO_printf(bio, " %s (%lu %s)\n", name, (unsigned long) len, len != 1 ? "bytes" : "byte"); while (len > 0) { int comp_type; pr_signals_handle(); comp_type = (*msg)[0]; BIO_printf(bio, " %s\n", tls_get_label(comp_type, tls_compression_labels)); *msg += 1; *msglen -= 1; len -= 1; } } static void tls_print_extension(BIO *bio, const char *indent, int server, int ext_type, const unsigned char *ext, size_t extlen) { BIO_printf(bio, "%sextension_type = %s (%lu %s)\n", indent, tls_get_label(ext_type, tls_extension_labels), (unsigned long) extlen, extlen != 1 ? "bytes" : "byte"); } static void tls_print_extensions(BIO *bio, const char *name, int server, const unsigned char **msg, size_t *msglen) { size_t len; if (*msglen == 0) { BIO_printf(bio, "%s: None\n", name); return; } len = ((*msg)[0] << 8) | (*msg)[1]; if (len != (*msglen - 2)) { return; } *msg += 2; *msglen -= 2; BIO_printf(bio, " %s (%lu %s)\n", name, (unsigned long) len, len != 1 ? "bytes" : "byte"); while (len > 0) { int ext_type; size_t ext_len; pr_signals_handle(); if (*msglen < 4) { break; } ext_type = ((*msg)[0] << 8) | (*msg)[1]; ext_len = ((*msg)[2] << 8) | (*msg)[3]; if (*msglen < (ext_len + 4)) { break; } *msg += 4; tls_print_extension(bio, " ", server, ext_type, *msg, ext_len); *msg += ext_len; *msglen -= (ext_len + 4); } } static void tls_print_client_hello(int io_flag, int version, int content_type, const unsigned char *buf, size_t buflen, SSL *ssl, void *arg) { BIO *bio; char *data = NULL; long datalen; bio = BIO_new(BIO_s_mem()); BIO_puts(bio, "\nClientHello:\n"); tls_print_ssl_version(bio, "client_version", &buf, &buflen, NULL); tls_print_random(bio, &buf, &buflen); tls_print_session_id(bio, &buf, &buflen); if (buflen < 2) { BIO_free(bio); return; } tls_print_ciphersuites(bio, "cipher_suites", &buf, &buflen); if (buflen < 1) { BIO_free(bio); return; } tls_print_compressions(bio, "compression_methods", &buf, &buflen); tls_print_extensions(bio, "extensions", FALSE, &buf, &buflen); datalen = BIO_get_mem_data(bio, &data); if (data != NULL) { data[datalen] = '\0'; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] %.*s", (int) datalen, data); } BIO_free(bio); } static void tls_print_server_hello(int io_flag, int version, int content_type, const unsigned char *buf, size_t buflen, SSL *ssl, void *arg) { BIO *bio; char *data = NULL; long datalen; int print_session_id = TRUE, print_compressions = TRUE, server_version; unsigned int suiteno; bio = BIO_new(BIO_s_mem()); BIO_puts(bio, "\nServerHello:\n"); tls_print_ssl_version(bio, "server_version", &buf, &buflen, &server_version); #if defined(TLS1_3_VERSION) if (server_version == TLS1_3_VERSION) { print_session_id = FALSE; print_compressions = FALSE; } #endif /* TLS1_3_VERSION */ tls_print_random(bio, &buf, &buflen); if (print_session_id == TRUE) { tls_print_session_id(bio, &buf, &buflen); } if (buflen < 2) { BIO_free(bio); return; } /* Print the selected ciphersuite. */ BIO_printf(bio, " cipher_suites (2 bytes)\n"); suiteno = (buf[0] << 8) | buf[1]; BIO_printf(bio, " %s (0x%x)\n", tls_get_label(suiteno, tls_ciphersuite_labels), suiteno); buf += 2; buflen -= 2; if (print_compressions == TRUE) { int comp_type; if (buflen < 1) { BIO_free(bio); return; } /* Print the selected compression. */ BIO_printf(bio, " compression_methods (1 byte)\n"); comp_type = *buf; BIO_printf(bio, " %s\n", tls_get_label(comp_type, tls_compression_labels)); buf += 1; buflen -= 1; } tls_print_extensions(bio, "extensions", TRUE, &buf, &buflen); datalen = BIO_get_mem_data(bio, &data); if (data != NULL) { data[datalen] = '\0'; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] %.*s", (int) datalen, data); } BIO_free(bio); } # if defined(SSL3_MT_NEWSESSION_TICKET) static void tls_print_ticket(int io_flag, int version, int content_type, const unsigned char *buf, size_t buflen, SSL *ssl, void *arg) { BIO *bio; char *data = NULL; long datalen; bio = BIO_new(BIO_s_mem()); BIO_puts(bio, "\nNewSessionTicket:\n"); if (buflen != 0) { unsigned int ticket_lifetime; int print_ticket_age = FALSE, print_extensions = FALSE; ticket_lifetime = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; buf += 4; buflen -= 4; BIO_printf(bio, " ticket_lifetime_hint\n %u (sec)\n", ticket_lifetime); # if defined(TLS1_3_VERSION) if (SSL_version(ssl) == TLS1_3_VERSION) { print_ticket_age = TRUE; print_extensions = TRUE; } # endif /* TLS1_3_VERSION */ if (print_ticket_age == TRUE) { unsigned int ticket_age_add; ticket_age_add = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; buf += 4; buflen -= 4; BIO_printf(bio, " ticket_age_add\n %u (sec)\n", ticket_age_add); tls_print_hexbuf(bio, " ", " ticket_nonce", 1, &buf, &buflen); } tls_print_hexbuf(bio, " ", " ticket", 2, &buf, &buflen); if (print_extensions == TRUE) { tls_print_extensions(bio, "extensions", TRUE, &buf, &buflen); } } else { BIO_puts(bio, " \n"); } datalen = BIO_get_mem_data(bio, &data); if (data != NULL) { data[datalen] = '\0'; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] %.*s", (int) datalen, data); } BIO_free(bio); } # endif /* SSL3_MT_NEWSESSION_TICKET */ #if !defined(OPENSSL_NO_TLSEXT) static void tls_tlsext_cb(SSL *ssl, int server, int type, unsigned char *tlsext_data, int tlsext_datalen, void *user_data) { char *extension_name = "(unknown)"; int print_basic_info = TRUE; /* Note: OpenSSL does not implement all possible extensions. For the * "(unknown)" extensions, see: * * http://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#tls-extensiontype-values-1 */ switch (type) { # if defined(TLSEXT_TYPE_server_name) case TLSEXT_TYPE_server_name: { BIO *bio = NULL; char *ext_info = ""; long ext_infolen = 0; extension_name = "server name"; if (pr_trace_get_level(trace_channel) >= 21 && tlsext_datalen >= 2) { /* We read 2 bytes for the extension length, 1 byte for the * name type, and 2 bytes for the name length. For the SNI * format specification, see: * https://tools.ietf.org/html/rfc6066#section-3 */ int ext_len; ext_len = (tlsext_data[0] << 8) | tlsext_data[1]; if (tlsext_datalen == ext_len + 2 && (ext_len & 1) == 0) { int name_type; tlsext_data += 2; name_type = tlsext_data[0]; tlsext_data += 1; if (name_type == TLSEXT_NAMETYPE_host_name) { size_t name_len; name_len = (tlsext_data[0] << 8) | tlsext_data[1]; tlsext_data += 2; bio = BIO_new(BIO_s_mem()); BIO_printf(bio, "\n %.*s (%lu)", (int) name_len, tlsext_data, (unsigned long) name_len); ext_infolen = BIO_get_mem_data(bio, &ext_info); if (ext_info != NULL) { ext_info[ext_infolen] = '\0'; } } } } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.tlsext] TLS %s extension \"%s\" (ID %d, %d %s)%.*s", server ? "server" : "client", extension_name, type, tlsext_datalen, tlsext_datalen != 1 ? "bytes" : "byte", (int) ext_infolen, ext_info != NULL ? ext_info : ""); if (bio != NULL) { BIO_free(bio); } print_basic_info = FALSE; break; } # endif /* TLSEXT_TYPE_server_name */ # if defined(TLSEXT_TYPE_max_fragment_length) case TLSEXT_TYPE_max_fragment_length: extension_name = "max fragment length"; break; # endif /* TLSEXT_TYPE_max_fragment_length */ # if defined(TLSEXT_TYPE_client_certificate_url) case TLSEXT_TYPE_client_certificate_url: extension_name = "client certificate URL"; break; # endif /* TLSEXT_TYPE_client_certificate_url */ # if defined(TLSEXT_TYPE_trusted_ca_keys) case TLSEXT_TYPE_trusted_ca_keys: extension_name = "trusted CA keys"; break; # endif /* TLSEXT_TYPE_trusted_ca_keys */ # if defined(TLSEXT_TYPE_truncated_hmac) case TLSEXT_TYPE_truncated_hmac: extension_name = "truncated HMAC"; break; # endif /* TLSEXT_TYPE_truncated_hmac */ # if defined(TLSEXT_TYPE_status_request) case TLSEXT_TYPE_status_request: extension_name = "status request"; break; # endif /* TLSEXT_TYPE_status_request */ # if defined(TLSEXT_TYPE_user_mapping) case TLSEXT_TYPE_user_mapping: extension_name = "user mapping"; break; # endif /* TLSEXT_TYPE_user_mapping */ # if defined(TLSEXT_TYPE_client_authz) case TLSEXT_TYPE_client_authz: extension_name = "client authz"; break; # endif /* TLSEXT_TYPE_client_authz */ # if defined(TLSEXT_TYPE_server_authz) case TLSEXT_TYPE_server_authz: extension_name = "server authz"; break; # endif /* TLSEXT_TYPE_server_authz */ # if defined(TLSEXT_TYPE_cert_type) case TLSEXT_TYPE_cert_type: extension_name = "cert type"; break; # endif /* TLSEXT_TYPE_cert_type */ # if defined(TLSEXT_TYPE_elliptic_curves) case TLSEXT_TYPE_elliptic_curves: extension_name = "elliptic curves"; break; # endif /* TLSEXT_TYPE_elliptic_curves */ # if defined(TLSEXT_TYPE_ec_point_formats) case TLSEXT_TYPE_ec_point_formats: extension_name = "EC point formats"; break; # endif /* TLSEXT_TYPE_ec_point_formats */ # if defined(TLSEXT_TYPE_srp) case TLSEXT_TYPE_srp: extension_name = "SRP"; break; # endif /* TLSEXT_TYPE_srp */ # if defined(TLSEXT_TYPE_signature_algorithms) case TLSEXT_TYPE_signature_algorithms: { BIO *bio = NULL; char *ext_info = ""; long ext_infolen = 0; extension_name = "signature algorithms"; if (pr_trace_get_level(trace_channel) >= 21 && tlsext_datalen >= 2) { int len; len = (tlsext_data[0] << 8) | tlsext_data[1]; if (tlsext_datalen == len + 2 && (len & 1) == 0) { tlsext_data += 2; bio = BIO_new(BIO_s_mem()); BIO_puts(bio, "\n"); while (len > 0) { unsigned int sig_algo; pr_signals_handle(); sig_algo = (tlsext_data[0] << 8) | tlsext_data[1]; BIO_printf(bio, " %s (0x%x)\n", tls_get_label(sig_algo, tls_sigalgo_labels), sig_algo); len -= 2; tlsext_data += 2; } ext_infolen = BIO_get_mem_data(bio, &ext_info); if (ext_info != NULL) { ext_info[ext_infolen] = '\0'; } } } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.tlsext] TLS %s extension \"%s\" (ID %d, %d %s)%.*s", server ? "server" : "client", extension_name, type, tlsext_datalen, tlsext_datalen != 1 ? "bytes" : "byte", (int) ext_infolen, ext_info != NULL ? ext_info : ""); if (bio != NULL) { BIO_free(bio); } print_basic_info = FALSE; break; } # endif /* TLSEXT_TYPE_signature_algorithms */ # if defined(TLSEXT_TYPE_use_srtp) case TLSEXT_TYPE_use_srtp: extension_name = "use SRTP"; break; # endif /* TLSEXT_TYPE_use_srtp */ # if defined(TLSEXT_TYPE_heartbeat) case TLSEXT_TYPE_heartbeat: extension_name = "heartbeat"; break; # endif /* TLSEXT_TYPE_heartbeat */ # if defined(TLSEXT_TYPE_signed_certificate_timestamp) case TLSEXT_TYPE_signed_certificate_timestamp: extension_name = "signed certificate timestamp"; break; # endif /* TLSEXT_TYPE_signed_certificate_timestamp */ # if defined(TLSEXT_TYPE_encrypt_then_mac) case TLSEXT_TYPE_encrypt_then_mac: extension_name = "encrypt then mac"; break; # endif /* TLSEXT_TYPE_encrypt_then_mac */ # if defined(TLSEXT_TYPE_extended_master_secret) case TLSEXT_TYPE_extended_master_secret: extension_name = "extended master secret"; break; # endif /* TLSEXT_TYPE_extended_master_secret */ # if defined(TLSEXT_TYPE_session_ticket) case TLSEXT_TYPE_session_ticket: extension_name = "session ticket"; break; # endif /* TLSEXT_TYPE_session_ticket */ # if defined(TLSEXT_TYPE_psk) case TLSEXT_TYPE_psk: extension_name = "PSK"; break; # endif /* TLSEXT_TYPE_psk */ # if defined(TLSEXT_TYPE_supported_versions) case TLSEXT_TYPE_supported_versions: { BIO *bio = NULL; char *ext_info = NULL; long ext_infolen = 0; /* If we are the server responding, we only indicate the selected * protocol version. Otherwise, we are a client indicating the range * of versions supported. */ extension_name = "supported versions"; if (pr_trace_get_level(trace_channel) >= 21) { bio = BIO_new(BIO_s_mem()); if (server) { if (tlsext_datalen == 2) { int version; version = (tlsext_data[0] << 8) | tlsext_data[1]; BIO_printf(bio, "\n %s (0x%x)\n", tls_get_label(version, tls_version_labels), version); } } else { if (tlsext_datalen >= 1) { int len; len = tlsext_data[0]; if (tlsext_datalen == len + 1) { tlsext_data += 1; BIO_puts(bio, "\n"); while (len > 0) { int version; pr_signals_handle(); version = (tlsext_data[0] << 8) | tlsext_data[1]; BIO_printf(bio, " %s (0x%x)\n", tls_get_label(version, tls_version_labels), version); len -= 2; tlsext_data += 2; } } } } ext_infolen = BIO_get_mem_data(bio, &ext_info); if (ext_info != NULL) { ext_info[ext_infolen] = '\0'; } } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.tlsext] TLS %s extension \"%s\" (ID %d, %d %s)%.*s", server ? "server" : "client", extension_name, type, tlsext_datalen, tlsext_datalen != 1 ? "bytes" : "byte", (int) ext_infolen, ext_info != NULL ? ext_info : ""); if (bio != NULL) { BIO_free(bio); } print_basic_info = FALSE; break; } # endif /* TLSEXT_TYPE_supported_versions */ # if defined(TLSEXT_TYPE_psk_kex_modes) case TLSEXT_TYPE_psk_kex_modes: { BIO *bio = NULL; char *ext_info = NULL; long ext_infolen = 0; extension_name = "PSK KEX modes"; if (pr_trace_get_level(trace_channel) >= 19) { if (tlsext_datalen >= 1) { int len; bio = BIO_new(BIO_s_mem()); len = tlsext_data[0]; if (tlsext_datalen == len + 1) { tlsext_data += 1; BIO_puts(bio, "\n"); while (len > 0) { int kex_mode; pr_signals_handle(); kex_mode = tlsext_data[0]; BIO_printf(bio, " %s (%d)\n", tls_get_label(kex_mode, tls_psk_kex_labels), kex_mode); len -= 1; tlsext_data += 1; } } } ext_infolen = BIO_get_mem_data(bio, &ext_info); if (ext_info != NULL) { ext_info[ext_infolen] = '\0'; } } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.tlsext] TLS %s extension \"%s\" (ID %d, %d %s)%.*s", server ? "server" : "client", extension_name, type, tlsext_datalen, tlsext_datalen != 1 ? "bytes" : "byte", (int) ext_infolen, ext_info != NULL ? ext_info : ""); if (bio != NULL) { BIO_free(bio); } print_basic_info = FALSE; break; } # endif /* TLSEXT_TYPE_psk_kex_modes */ # if defined(TLSEXT_TYPE_renegotiate) case TLSEXT_TYPE_renegotiate: extension_name = "renegotiation info"; break; # endif /* TLSEXT_TYPE_renegotiate */ # if defined(TLSEXT_TYPE_opaque_prf_input) case TLSEXT_TYPE_opaque_prf_input: extension_name = "opaque PRF input"; break; # endif /* TLSEXT_TYPE_opaque_prf_input */ # if defined(TLSEXT_TYPE_next_proto_neg) case TLSEXT_TYPE_next_proto_neg: extension_name = "next protocol"; break; # endif /* TLSEXT_TYPE_next_proto_neg */ # if defined(TLSEXT_TYPE_application_layer_protocol_negotiation) case TLSEXT_TYPE_application_layer_protocol_negotiation: extension_name = "application layer protocol"; break; # endif /* TLSEXT_TYPE_application_layer_protocol_negotiation */ # if defined(TLSEXT_TYPE_padding) case TLSEXT_TYPE_padding: extension_name = "TLS padding"; break; # endif /* TLSEXT_TYPE_padding */ # if defined(TLSEXT_TYPE_early_data) case TLSEXT_TYPE_early_data: extension_name = "early data"; break; # endif /* TLSEXT_TYPE_early_data */ # if defined(TLSEXT_TYPE_post_handshake_auth) case TLSEXT_TYPE_post_handshake_auth: extension_name = "post handshake auth"; break; # endif /* TLSEXT_TYPE_post_handshake_auth */ default: print_basic_info = TRUE; break; } if (print_basic_info == TRUE) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.tlsext] TLS %s extension \"%s\" (ID %d, %d %s)", server ? "server" : "client", extension_name, type, tlsext_datalen, tlsext_datalen != 1 ? "bytes" : "byte"); } } #endif /* OPENSSL_NO_TLSEXT */ static void tls_msg_cb(int io_flag, int version, int content_type, const void *buf, size_t buflen, SSL *ssl, void *arg) { char *action_str = NULL; char *version_str = NULL; char *bytes_str = buflen != 1 ? "bytes" : "byte"; if (io_flag == 0) { action_str = "received"; } else if (io_flag == 1) { action_str = "sent"; } switch (version) { case SSL2_VERSION: version_str = "SSLv2"; break; case SSL3_VERSION: version_str = "SSLv3"; break; case TLS1_VERSION: version_str = "TLSv1"; break; # if defined(TLS1_1_VERSION) case TLS1_1_VERSION: version_str = "TLSv1.1"; break; # endif /* TLS1_1_VERSION */ # if defined(TLS1_2_VERSION) case TLS1_2_VERSION: version_str = "TLSv1.2"; break; # endif /* TLS1_2_VERSION */ # if defined(TLS1_3_VERSION) case TLS1_3_VERSION: version_str = "TLSv1.3"; break; # endif /* TLS1_3_VERSION */ default: # if defined(SSL3_RT_HEADER) /* OpenSSL calls this callback for SSL records received; filter those * from true "unknowns". */ if (version == 0 && (content_type != SSL3_RT_HEADER || buflen != SSL3_RT_HEADER_LENGTH)) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] unknown/unsupported version: %d", version); } # else (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] unknown/unsupported version: %d", version); # endif break; } if (version == SSL3_VERSION || # if defined(TLS1_1_VERSION) version == TLS1_1_VERSION || # endif /* TLS1_1_VERSION */ # if defined(TLS1_2_VERSION) version == TLS1_2_VERSION || # endif /* TLS1_2_VERSION */ # if defined(TLS1_3_VERSION) version == TLS1_3_VERSION || # endif /* TLS1_3_VERSION */ version == TLS1_VERSION) { switch (content_type) { case SSL3_RT_CHANGE_CIPHER_SPEC: /* ChangeCipherSpec message */ (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] %s %s ChangeCipherSpec message (%u %s)", action_str, version_str, (unsigned int) buflen, bytes_str); break; case SSL3_RT_ALERT: { /* Alert messages */ pr_trace_msg(trace_channel, 27, "%s %s Alert (%u %s)", action_str, version_str, (unsigned int) buflen, bytes_str); if (buflen == 2) { char *severity_str = NULL; /* Peek naughtily into the buffer. */ switch (((const unsigned char *) buf)[0]) { case SSL3_AL_WARNING: severity_str = "warning"; break; case SSL3_AL_FATAL: severity_str = "fatal"; break; } switch (((const unsigned char *) buf)[1]) { case SSL3_AD_CLOSE_NOTIFY: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] %s %s %s 'close_notify' Alert message (%u %s)", action_str, version_str, severity_str, (unsigned int) buflen, bytes_str); break; case SSL3_AD_UNEXPECTED_MESSAGE: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] %s %s %s 'unexpected_message' Alert message " "(%u %s)", action_str, version_str, severity_str, (unsigned int) buflen, bytes_str); break; case SSL3_AD_BAD_RECORD_MAC: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] %s %s %s 'bad_record_mac' Alert message (%u %s)", action_str, version_str, severity_str, (unsigned int) buflen, bytes_str); break; case 21: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] %s %s %s 'decryption_failed' Alert message " "(%u %s)", action_str, version_str, severity_str, (unsigned int) buflen, bytes_str); break; case 22: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] %s %s %s 'record_overflow' Alert message (%u %s)", action_str, version_str, severity_str, (unsigned int) buflen, bytes_str); break; case 30: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] %s %s %s 'decompression_failure' Alert message " "(%u %s)", action_str, version_str, severity_str, (unsigned int) buflen, bytes_str); break; case SSL3_AD_HANDSHAKE_FAILURE: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] %s %s %s 'handshake_failure' Alert message " "(%u %s)", action_str, version_str, severity_str, (unsigned int) buflen, bytes_str); break; # if defined(SSL3_AD_BAD_CERTIFICATE) case SSL3_AD_BAD_CERTIFICATE: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] %s %s %s 'bad_certificate' Alert message " "(%u %s)", action_str, version_str, severity_str, (unsigned int) buflen, bytes_str); break; # endif /* SSL3_AD_BAD_CERTIFICATE */ # if defined(SSL3_AD_CERTIFICATE_REVOKED) case SSL3_AD_CERTIFICATE_REVOKED: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] %s %s %s 'certificate_revoked' Alert message " "(%u %s)", action_str, version_str, severity_str, (unsigned int) buflen, bytes_str); break; # endif /* SSL3_AD_CERTIFICATE_REVOKED */ # if defined(SSL3_AD_CERTIFICATE_EXPIRED) case SSL3_AD_CERTIFICATE_EXPIRED: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] %s %s %s 'certificate_expired' Alert message " "(%u %s)", action_str, version_str, severity_str, (unsigned int) buflen, bytes_str); break; # endif /* SSL3_AD_CERTIFICATE_EXPIRED */ # if defined(SSL3_AD_CERTIFICATE_UNKNOWN) case SSL3_AD_CERTIFICATE_UNKNOWN: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] %s %s %s 'certificate_unknown' Alert message " "(%u %s)", action_str, version_str, severity_str, (unsigned int) buflen, bytes_str); break; # endif /* SSL3_AD_CERTIFICATE_UNKNOWN */ # if defined(SSL3_AD_ILLEGAL_PARAMETER) case SSL3_AD_ILLEGAL_PARAMETER: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] %s %s %s 'illegal_parameter' Alert message " "(%u %s)", action_str, version_str, severity_str, (unsigned int) buflen, bytes_str); break; # endif /* SSL3_AD_ILLEGAL_PARAMETER */ } } else { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] %s %s Alert message, unknown type (%u %s)", action_str, version_str, (unsigned int) buflen, bytes_str); } break; } case SSL3_RT_HANDSHAKE: { /* Handshake messages */ pr_trace_msg(trace_channel, 27, "%s %s Handshake (%u %s)", action_str, version_str, (unsigned int) buflen, bytes_str); if (buflen > 0) { /* Peek naughtily into the buffer. */ switch (((const unsigned char *) buf)[0]) { case SSL3_MT_HELLO_REQUEST: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] %s %s 'HelloRequest' Handshake message (%u %s)", action_str, version_str, (unsigned int) buflen, bytes_str); break; case SSL3_MT_CLIENT_HELLO: { const unsigned char *msg; size_t msglen; msg = buf; msglen = buflen; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] %s %s 'ClientHello' Handshake message (%u %s)", action_str, version_str, (unsigned int) buflen, bytes_str); tls_print_client_hello(io_flag, version, content_type, msg + 4, msglen - 4, ssl, arg); break; } case SSL3_MT_SERVER_HELLO: { const unsigned char *msg; size_t msglen; msg = buf; msglen = buflen; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] %s %s 'ServerHello' Handshake message (%u %s)", action_str, version_str, (unsigned int) buflen, bytes_str); tls_print_server_hello(io_flag, version, content_type, msg + 4, msglen - 4, ssl, arg); break; } # if defined(SSL3_MT_NEWSESSION_TICKET) case SSL3_MT_NEWSESSION_TICKET: { const unsigned char *msg; size_t msglen; msg = buf; msglen = buflen; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] %s %s 'NewSessionTicket' Handshake message (%u %s)", action_str, version_str, (unsigned int) buflen, bytes_str); tls_print_ticket(io_flag, version, content_type, msg + 4, msglen - 4, ssl, arg); break; } # endif /* SSL3_MT_NEWSESSION_TICKET */ case SSL3_MT_CERTIFICATE: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] %s %s 'Certificate' Handshake message (%u %s)", action_str, version_str, (unsigned int) buflen, bytes_str); break; case SSL3_MT_SERVER_KEY_EXCHANGE: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] %s %s 'ServerKeyExchange' Handshake message " "(%u %s)", action_str, version_str, (unsigned int) buflen, bytes_str); break; case SSL3_MT_CERTIFICATE_REQUEST: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] %s %s 'CertificateRequest' Handshake message " "(%u %s)", action_str, version_str, (unsigned int) buflen, bytes_str); break; case SSL3_MT_SERVER_DONE: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] %s %s 'ServerHelloDone' Handshake message (%u %s)", action_str, version_str, (unsigned int) buflen, bytes_str); break; case SSL3_MT_CERTIFICATE_VERIFY: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] %s %s 'CertificateVerify' Handshake message " "(%u %s)", action_str, version_str, (unsigned int) buflen, bytes_str); break; case SSL3_MT_CLIENT_KEY_EXCHANGE: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] %s %s 'ClientKeyExchange' Handshake message " "(%u %s)", action_str, version_str, (unsigned int) buflen, bytes_str); break; case SSL3_MT_FINISHED: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] %s %s 'Finished' Handshake message (%u %s)", action_str, version_str, (unsigned int) buflen, bytes_str); break; # if defined(SSL3_MT_CERTIFICATE_STATUS) case SSL3_MT_CERTIFICATE_STATUS: (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] %s %s 'CertificateStatus' Handshake message (%u %s)", action_str, version_str, (unsigned int) buflen, bytes_str); break; # endif /* SSL3_MT_CERTIFICATE_STATUS */ # if defined(SSL3_MT_ENCRYPTED_EXTENSIONS) case SSL3_MT_ENCRYPTED_EXTENSIONS: { const unsigned char *msg; size_t msglen; BIO *bio; char *data = NULL; long datalen; msg = buf; msglen = buflen; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] %s %s 'EncryptedExtensions' Handshake message " "(%u %s)", action_str, version_str, (unsigned int) buflen, bytes_str); bio = BIO_new(BIO_s_mem()); msg += 4; msglen -= 4; BIO_puts(bio, "\nEncryptedExtensions:\n"); tls_print_extensions(bio, "extensions", TRUE, &msg, &msglen); datalen = BIO_get_mem_data(bio, &data); if (data != NULL) { data[datalen] = '\0'; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] %.*s", (int) datalen, data); } BIO_free(bio); break; } # endif /* SSL3_MT_ENCRYPTED_EXTENSIONS */ } } else { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] %s %s Handshake message, unknown type %d (%u %s)", action_str, version_str, content_type, (unsigned int) buflen, bytes_str); } break; } } # if defined(SSL3_RT_HEADER) } else if (version == 0 && content_type == SSL3_RT_HEADER && buflen == SSL3_RT_HEADER_LENGTH) { const unsigned char *msg; const char *record_type; unsigned int msg_len; msg = buf; record_type = tls_get_label(msg[0], tls_record_type_labels); msg_len = msg[buflen - 2] << 8 | msg[buflen - 1]; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] %s protocol record message (content_type = %s, len = %u)", action_str, record_type, msg_len); # endif } else { /* This case might indicate an issue with OpenSSL itself; the version * given to the msg_callback function was not initialized, or not set to * one of the recognized SSL/TLS versions. Weird. */ (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] %s message of unknown version %d, type %d (%u %s)", action_str, version, content_type, (unsigned int) buflen, bytes_str); } } # endif /* OpenSSL-0.9.7 or later */ #endif /* PR_USE_OPENSSL */ int proxy_tls_sess_init(pool *p, struct proxy_session *proxy_sess, int flags) { #if defined(PR_USE_OPENSSL) config_rec *c; unsigned int enabled_proto_count = 0, tls_protocol = PROXY_TLS_PROTO_DEFAULT; int disabled_proto, res, xerrno = 0; const char *enabled_proto_str = NULL; char *ca_file = NULL, *ca_path = NULL, *cert_file = NULL, *key_file = NULL, *crl_file = NULL, *crl_path = NULL; if (proxy_sess == NULL) { errno = EINVAL; return -1; } if (proxy_sess->use_ftp == FALSE) { return 0; } c = find_config(main_server->conf, CONF_PARAM, "ProxyTLSEngine", FALSE); if (c != NULL) { tls_engine = *((int *) c->argv[0]); } if (tls_engine == PROXY_TLS_ENGINE_OFF) { return 0; } if (p == NULL) { errno = EINVAL; return -1; } if (ssl_ctx == NULL) { /* We haven't been initialized properly! */ pr_trace_msg(trace_channel, 2, "%s", "missing required SSL_CTX initialization!"); errno = EPERM; return -1; } c = find_config(main_server->conf, CONF_PARAM, "ProxyTLSOptions", FALSE); while (c != NULL) { unsigned long opts = 0; pr_signals_handle(); opts = *((unsigned long *) c->argv[0]); tls_opts |= opts; c = find_config_next(c, c->next, CONF_PARAM, "ProxyTLSOptions", FALSE); } PRIVS_ROOT tls_ds.dsh = (tls_ds.open)(proxy_pool, tls_tables_path, tls_opts); xerrno = errno; PRIVS_RELINQUISH if (tls_ds.dsh == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error opening TLS datastore: %s", strerror(xerrno)); errno = xerrno; return -1; } c = find_config(main_server->conf, CONF_PARAM, "ProxyTLSProtocol", FALSE); if (c != NULL) { tls_protocol = *((unsigned int *) c->argv[0]); } disabled_proto = get_disabled_protocols(tls_protocol); /* Per the comments in , SSL_CTX_set_options() uses |= on * the previous value. This means we can easily OR in our new option * values with any previously set values. */ enabled_proto_str = get_enabled_protocols_str(main_server->pool, tls_protocol, &enabled_proto_count); pr_log_debug(DEBUG8, MOD_PROXY_VERSION ": supporting %s %s", enabled_proto_str, enabled_proto_count != 1 ? "protocols" : "protocol only"); SSL_CTX_set_options(ssl_ctx, disabled_proto); c = find_config(main_server->conf, CONF_PARAM, "ProxyTLSCipherSuite", FALSE); while (c != NULL) { int protocol; pr_signals_handle(); protocol = *((int *) c->argv[1]); #if OPENSSL_VERSION_NUMBER >= 0x10101000L && \ defined(TLS1_3_VERSION) if (protocol == PROXY_TLS_PROTO_TLS_V1_3) { tlsv13_cipher_suite = c->argv[0]; } else { tls_cipher_suite = c->argv[0]; } #else tls_cipher_suite = c->argv[0]; #endif /* TLS1_3_VERSION */ c = find_config_next(c, c->next, CONF_PARAM, "ProxyTLSCipherSuite", FALSE); } if (tls_cipher_suite == NULL) { tls_cipher_suite = PROXY_TLS_DEFAULT_CIPHER_SUITE; } SSL_CTX_set_cipher_list(ssl_ctx, tls_cipher_suite); #if OPENSSL_VERSION_NUMBER >= 0x10101000L && \ defined(TLS1_3_VERSION) if (tlsv13_cipher_suite != NULL) { SSL_CTX_set_ciphersuites(ssl_ctx, tlsv13_cipher_suite); } #endif /* TLS1_3_VERSION */ c = find_config(main_server->conf, CONF_PARAM, "ProxyTLSTimeoutHandshake", FALSE); if (c != NULL) { handshake_timeout = *((unsigned int *) c->argv[0]); } c = find_config(main_server->conf, CONF_PARAM, "ProxyTLSCACertificateFile", FALSE); if (c != NULL) { ca_file = c->argv[0]; } else { ca_file = PR_CONFIG_DIR "/cacerts.pem"; if (!file_exists2(p, ca_file)) { pr_trace_msg(trace_channel, 9, "warning: no default ProxyTLSCACertificateFile found at '%s'", ca_file); ca_file = NULL; } } c = find_config(main_server->conf, CONF_PARAM, "ProxyTLSCACertificatePath", FALSE); if (c != NULL) { ca_path = c->argv[0]; } if (ca_file != NULL || ca_path != NULL) { long verify_flags = 0; X509_VERIFY_PARAM *verify_param; /* Set the locations used for verifying certificates. */ PRIVS_ROOT if (SSL_CTX_load_verify_locations(ssl_ctx, ca_file, ca_path) != 1) { PRIVS_RELINQUISH (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to set CA verification using file '%s' or " "directory '%s': %s", ca_file ? ca_file : "(none)", ca_path ? ca_path : "(none)", proxy_tls_get_errors()); errno = EPERM; return -1; } PRIVS_RELINQUISH verify_param = X509_VERIFY_PARAM_new(); #if 0 /* NOTE: Many server certs may not have a CRL provider configured; such certs * would be deemed invalid/unusable by these CRL_CHECK flags. So they are * disabled, for now. */ # if defined(X509_V_FLAG_CRL_CHECK) verify_flags |= X509_V_FLAG_CRL_CHECK; # endif # if defined(X509_V_FLAG_CRL_CHECK_ALL) verify_flags |= X509_V_FLAG_CRL_CHECK_ALL; # endif #endif # if defined(X509_V_FLAG_TRUSTED_FIRST) verify_flags |= X509_V_FLAG_TRUSTED_FIRST; # endif # if defined(X509_V_FLAG_PARTIAL_CHAIN) verify_flags |= X509_V_FLAG_PARTIAL_CHAIN; # endif if (X509_VERIFY_PARAM_set_flags(verify_param, verify_flags) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error preparing X509 verification parameters: %s", proxy_tls_get_errors()); } else { if (SSL_CTX_set1_param(ssl_ctx, verify_param) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error setting X509 verification parameters: %s", proxy_tls_get_errors()); } } } else { /* Default to using locations set in the OpenSSL config file. */ pr_trace_msg(trace_channel, 9, "using default OpenSSL CA verification locations (see $SSL_CERT_DIR " "environment variable)"); if (SSL_CTX_set_default_verify_paths(ssl_ctx) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error setting default CA verification locations: %s", proxy_tls_get_errors()); } } c = find_config(main_server->conf, CONF_PARAM, "ProxyTLSCARevocationFile", FALSE); if (c != NULL) { crl_file = c->argv[0]; } c = find_config(main_server->conf, CONF_PARAM, "ProxyTLSCARevocationPath", FALSE); if (c != NULL) { crl_path = c->argv[0]; } if (crl_file != NULL || crl_path != NULL) { X509_STORE *crl_store; crl_store = X509_STORE_new(); if (crl_store == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error allocating CRL store: %s", proxy_tls_get_errors()); errno = EPERM; return -1; } if (X509_STORE_load_locations(crl_store, crl_file, crl_path) != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error loading ProxyTLSCARevocation files: %s", proxy_tls_get_errors()); } else { SSL_CTX_set_cert_store(ssl_ctx, crl_store); } } c = find_config(main_server->conf, CONF_PARAM, "ProxyTLSVerifyServer", FALSE); if (c != NULL) { tls_verify_server = *((int *) c->argv[0]); } c = find_config(main_server->conf, CONF_PARAM, "ProxyTLSCertificateFile", FALSE); if (c != NULL) { cert_file = c->argv[0]; } c = find_config(main_server->conf, CONF_PARAM, "ProxyTLSCertificateKeyFile", FALSE); if (c != NULL) { key_file = c->argv[0]; } else { key_file = cert_file; } if (cert_file != NULL) { int ok = TRUE; PRIVS_ROOT res = SSL_CTX_use_certificate_file(ssl_ctx, cert_file, SSL_FILETYPE_PEM); PRIVS_RELINQUISH if (res != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error loading certificate from ProxyTLSCertificateFile '%s': %s", cert_file, proxy_tls_get_errors()); ok = FALSE; } if (ok) { PRIVS_ROOT res = SSL_CTX_use_PrivateKey_file(ssl_ctx, key_file, SSL_FILETYPE_PEM); PRIVS_RELINQUISH if (res != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error loading private key from ProxyTLSCertificateKeyFile '%s': %s", key_file, proxy_tls_get_errors()); ok = FALSE; } } if (ok) { res = SSL_CTX_check_private_key(ssl_ctx); if (res != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "warning: ProxyTLSCertificateKeyFile '%s' private key does not " "match ProxyTLSCertificateFile '%s' certificate", key_file, cert_file); } } } # if defined(PSK_MAX_PSK_LEN) c = find_config(main_server->conf, CONF_PARAM, "ProxyTLSPreSharedKey", FALSE); if (c != NULL) { const char *identity, *path; pr_signals_handle(); identity = c->argv[0]; path = c->argv[1]; /* Advance past the "hex:" format prefix. */ path += 4; res = tls_load_psk(identity, path); if (res < 0) { pr_log_debug(DEBUG2, MOD_PROXY_VERSION ": error loading ProxyTLSPreSharedKey file '%s': %s", path, strerror(errno)); } } if (tls_psk_name != NULL) { pr_trace_msg(trace_channel, 9, "enabling support for PSK identities"); SSL_CTX_set_psk_client_callback(ssl_ctx, tls_psk_cb); } # endif /* PSK support */ # if !defined(OPENSSL_NO_TLSEXT) && defined(TLSEXT_STATUSTYPE_ocsp) SSL_CTX_set_tlsext_status_cb(ssl_ctx, tls_ocsp_response_cb); # endif /* OCSP support */ if (tls_opts & PROXY_TLS_OPT_ALLOW_WEAK_SECURITY) { # if OPENSSL_VERSION_NUMBER >= 0x10100000L SSL_CTX_set_security_level(ssl_ctx, 0); # endif /* OpenSSL-1.1.0 and later */ } if (tls_opts & PROXY_TLS_OPT_ENABLE_DIAGS) { SSL_CTX_set_info_callback(ssl_ctx, tls_info_cb); # if OPENSSL_VERSION_NUMBER > 0x000907000L SSL_CTX_set_msg_callback(ssl_ctx, tls_msg_cb); # endif } if (netio_install_ctrl() < 0) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error installing control connection proxy NetIO: %s", strerror(xerrno)); errno = xerrno; return -1; } #endif /* PR_USE_OPENSSL */ return 0; } int proxy_tls_sess_free(pool *p) { if (p == NULL) { errno = EINVAL; return -1; } #if defined(PR_USE_OPENSSL) /* Reset any state, but only if we have not already negotiated an SSL * session. */ if (session.rfc2228_mech == NULL) { handshake_timeout = 30; tls_opts = 0UL; tls_engine = PROXY_TLS_ENGINE_AUTO; tls_verify_server = TRUE; tls_cipher_suite = NULL; # if OPENSSL_VERSION_NUMBER >= 0x10101000L && \ defined(TLS1_3_VERSION) tlsv13_cipher_suite = NULL; # endif /* TLS1_3_VERSION */ # if defined(PSK_MAX_PSK_LEN) tls_psk_name = NULL; tls_psk_bn = NULL; tls_psk_used = FALSE; # endif /* PSK support */ if (tls_ds.dsh != NULL) { (void) (tls_ds.close)(p, tls_ds.dsh); tls_ds.dsh = NULL; } if (ssl_ctx != NULL) { if (init_ssl_ctx() < 0) { return -1; } } if (tls_ctrl_netio != NULL) { pr_netio_t *using_netio = NULL; if (proxy_netio_using(PR_NETIO_STRM_CTRL, &using_netio) == 0) { if (using_netio == tls_ctrl_netio) { proxy_netio_use(PR_NETIO_STRM_CTRL, NULL); } } destroy_pool(tls_ctrl_netio->pool); tls_ctrl_netio = NULL; } } #endif /* PR_USE_OPENSSL */ return 0; } proftpd-mod_proxy-0.9.5/lib/proxy/tls/000077500000000000000000000000001475737016700177735ustar00rootroot00000000000000proftpd-mod_proxy-0.9.5/lib/proxy/tls/db.c000066400000000000000000000324611475737016700205320ustar00rootroot00000000000000/* * ProFTPD - mod_proxy TLS Database implementation * Copyright (c) 2017-2021 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "proxy/db.h" #include "proxy/tls.h" #include "proxy/tls/db.h" #ifdef PR_USE_OPENSSL extern xaset_t *server_list; static const char *trace_channel = "proxy.tls.db"; #define PROXY_TLS_DB_SCHEMA_NAME "proxy_tls" #define PROXY_TLS_DB_SCHEMA_VERSION 3 static unsigned long db_opts = 0UL; static int tls_db_add_sess(pool *p, void *dbh, const char *key, SSL_SESSION *sess) { int res, vhost_id, xerrno = 0; const char *stmt, *errstr = NULL; BIO *bio; char *data = NULL; long datalen = 0; array_header *results; bio = BIO_new(BIO_s_mem()); BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); res = PEM_write_bio_SSL_SESSION(bio, sess); if (res != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error writing PEM-encoded SSL session data: %s", proxy_tls_get_errors()); } (void) BIO_flush(bio); datalen = BIO_get_mem_data(bio, &data); if (data == NULL) { pr_trace_msg(trace_channel, 9, "no PEM data found for SSL session, not caching"); BIO_free(bio); return 0; } data[datalen] = '\0'; if (db_opts & PROXY_TLS_OPT_ENABLE_DIAGS) { BIO *diags_bio; diags_bio = BIO_new(BIO_s_mem()); if (diags_bio != NULL) { if (SSL_SESSION_print(diags_bio, sess) == 1) { char *diags_data = NULL; long diags_datalen = 0; diags_datalen = BIO_get_mem_data(diags_bio, &diags_data); if (diags_data != NULL) { diags_data[diags_datalen] = '\0'; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.db] caching SSL session (%lu bytes):\n%s", (unsigned long) datalen, diags_data); } } } } /* We use INSERT OR REPLACE here to get upsert semantics; we only want/ * need one cached SSL session per URI. */ stmt = "INSERT OR REPLACE INTO proxy_tls_sessions (vhost_id, backend_uri, session) VALUES (?, ?, ?);"; res = proxy_db_prepare_stmt(p, dbh, stmt); if (res < 0) { xerrno = errno; BIO_free(bio); errno = xerrno; return -1; } vhost_id = main_server->sid; res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT, (void *) &vhost_id, 0); if (res < 0) { xerrno = errno; BIO_free(bio); errno = xerrno; return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT, (void *) key, -1); if (res < 0) { xerrno = errno; BIO_free(bio); errno = xerrno; return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 3, PROXY_DB_BIND_TYPE_TEXT, (void *) data, -1); if (res < 0) { xerrno = errno; BIO_free(bio); errno = xerrno; return -1; } results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr); if (results == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr ? errstr : strerror(errno)); BIO_free(bio); errno = EPERM; return -1; } BIO_free(bio); pr_trace_msg(trace_channel, 17, "cached SSL session for key '%s'", key); return 0; } static int tls_db_remove_sess(pool *p, void *dbh, const char *key) { int res, vhost_id; const char *stmt, *errstr = NULL; array_header *results; stmt = "DELETE FROM proxy_tls_sessions WHERE vhost_id = ? AND backend_uri = ?;"; res = proxy_db_prepare_stmt(p, dbh, stmt); if (res < 0) { return -1; } vhost_id = main_server->sid; res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT, (void *) &vhost_id, 0); if (res < 0) { return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT, (void *) key, -1); if (res < 0) { return -1; } results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr); if (results == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr ? errstr : strerror(errno)); errno = EPERM; return -1; } return 0; } static SSL_SESSION *tls_db_get_sess(pool *p, void *dbh, const char *key) { int res, vhost_id; BIO *bio; const char *stmt, *errstr = NULL; array_header *results; char *data = NULL; size_t datalen; SSL_SESSION *sess = NULL; stmt = "SELECT session FROM proxy_tls_sessions WHERE vhost_id = ? AND backend_uri = ?;"; res = proxy_db_prepare_stmt(p, dbh, stmt); if (res < 0) { return NULL; } vhost_id = main_server->sid; res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT, (void *) &vhost_id, 0); if (res < 0) { return NULL; } res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT, (void *) key, -1); if (res < 0) { return NULL; } results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr); if (results == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr ? errstr : strerror(errno)); errno = EPERM; return NULL; } if (results->nelts == 0) { errno = ENOENT; return NULL; } data = ((char **) results->elts)[0]; datalen = strlen(data) + 1; bio = BIO_new_mem_buf(data, datalen); sess = PEM_read_bio_SSL_SESSION(bio, NULL, 0, NULL); if (sess == NULL) { pr_trace_msg(trace_channel, 3, "error converting database entry to SSL session: %s", proxy_tls_get_errors()); } BIO_free(bio); if (sess == NULL) { errno = ENOENT; return NULL; } return sess; } static int tls_db_count_sess(pool *p, void *dbh) { int count = 0, res; const char *stmt, *errstr = NULL; array_header *results; stmt = "SELECT COUNT(*) FROM proxy_tls_sessions;"; res = proxy_db_prepare_stmt(p, dbh, stmt); if (res < 0) { return -1; } results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr); if (results == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr ? errstr : strerror(errno)); errno = EPERM; return -1; } if (results->nelts != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "expected 1 result from statement '%s', got %d", stmt, results->nelts); errno = EINVAL; return -1; } count = atoi(((char **) results->elts)[0]); return count; } /* Initialization routines */ static int tls_db_add_schema(pool *p, void *dbh, const char *db_path) { int res; const char *stmt, *errstr = NULL; /* CREATE TABLE proxy_tls_vhosts ( * vhost_id INTEGER NOT NULL PRIMARY KEY, * vhost_name TEXT NOT NULL * ); */ stmt = "CREATE TABLE IF NOT EXISTS proxy_tls_vhosts (vhost_id INTEGER NOT NULL PRIMARY KEY, vhost_name TEXT NOT NULL);"; res = proxy_db_exec_stmt(p, dbh, stmt, &errstr); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr); errno = EPERM; return -1; } /* CREATE TABLE proxy_tls_sessions ( * backend_uri STRING NOT NULL PRIMARY KEY, * vhost_id INTEGER NOT NULL, * session TEXT NOT NULL, * FOREIGN KEY (vhost_id) REFERENCES proxy_tls_vhosts (vhost_id) * ); */ stmt = "CREATE TABLE IF NOT EXISTS proxy_tls_sessions (backend_uri STRING NOT NULL PRIMARY KEY, vhost_id INTEGER NOT NULL, session TEXT NOT NULL, FOREIGN KEY (vhost_id) REFERENCES proxy_tls_hosts (vhost_id));"; res = proxy_db_exec_stmt(p, dbh, stmt, &errstr); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr); errno = EPERM; return -1; } /* Note that we deliberately do NOT truncate the session cache table. */ return 0; } static int tls_truncate_db_tables(pool *p, void *dbh) { int res; const char *stmt, *errstr = NULL; stmt = "DELETE FROM proxy_tls_vhosts;"; res = proxy_db_exec_stmt(p, dbh, stmt, &errstr); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr); errno = EPERM; return -1; } /* Note that we deliberately do NOT truncate the session cache table. */ return 0; } static int tls_db_add_vhost(pool *p, void *dbh, server_rec *s) { int res, xerrno = 0; const char *stmt, *errstr = NULL; array_header *results; stmt = "INSERT INTO proxy_tls_vhosts (vhost_id, vhost_name) VALUES (?, ?);"; res = proxy_db_prepare_stmt(p, dbh, stmt); if (res < 0) { xerrno = errno; (void) pr_log_debug(DEBUG3, MOD_PROXY_VERSION ": error preparing statement '%s': %s", stmt, strerror(xerrno)); errno = xerrno; return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT, (void *) &(s->sid), 0); if (res < 0) { return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT, (void *) s->ServerName, -1); if (res < 0) { return -1; } results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr); if (results == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error executing '%s': %s", stmt, errstr ? errstr : strerror(errno)); errno = EPERM; return -1; } return 0; } static int tls_db_init(pool *p, const char *tables_path, int flags) { int db_flags, res, xerrno = 0; server_rec *s; struct proxy_dbh *dbh = NULL; const char *db_path = NULL; if (tables_path == NULL) { errno = EINVAL; return -1; } db_path = pdircat(p, tables_path, "proxy-tls.db", NULL); db_flags = PROXY_DB_OPEN_FL_SCHEMA_VERSION_CHECK|PROXY_DB_OPEN_FL_INTEGRITY_CHECK|PROXY_DB_OPEN_FL_VACUUM; if (flags & PROXY_DB_OPEN_FL_SKIP_VACUUM) { /* If the caller needs us to skip the vacuum, we will. */ db_flags &= ~PROXY_DB_OPEN_FL_VACUUM; } PRIVS_ROOT dbh = proxy_db_open_with_version(p, db_path, PROXY_TLS_DB_SCHEMA_NAME, PROXY_TLS_DB_SCHEMA_VERSION, db_flags); xerrno = errno; PRIVS_RELINQUISH if (dbh == NULL) { (void) pr_log_pri(PR_LOG_NOTICE, MOD_PROXY_VERSION ": error opening database '%s' for schema '%s', version %u: %s", db_path, PROXY_TLS_DB_SCHEMA_NAME, PROXY_TLS_DB_SCHEMA_VERSION, strerror(xerrno)); errno = xerrno; return -1; } res = tls_db_add_schema(p, dbh, db_path); if (res < 0) { xerrno = errno; (void) pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": error creating schema in database '%s' for '%s': %s", db_path, PROXY_TLS_DB_SCHEMA_NAME, strerror(xerrno)); (void) proxy_db_close(p, dbh); errno = xerrno; return -1; } res = tls_truncate_db_tables(p, dbh); if (res < 0) { xerrno = errno; (void) proxy_db_close(p, dbh); errno = xerrno; return -1; } for (s = (server_rec *) server_list->xas_list; s; s = s->next) { res = tls_db_add_vhost(p, dbh, s); if (res < 0) { xerrno = errno; (void) pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": error adding database entry for server '%s' in '%s': %s", s->ServerName, PROXY_TLS_DB_SCHEMA_NAME, strerror(xerrno)); (void) proxy_db_close(p, dbh); errno = xerrno; return -1; } } (void) proxy_db_close(p, dbh); return 0; } static int tls_db_close(pool *p, void *dbh) { if (dbh != NULL) { if (proxy_db_close(p, dbh) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error closing %s database: %s", PROXY_TLS_DB_SCHEMA_NAME, strerror(errno)); } } return 0; } static void *tls_db_open(pool *p, const char *tables_dir, unsigned long opts) { int xerrno = 0; struct proxy_dbh *dbh; const char *db_path; db_path = pdircat(p, tables_dir, "proxy-tls.db", NULL); PRIVS_ROOT dbh = proxy_db_open_with_version(p, db_path, PROXY_TLS_DB_SCHEMA_NAME, PROXY_TLS_DB_SCHEMA_VERSION, 0); xerrno = errno; PRIVS_RELINQUISH if (dbh == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error opening database '%s' for schema '%s', version %u: %s", db_path, PROXY_TLS_DB_SCHEMA_NAME, PROXY_TLS_DB_SCHEMA_VERSION, strerror(xerrno)); errno = xerrno; return NULL; } db_opts = opts; return dbh; } #endif /* PR_USE_OPENSSL */ int proxy_tls_db_as_datastore(struct proxy_tls_datastore *ds, void *ds_data, size_t ds_datasz) { if (ds == NULL) { errno = EINVAL; return -1; } (void) ds_data; (void) ds_datasz; #ifdef PR_USE_OPENSSL ds->add_sess = tls_db_add_sess; ds->remove_sess = tls_db_remove_sess; ds->get_sess = tls_db_get_sess; ds->count_sess = tls_db_count_sess; ds->init = tls_db_init; ds->open = tls_db_open; ds->close = tls_db_close; #endif /* PR_USE_OPENSSL */ return 0; } proftpd-mod_proxy-0.9.5/lib/proxy/tls/redis.c000066400000000000000000000227241475737016700212540ustar00rootroot00000000000000/* * ProFTPD - mod_proxy TLS Redis implementation * Copyright (c) 2017-2020 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "redis.h" #include "proxy/tls.h" #include "proxy/tls/redis.h" #ifdef PR_USE_OPENSSL extern xaset_t *server_list; static const char *trace_channel = "proxy.tls.redis"; static void *redis_prefix = NULL; static size_t redis_prefixsz = 0; static unsigned long redis_opts = 0UL; static char *make_key(pool *p, unsigned int vhost_id) { char *key; size_t keysz; keysz = 64; key = pcalloc(p, keysz + 1); snprintf(key, keysz, "proxy_tls_sessions:vhost#%u", vhost_id); return key; } static int tls_redis_add_sess(pool *p, void *redis, const char *sess_key, SSL_SESSION *sess) { int res, xerrno = 0; pool *tmp_pool; char *key; BIO *bio; char *data = NULL; long datalen = 0; bio = BIO_new(BIO_s_mem()); BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); res = PEM_write_bio_SSL_SESSION(bio, sess); if (res != 1) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error writing PEM-encoded SSL session data: %s", proxy_tls_get_errors()); } (void) BIO_flush(bio); datalen = BIO_get_mem_data(bio, &data); if (data == NULL) { pr_trace_msg(trace_channel, 9, "no PEM data found for SSL session, not caching"); BIO_free(bio); return 0; } data[datalen] = '\0'; if (redis_opts & PROXY_TLS_OPT_ENABLE_DIAGS) { BIO *diags_bio; diags_bio = BIO_new(BIO_s_mem()); if (diags_bio != NULL) { if (SSL_SESSION_print(diags_bio, sess) == 1) { char *diags_data = NULL; long diags_datalen = 0; diags_datalen = BIO_get_mem_data(diags_bio, &diags_data); if (diags_data != NULL) { diags_data[diags_datalen] = '\0'; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.redis] caching SSL session (%lu bytes):\n%s", (unsigned long) datalen, diags_data); } } } } tmp_pool = make_sub_pool(p); key = make_key(tmp_pool, main_server->sid); res = pr_redis_hash_set(redis, &proxy_module, key, sess_key, data, datalen); xerrno = errno; if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error setting value for field '%s' in Redis hash '%s': %s", sess_key, key, strerror(xerrno)); destroy_pool(tmp_pool); BIO_free(bio); errno = xerrno; return -1; } pr_trace_msg(trace_channel, 17, "cached SSL session (%lu bytes) for key '%s'", (unsigned long) datalen, sess_key); destroy_pool(tmp_pool); BIO_free(bio); return 0; } static int tls_redis_remove_sess(pool *p, void *redis, const char *sess_key) { int res, xerrno; pool *tmp_pool; char *key; tmp_pool = make_sub_pool(p); key = make_key(tmp_pool, main_server->sid); res = pr_redis_hash_delete(redis, &proxy_module, key, sess_key); xerrno = errno; if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error deleting field '%s' from Redis hash '%s': %s", sess_key, key, strerror(xerrno)); destroy_pool(tmp_pool); errno = xerrno; return -1; } pr_trace_msg(trace_channel, 17, "removed cached SSL session for key '%s'", sess_key); destroy_pool(tmp_pool); return 0; } static SSL_SESSION *tls_redis_get_sess(pool *p, void *redis, const char *sess_key) { int res, xerrno; pool *tmp_pool; BIO *bio; char *key; char *data = NULL; size_t datalen = 0; SSL_SESSION *sess = NULL; tmp_pool = make_sub_pool(p); key = make_key(tmp_pool, main_server->sid); res = pr_redis_hash_get(tmp_pool, redis, &proxy_module, key, sess_key, (void **) &data, &datalen); xerrno = errno; if (res < 0) { if (xerrno != ENOENT) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error getting value for field '%s' from Redis hash '%s': %s", sess_key, key, strerror(xerrno)); } destroy_pool(tmp_pool); errno = xerrno; return NULL; } pr_trace_msg(trace_channel, 19, "retrieved cached session (%lu bytes) for key '%s'", (unsigned long) datalen, sess_key); bio = BIO_new_mem_buf((char *) data, datalen); sess = PEM_read_bio_SSL_SESSION(bio, NULL, 0, NULL); destroy_pool(tmp_pool); if (sess == NULL) { pr_trace_msg(trace_channel, 3, "error converting database entry to SSL session: %s", proxy_tls_get_errors()); } BIO_free(bio); if (sess == NULL) { errno = ENOENT; return NULL; } pr_trace_msg(trace_channel, 17, "retrieved cached SSL session for key '%s'", sess_key); return sess; } static int tls_redis_count_sess(pool *p, void *redis) { int res, xerrno; uint64_t count = 0; pool *tmp_pool; char *key; tmp_pool = make_sub_pool(p); key = make_key(tmp_pool, main_server->sid); res = pr_redis_hash_count(redis, &proxy_module, key, &count); xerrno = errno; if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error getting size of Redis hash '%s': %s", key, strerror(xerrno)); destroy_pool(tmp_pool); errno = xerrno; return -1; } destroy_pool(tmp_pool); return (int) count; } /* Initialization routines */ static int tls_redis_truncate_tables(pool *p, pr_redis_t *redis, unsigned int vhost_id) { register unsigned int i; int res, xerrno; pool *tmp_pool; const char *key; array_header *fields = NULL; tmp_pool = make_sub_pool(p); key = make_key(tmp_pool, vhost_id); res = pr_redis_hash_keys(tmp_pool, redis, &proxy_module, key, &fields); xerrno = errno; if (res < 0) { if (xerrno == ENOENT) { /* Ignore. */ res = 0; } else { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error obtaining fields from Redis hash '%s': %s", key, strerror(errno)); } destroy_pool(tmp_pool); errno = xerrno; return res; } pr_trace_msg(trace_channel, 17, "deleting %u %s for hash '%s'", fields->nelts, fields->nelts != 1 ? "fields" : "field", key); for (i = 0; i < fields->nelts; i++) { char *field; field = ((char **) fields->elts)[i]; pr_trace_msg(trace_channel, 17, "deleting field '%s' from Redis hash '%s'", field, key); res = pr_redis_hash_delete(redis, &proxy_module, key, field); if (res < 0) { pr_trace_msg(trace_channel, 4, "error deleting field '%s' from Redis hash '%s': %s", field, key, strerror(errno)); } } destroy_pool(tmp_pool); return 0; } static int tls_redis_init(pool *p, const char *tables_path, int flags) { int res, xerrno = 0; server_rec *s; pr_redis_t *redis = NULL; (void) tables_path; (void) flags; redis = pr_redis_conn_new(p, &proxy_module, 0); xerrno = errno; if (redis == NULL) { (void) pr_log_pri(PR_LOG_NOTICE, MOD_PROXY_VERSION ": error opening Redis connection: %s", strerror(xerrno)); errno = xerrno; return -1; } (void) pr_redis_conn_set_namespace(redis, &proxy_module, redis_prefix, redis_prefixsz); for (s = (server_rec *) server_list->xas_list; s; s = s->next) { res = tls_redis_truncate_tables(p, redis, s->sid); if (res < 0) { pr_trace_msg(trace_channel, 3, "error truncating Redis keys for server '%s': %s", s->ServerName, strerror(errno)); } } (void) pr_redis_conn_close(redis); return 0; } static int tls_redis_close(pool *p, void *redis) { if (redis != NULL) { if (pr_redis_conn_close(redis) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error closing Redis connection: %s", strerror(errno)); } } return 0; } static void *tls_redis_open(pool *p, const char *tables_dir, unsigned long opts) { int xerrno = 0; pr_redis_t *redis; redis = pr_redis_conn_new(p, &proxy_module, 0); xerrno = errno; if (redis == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error opening Redis connection: %s", strerror(xerrno)); errno = xerrno; return NULL; } (void) pr_redis_conn_set_namespace(redis, &proxy_module, redis_prefix, redis_prefixsz); redis_opts = opts; return redis; } #endif /* PR_USE_OPENSSL */ int proxy_tls_redis_as_datastore(struct proxy_tls_datastore *ds, void *ds_data, size_t ds_datasz) { if (ds == NULL) { errno = EINVAL; return -1; } #ifdef PR_USE_OPENSSL redis_prefix = ds_data; redis_prefixsz = ds_datasz; ds->add_sess = tls_redis_add_sess; ds->remove_sess = tls_redis_remove_sess; ds->get_sess = tls_redis_get_sess; ds->count_sess = tls_redis_count_sess; ds->init = tls_redis_init; ds->open = tls_redis_open; ds->close = tls_redis_close; #endif /* PR_USE_OPENSSL */ return 0; } proftpd-mod_proxy-0.9.5/lib/proxy/uri.c000066400000000000000000000240441475737016700201400ustar00rootroot00000000000000/* * ProFTPD - mod_proxy URI implementation * Copyright (c) 2012-2020 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_proxy.h" #include "proxy/uri.h" /* Relevant RFCs: * * RFC 1738: Uniform Resource Locators (obsolete) * RFC 3986: Uniform Resource Identifier - Generic Syntax */ static const char *trace_channel = "proxy.uri"; static char *uri_parse_host(pool *p, const char *orig_uri, const char *uri, char **remaining) { char *host = NULL, *ptr = NULL; /* We have either of: * * host<:...> * [host]<:...> * * Look for an opening square bracket, to see if we have an IPv6 address * in the URI. */ if (uri[0] == '[') { ptr = strchr(uri + 1, ']'); if (ptr == NULL) { /* If there is no ']', then it's a badly-formatted URI. */ pr_trace_msg(trace_channel, 4, "badly formatted IPv6 address in host info '%.100s'", orig_uri); errno = EINVAL; return NULL; } host = pstrndup(p, uri + 1, ptr - uri - 1); if (remaining != NULL) { size_t urilen; urilen = strlen(ptr); if (urilen > 0) { *remaining = ptr + 1; } else { *remaining = NULL; } } pr_trace_msg(trace_channel, 17, "parsed host '%s' out of URI '%s'", host, orig_uri); return host; } ptr = strchr(uri + 1, ':'); if (ptr == NULL) { if (remaining != NULL) { *remaining = NULL; } host = pstrdup(p, uri); pr_trace_msg(trace_channel, 17, "parsed host '%s' out of URI '%s'", host, orig_uri); return host; } if (remaining != NULL) { *remaining = ptr; } host = pstrndup(p, uri, ptr - uri); pr_trace_msg(trace_channel, 17, "parsed host '%s' out of URI '%s'", host, orig_uri); return host; } /* Determine whether "username:password@" are present. If so, then parse it * out, and return a pointer to the portion of the URI after the parsed-out * userinfo. */ static char *uri_parse_userinfo(pool *p, const char *orig_uri, const char *uri, char **username, char **password) { char *ptr, *ptr2, *rem_uri = NULL, *userinfo, *user = NULL, *passwd = NULL; /* We have either: * * host<:...> * [host]<:...> * * thus no user info, OR: * * username:password@host... * username:password@[host]... * username:@host... * username:pass@word@host... * user@domain.com:pass@word@host... * * all of which have at least one occurrence of the '@' character. */ ptr = strchr(uri, '@'); if (ptr == NULL) { /* No '@' character at all? No user info, then. */ if (username != NULL) { *username = NULL; } if (password != NULL) { *password = NULL; } return pstrdup(p, uri); } /* To handle the case where the password field might itself contain an * '@' character, we first search from the end for '@'. If found, then we * search for '@' from the beginning. If also found, AND if both ocurrences * are the same, then we have a plain "username:password@" string. * * Note that we can handle '@' characters within passwords (or usernames), * but we currently cannot handle ':' characters within usernames. */ ptr2 = strrchr(uri, '@'); if (ptr2 != NULL) { if (ptr != ptr2) { /* Use the last found '@' as the delimiter. */ ptr = ptr2; } } userinfo = pstrndup(p, uri, ptr - uri); rem_uri = ptr + 1; ptr = strchr(userinfo, ':'); if (ptr == NULL) { pr_trace_msg(trace_channel, 4, "badly formatted userinfo '%.100s' (missing ':' character) in " "URI '%.100s', ignoring", userinfo, orig_uri); if (username != NULL) { *username = NULL; } if (password != NULL) { *password = NULL; } return rem_uri; } user = pstrndup(p, userinfo, ptr - userinfo); if (username != NULL) { *username = user; } /* Watch for empty passwords. */ if (*(ptr+1) == '\0') { passwd = pstrdup(p, ""); } else { passwd = pstrdup(p, ptr + 1); } if (password != NULL) { *password = passwd; } pr_trace_msg(trace_channel, 17, "parsed username '%s', password '%s' out of URI '%s'", user, passwd, orig_uri); return rem_uri; } int proxy_uri_parse(pool *p, const char *uri, char **scheme, char **host, unsigned int *port, char **username, char **password) { char *ptr, *ptr2; size_t idx, len; if (uri == NULL || scheme == NULL || host == NULL || port == NULL) { errno = EINVAL; return -1; } /* First, look for a ':' */ ptr = strchr(uri, ':'); if (ptr == NULL) { pr_trace_msg(trace_channel, 4, "missing colon in URI '%.100s'", uri); errno = EINVAL; return -1; } len = (ptr - uri); *scheme = pstrndup(p, uri, len); idx = strspn(*scheme, "abcdefghijklmnopqrstuvwxyz+.-"); if (idx < len && (*scheme)[idx] != '\0') { /* Invalid character in the scheme string, according to RFC 1738 rules. */ pr_trace_msg(trace_channel, 4, "invalid character (%c) at index %lu in scheme '%.100s'", (*scheme)[idx], (unsigned long) idx, *scheme); errno = EINVAL; return -1; } /* The double-slashes must immediately follow the colon. */ if (*(ptr + 1) != '/' || *(ptr + 2) != '/') { pr_trace_msg(trace_channel, 4, "missing required '//' following colon in URI '%.100s'", uri); errno = EINVAL; return -1; } ptr += 3; if (*ptr == '\0') { /* The given URL looked like "scheme://". */ pr_trace_msg(trace_channel, 4, "missing required authority following '//' in URI '%.100s'", uri); errno = EINVAL; return -1; } /* Possible URIs at this point: * * scheme://host:port/path/... * scheme://host:port/ * scheme://host:port * scheme://host * scheme://username:password@host... * * And, in the case where 'host' is an IPv6 address: * * scheme://[host]:port/path/... * scheme://[host]:port/ * scheme://[host]:port * scheme://[host] * scheme://username:password@[host]... */ /* We explicitly do NOT support URL-encoded characters in the URIs we * will handle. */ ptr2 = strchr(ptr, '%'); if (ptr2 != NULL) { pr_trace_msg(trace_channel, 4, "invalid character (%%) at index %ld in scheme-specific info '%.100s'", (long) (ptr2 - ptr), ptr); errno = EINVAL; return -1; } ptr = uri_parse_userinfo(p, uri, ptr, username, password); ptr2 = strchr(ptr, ':'); if (ptr2 == NULL) { *host = uri_parse_host(p, uri, ptr, NULL); if (strcmp(*scheme, "ftp") == 0 || strcmp(*scheme, "ftps") == 0) { *port = 21; } else if (strcmp(*scheme, "sftp") == 0) { *port = 22; } else { if (pr_strnrstr(*scheme, 0, "+srv", 0, PR_STR_FL_IGNORE_CASE) != TRUE && pr_strnrstr(*scheme, 0, "+txt", 0, PR_STR_FL_IGNORE_CASE) != TRUE) { pr_trace_msg(trace_channel, 4, "unable to determine port for scheme '%.100s'", *scheme); errno = EINVAL; return -1; } } } else { *host = uri_parse_host(p, uri, ptr, &ptr2); } /* Optional port field present? */ if (ptr2 != NULL) { ptr2 = strchr(ptr2, ':'); } if (ptr2 == NULL) { /* XXX How to configure "implicit" FTPS, if at all? */ if (strcmp(*scheme, "ftp") == 0 || strcmp(*scheme, "ftps") == 0) { *port = 21; } else if (strcmp(*scheme, "sftp") == 0) { *port = 22; } else { if (pr_strnrstr(*scheme, 0, "+srv", 0, PR_STR_FL_IGNORE_CASE) != TRUE && pr_strnrstr(*scheme, 0, "+txt", 0, PR_STR_FL_IGNORE_CASE) != TRUE) { pr_trace_msg(trace_channel, 4, "unable to determine port for scheme '%.100s'", *scheme); errno = EINVAL; return -1; } } } else { register unsigned int i; char *ptr3, *portspec; size_t portspeclen; /* Look for any possible trailing '/'. */ ptr3 = strchr(ptr2, '/'); if (ptr3 == NULL) { portspec = ptr2 + 1; portspeclen = strlen(portspec); } else { portspeclen = ptr3 - (ptr2 + 1); portspec = pstrndup(p, ptr2 + 1, portspeclen); } /* Ensure that only numeric characters appear in the portspec. */ for (i = 0; i < portspeclen; i++) { if (isdigit((int) portspec[i]) == 0) { pr_trace_msg(trace_channel, 4, "invalid character (%c) at index %d in port specification '%.100s'", portspec[i], i, portspec); errno = EINVAL; return -1; } } /* The above check will rule out any negative numbers, since it will * reject the minus character. Thus we only need to check for a zero * port, or a number that's outside the 1-65535 range. */ *port = atoi(portspec); if (*port == 0 || *port >= 65536) { pr_trace_msg(trace_channel, 4, "port specification '%.100s' yields invalid port number %d", portspec, *port); errno = EINVAL; return -1; } } /* We deliberately ignore any configured for SRV, TXT scheme variants. * The ports to use will be obtained from the DNS records for such * schemes. */ if (pr_strnrstr(*scheme, 0, "+srv", 0, PR_STR_FL_IGNORE_CASE) == TRUE || pr_strnrstr(*scheme, 0, "+txt", 0, PR_STR_FL_IGNORE_CASE) == TRUE) { *port = 0; } return 0; } proftpd-mod_proxy-0.9.5/mod_proxy.c000066400000000000000000005272331475737016700174420ustar00rootroot00000000000000/* * ProFTPD - mod_proxy * Copyright (c) 2012-2025 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. * * -----DO NOT EDIT BELOW THIS LINE----- * $Archive: mod_proxy.a $ * $Libraries: -lsqlite3 -lssl -lcrypto$ */ #include "mod_proxy.h" #include "proxy/random.h" #include "proxy/db.h" #include "proxy/session.h" #include "proxy/conn.h" #include "proxy/netio.h" #include "proxy/inet.h" #include "proxy/ssh.h" #include "proxy/tls.h" #include "proxy/forward.h" #include "proxy/reverse.h" #include "proxy/ftp/conn.h" #include "proxy/ftp/ctrl.h" #include "proxy/ftp/data.h" #include "proxy/ftp/dirlist.h" #include "proxy/ftp/facts.h" #include "proxy/ftp/msg.h" #include "proxy/ftp/sess.h" #include "proxy/ftp/xfer.h" #include "proxy/ssh/ssh2.h" #include "proxy/ssh/auth.h" #include "proxy/ssh/crypto.h" #if defined(HAVE_OSSL_PROVIDER_LOAD_OPENSSL) # include #endif /* HAVE_OSSL_PROVIDER_LOAD_OPENSSL */ /* Proxy role */ #define PROXY_ROLE_REVERSE 1 #define PROXY_ROLE_FORWARD 2 /* How long (in secs) to wait to connect to real server? */ #define PROXY_CONNECT_DEFAULT_TIMEOUT 5 /* How long (in secs) to wait for the end-of-data-transfer response? */ #define PROXY_LINGER_DEFAULT_TIMEOUT 3 extern xaset_t *server_list; extern module xfer_module; /* From response.c */ extern pr_response_t *resp_list, *resp_err_list; module proxy_module; int proxy_logfd = -1; pool *proxy_pool = NULL; unsigned long proxy_opts = 0UL; unsigned int proxy_sess_state = 0U; int proxy_datastore = PROXY_DATASTORE_SQLITE; void *proxy_datastore_data = NULL; size_t proxy_datastore_datasz = 0; static int proxy_engine = FALSE; static unsigned int proxy_login_attempts = 0; static int proxy_role = PROXY_ROLE_REVERSE; static const char *proxy_tables_dir = NULL; static int proxy_tls_xfer_prot_policy = PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_REQUIRED; #if defined(HAVE_OSSL_PROVIDER_LOAD_OPENSSL) static OSSL_PROVIDER *legacy_provider = NULL; #endif /* HAVE_OSSL_PROVIDER_LOAD_OPENSSL */ static const char *trace_channel = "proxy"; /* Necessary function prototypes. */ static int proxy_sess_init(void); static void proxy_timeoutidle_ev(const void *, void *); static void proxy_timeoutnoxfer_ev(const void *, void *); static void proxy_timeoutstalled_ev(const void *, void *); static int recv_resp(cmd_rec *cmd, struct proxy_session *proxy_sess, pr_response_t **rp) { int res, xerrno = 0; pr_response_t *resp; unsigned int resp_nlines = 0; resp = proxy_ftp_ctrl_recv_resp(cmd->tmp_pool, proxy_sess->backend_ctrl_conn, &resp_nlines, 0); if (resp == NULL) { xerrno = errno; /* For a certain number of conditions, if we cannot read the response * from the backend, then we should just close the frontend, otherwise * we might "leak" to the client the fact that we are fronting some * backend server rather than being the server. */ if (xerrno == ECONNRESET || xerrno == ECONNABORTED || xerrno == ENOENT || xerrno == EPIPE) { pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION, "Backend control connection lost"); } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error receiving %s response from backend: %s", (char *) cmd->argv[0], strerror(xerrno)); pr_response_add_err(R_500, _("%s: %s"), (char *) cmd->argv[0], strerror(xerrno)); pr_response_flush(&resp_err_list); errno = xerrno; return -1; } res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool, proxy_sess->frontend_ctrl_conn, resp, resp_nlines); if (res < 0) { xerrno = errno; pr_response_block(TRUE); errno = xerrno; return -1; } if (rp != NULL) { *rp = resp; } return 0; } MODRET proxy_cmd(cmd_rec *cmd, struct proxy_session *proxy_sess, pr_response_t **rp) { int res, xerrno = 0; res = proxy_ftp_ctrl_send_cmd(cmd->tmp_pool, proxy_sess->backend_ctrl_conn, cmd); xerrno = errno; if (res < 0) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error sending %s to backend: %s", (char *) cmd->argv[0], strerror(xerrno)); pr_response_add_err(R_500, _("%s: %s"), (char *) cmd->argv[0], strerror(xerrno)); pr_response_flush(&resp_err_list); errno = xerrno; return PR_ERROR(cmd); } if (recv_resp(cmd, proxy_sess, rp) < 0) { return PR_ERROR(cmd); } pr_response_block(TRUE); return PR_HANDLED(cmd); } MODRET proxy_abort(cmd_rec *cmd, struct proxy_session *proxy_sess, pr_response_t **rp) { int res, xerrno = 0; res = proxy_ftp_ctrl_send_abort(cmd->tmp_pool, proxy_sess->backend_ctrl_conn, cmd); xerrno = errno; if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error sending %s to backend: %s", (char *) cmd->argv[0], strerror(xerrno)); pr_response_add_err(R_500, _("%s: %s"), (char *) cmd->argv[0], strerror(xerrno)); pr_response_flush(&resp_err_list); errno = xerrno; return PR_ERROR(cmd); } if (proxy_sess->backend_data_conn != NULL) { pr_trace_msg(trace_channel, 19, "received ABOR on frontend connection, " "closing backend data connection"); proxy_inet_close(session.pool, proxy_sess->backend_data_conn); pr_inet_close(session.pool, proxy_sess->backend_data_conn); proxy_sess->backend_data_conn = NULL; } if (recv_resp(cmd, proxy_sess, rp) < 0) { return PR_ERROR(cmd); } /* The ABOR command might have two responses, as when there is a data * transfer in progress: one response for the data transfer, and one for * handling the ABOR command itself. */ if ((proxy_sess->frontend_sess_flags & SF_XFER) || (proxy_sess->backend_sess_flags & SF_XFER)) { if (recv_resp(cmd, proxy_sess, rp) < 0) { return PR_ERROR(cmd); } } pr_response_block(TRUE); return PR_HANDLED(cmd); } MODRET proxy_data_cmd(cmd_rec *cmd, struct proxy_session *proxy_sess) { int res, xerrno = 0; modret_t *mr; pr_response_t *resp = NULL; unsigned int resp_nlines = 0; mr = proxy_cmd(cmd, proxy_sess, &resp); if (!MODRET_ISHANDLED(mr)) { pr_response_block(TRUE); return mr; } if (resp->num[0] != '1') { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "received non-1xx response from backend for %s: %s %s", (char *) cmd->argv[0], resp->num, resp->msg); pr_response_block(FALSE); pr_response_add_err(resp->num, "%s", resp->msg); pr_response_flush(&resp_err_list); pr_response_block(TRUE); errno = EINVAL; return PR_ERROR(cmd); } /* Now we wait for our closing response. */ resp = proxy_ftp_ctrl_recv_resp(cmd->tmp_pool, proxy_sess->backend_ctrl_conn, &resp_nlines, 0); if (resp == NULL) { xerrno = errno; pr_response_block(FALSE); /* For a certain number of conditions, if we cannot read the response * from the backend, then we should just close the frontend, otherwise * we might "leak" to the client the fact that we are fronting some * backend server rather than being the server. */ if (xerrno == ECONNRESET || xerrno == ECONNABORTED || xerrno == ENOENT || xerrno == EPIPE) { pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION, "Backend control connection lost"); } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error receiving %s response from backend: %s", (char *) cmd->argv[0], strerror(xerrno)); pr_response_add_err(R_500, _("%s: %s"), (char *) cmd->argv[0], strerror(xerrno)); pr_response_flush(&resp_err_list); pr_response_block(TRUE); errno = xerrno; return PR_ERROR(cmd); } pr_response_block(FALSE); res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool, proxy_sess->frontend_ctrl_conn, resp, resp_nlines); if (res < 0) { xerrno = errno; pr_response_block(TRUE); errno = xerrno; return PR_ERROR(cmd); } pr_response_block(TRUE); return PR_HANDLED(cmd); } static int proxy_stalled_timeout_cb(CALLBACK_FRAME) { int timeout_stalled; timeout_stalled = pr_data_get_timeout(PR_DATA_TIMEOUT_STALLED); pr_event_generate("core.timeout-stalled", NULL); pr_log_pri(PR_LOG_NOTICE, "Data transfer stall timeout: %d %s", timeout_stalled, timeout_stalled != 1 ? "seconds" : "second"); pr_session_disconnect(NULL, PR_SESS_DISCONNECT_TIMEOUT, "TimeoutStalled during data transfer"); /* Do not restart the timer (should never be reached). */ return 0; } static int proxy_mkdir(const char *dir, uid_t uid, gid_t gid, mode_t mode) { mode_t prev_mask; struct stat st; int res = -1; res = stat(dir, &st); if (res < 0 && errno != ENOENT) { return -1; } /* The directory already exists. */ if (res == 0) { return 0; } /* The given mode is absolute, not subject to any Umask setting. */ prev_mask = umask(0); if (mkdir(dir, mode) < 0) { int xerrno = errno; (void) umask(prev_mask); errno = xerrno; return -1; } umask(prev_mask); if (chown(dir, uid, gid) < 0) { return -1; } return 0; } static int proxy_mkpath(pool *p, const char *path, uid_t uid, gid_t gid, mode_t mode) { char *currpath = NULL, *tmppath = NULL; struct stat st; if (stat(path, &st) == 0) { /* Path already exists, nothing to be done. */ errno = EEXIST; return -1; } tmppath = pstrdup(p, path); currpath = "/"; while (tmppath && *tmppath) { char *currdir = strsep(&tmppath, "/"); currpath = pdircat(p, currpath, currdir, NULL); if (proxy_mkdir(currpath, uid, gid, mode) < 0) { return -1; } pr_signals_handle(); } return 0; } static void proxy_remove_symbols(void) { int res; /* Remove mod_xfer's PRE_CMD handlers; they will only interfere * with proxied data transfers (see GitHub issue #3). */ res = pr_stash_remove_cmd(C_APPE, &xfer_module, PRE_CMD, NULL, -1); if (res < 0) { pr_trace_msg(trace_channel, 1, "error removing PRE_CMD APPE mod_xfer handler: %s", strerror(errno)); } else { pr_trace_msg(trace_channel, 9, "removed PRE_CMD APPE mod_xfer handlers"); } res = pr_stash_remove_cmd(C_RETR, &xfer_module, PRE_CMD, NULL, -1); if (res < 0) { pr_trace_msg(trace_channel, 1, "error removing PRE_CMD RETR mod_xfer handler: %s", strerror(errno)); } else { pr_trace_msg(trace_channel, 9, "removed PRE_CMD RETR mod_xfer handlers"); } res = pr_stash_remove_cmd(C_STOR, &xfer_module, PRE_CMD, NULL, -1); if (res < 0) { pr_trace_msg(trace_channel, 1, "error removing PRE_CMD STOR mod_xfer handler: %s", strerror(errno)); } else { pr_trace_msg(trace_channel, 9, "removed PRE_CMD STOR mod_xfer handlers"); } res = pr_stash_remove_cmd(C_STOU, &xfer_module, PRE_CMD, NULL, -1); if (res < 0) { pr_trace_msg(trace_channel, 1, "error removing PRE_CMD STOU mod_xfer handler: %s", strerror(errno)); } else { pr_trace_msg(trace_channel, 9, "removed PRE_CMD STOU mod_xfer handlers"); } } static void proxy_restrict_session(void) { const char *proxy_chroot = NULL; config_rec *c; char *xferlog = PR_XFERLOG_PATH; /* Open the TransferLog here, BEFORE we chroot. */ c = find_config(main_server->conf, CONF_PARAM, "TransferLog", FALSE); if (c != NULL) { xferlog = c->argv[0]; } PRIVS_ROOT if (strcasecmp(xferlog, "none") == 0) { xferlog_open(NULL); } else { xferlog_open(xferlog); } if (getuid() == PR_ROOT_UID) { int res; /* Chroot to the ProxyTables/empty/ directory before dropping root privs. */ proxy_chroot = pdircat(proxy_pool, proxy_tables_dir, "empty", NULL); res = chroot(proxy_chroot); if (res < 0) { int xerrno = errno; PRIVS_RELINQUISH (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to chroot to ProxyTables/empty/ directory '%s': %s", proxy_chroot, strerror(xerrno)); pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_MODULE_ACL, "Unable to chroot proxy session"); } else { pr_trace_msg(trace_channel, 9, "chrooted session to '%s'", proxy_chroot); } if (chdir("/") < 0) { int xerrno = errno; PRIVS_RELINQUISH (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to chdir to root directory within chroot: %s", strerror(xerrno)); pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_MODULE_ACL, "Unable to chroot proxy session"); } } /* Make the proxy session process have the identity of the configured daemon * User/Group. */ PRIVS_REVOKE session.disable_id_switching = TRUE; if (proxy_chroot != NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "proxy session running as UID %lu, GID %lu, restricted to '%s'", (unsigned long) getuid(), (unsigned long) getgid(), proxy_chroot); } else { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "proxy session running as UID %lu, GID %lu, located in '%s'", (unsigned long) getuid(), (unsigned long) getgid(), getcwd(NULL, 0)); } } /* Configuration handlers */ /* usage: ProxyDataTransferPolicy "active"|"passive"|"pasv"|"epsv"|"port"| * "eprt"|"client" */ MODRET set_proxydataxferpolicy(cmd_rec *cmd) { config_rec *c; int cmd_id = -1; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); cmd_id = pr_cmd_get_id(cmd->argv[1]); if (cmd_id < 0) { if (strncasecmp(cmd->argv[1], "active", 7) == 0) { /* Try to use EPRT over PORT. */ cmd_id = PR_CMD_EPRT_ID; } else if (strncasecmp(cmd->argv[1], "passive", 8) == 0) { /* Try to use EPSV over PASV. */ cmd_id = PR_CMD_EPSV_ID; } else if (strncasecmp(cmd->argv[1], "client", 7) == 0) { cmd_id = 0; } else { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unsupported DataTransferPolicy: ", (char *) cmd->argv[1], NULL)); } } if (cmd_id != PR_CMD_PASV_ID && cmd_id != PR_CMD_EPSV_ID && cmd_id != PR_CMD_PORT_ID && cmd_id != PR_CMD_EPRT_ID && cmd_id != 0) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unsupported DataTransferPolicy: ", (char *) cmd->argv[1], NULL)); } c = add_config_param(cmd->argv[0], 1, NULL); c->argv[0] = palloc(c->pool, sizeof(int)); *((int *) c->argv[0]) = cmd_id; return PR_HANDLED(cmd); } /* usage: ProxyDatastore type [info] */ MODRET set_proxydatastore(cmd_rec *cmd) { int ds = -1; const char *ds_name = NULL; void *ds_data = NULL; size_t ds_datasz = 0; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT); ds_name = cmd->argv[1]; if (strcasecmp(ds_name, "sqlite") == 0) { ds = PROXY_DATASTORE_SQLITE; ds_data = NULL; ds_datasz = 0; #ifdef PR_USE_REDIS } else if (strcasecmp(ds_name, "redis") == 0) { if (cmd->argc != 3) { CONF_ERROR(cmd, "missing required Redis key prefix"); } ds = PROXY_DATASTORE_REDIS; ds_data = pstrdup(proxy_pool, cmd->argv[2]); ds_datasz = strlen(ds_data); #endif /* PR_USE_REDIS */ } else { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unsupported ProxyDatastore: ", ds_name, NULL)); } proxy_datastore = ds; proxy_datastore_data = ds_data; proxy_datastore_datasz = ds_datasz; return PR_HANDLED(cmd); } /* usage: ProxyDirectoryListPolicy "client"|"LIST" [opt1 ... ]*/ MODRET set_proxydirlistpolicy(cmd_rec *cmd) { config_rec *c; int policy_id = -1; unsigned long opts = 0UL; if (cmd->argc < 2) { CONF_ERROR(cmd, "wrong number of parameters"); } CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); if (strcasecmp(cmd->argv[1], "client") == 0) { policy_id = PROXY_SESS_DIRECTORY_LIST_POLICY_DEFAULT; } else if (strcasecmp(cmd->argv[1], "LIST") == 0) { policy_id = PROXY_SESS_DIRECTORY_LIST_POLICY_LIST; } else { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unsupported DirectoryListPolicy: ", (char *) cmd->argv[1], NULL)); } if (cmd->argc > 2) { register unsigned int i; for (i = 2; i < cmd->argc; i++) { if (strcasecmp(cmd->argv[i], "UseSlink") == 0) { opts |= PROXY_FTP_DIRLIST_OPT_USE_SLINK; } else { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unknown DirectoryListPolicy option: ", (char *) cmd->argv[i], NULL)); } } } c = add_config_param(cmd->argv[0], 2, NULL, NULL); c->argv[0] = palloc(c->pool, sizeof(int)); *((int *) c->argv[0]) = policy_id; c->argv[1] = palloc(c->pool, sizeof(unsigned long)); *((unsigned long *) c->argv[1]) = opts; return PR_HANDLED(cmd); } /* usage: ProxyEngine on|off */ MODRET set_proxyengine(cmd_rec *cmd) { int engine = 1; config_rec *c; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); engine = get_boolean(cmd, 1); if (engine == -1) { CONF_ERROR(cmd, "expected Boolean parameter"); } c = add_config_param(cmd->argv[0], 1, NULL); c->argv[0] = pcalloc(c->pool, sizeof(int)); *((int *) c->argv[0]) = engine; return PR_HANDLED(cmd); } /* usage: ProxyForwardEnabled on|off */ MODRET set_proxyforwardenabled(cmd_rec *cmd) { int enabled = -1, *note = NULL, res; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_CLASS); enabled = get_boolean(cmd, 1); if (enabled < 0) { CONF_ERROR(cmd, "expected Boolean parameter"); } /* Stash this setting in the notes for this class. */ note = palloc(cmd->server->pool, sizeof(int)); *note = enabled; res = pr_class_add_note(PROXY_FORWARD_ENABLED_NOTE, note, sizeof(int)); if (res < 0) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error storing parameter: ", strerror(errno), NULL)); } return PR_HANDLED(cmd); } /* usage: ProxyForwardMethod method */ MODRET set_proxyforwardmethod(cmd_rec *cmd) { config_rec *c; int forward_method = -1; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); forward_method = proxy_forward_get_method(cmd->argv[1]); if (forward_method < 0) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unknown/unsupported forward method: ", (char *) cmd->argv[1], NULL)); } c = add_config_param(cmd->argv[0], 1, NULL); c->argv[0] = palloc(c->pool, sizeof(int)); *((int *) c->argv[0]) = forward_method; return PR_HANDLED(cmd); } /* usage: ProxyForwardTo [!]pattern [flags] */ MODRET set_proxyforwardto(cmd_rec *cmd) { #ifdef PR_USE_REGEX config_rec *c; pr_regex_t *pre = NULL; int negated = FALSE, regex_flags = REG_EXTENDED|REG_NOSUB, res = 0; char *pattern; if (cmd->argc-1 < 1 || cmd->argc-1 > 2) { CONF_ERROR(cmd, "bad number of parameters"); } CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); /* Make sure that, if present, the flags parameter is correctly formatted. */ if (cmd->argc-1 == 2) { int flags = 0; /* We need to parse the flags parameter here, to see if any flags which * affect the compilation of the regex (e.g. NC) are present. */ flags = pr_filter_parse_flags(cmd->tmp_pool, cmd->argv[2]); if (flags < 0) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": badly formatted flags parameter: '", (char *) cmd->argv[2], "'", NULL)); } if (flags == 0) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown filter flags '", (char *) cmd->argv[2], "'", NULL)); } regex_flags |= flags; } pre = pr_regexp_alloc(&proxy_module); pattern = cmd->argv[1]; if (*pattern == '!') { negated = TRUE; pattern++; } res = pr_regexp_compile(pre, pattern, regex_flags); if (res != 0) { char errstr[200] = {'\0'}; pr_regexp_error(res, pre, errstr, sizeof(errstr)); pr_regexp_free(NULL, pre); CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "'", pattern, "' failed regex " "compilation: ", errstr, NULL)); } c = add_config_param(cmd->argv[0], 2, pre, NULL); c->argv[1] = palloc(c->pool, sizeof(int)); *((int *) c->argv[1]) = negated; return PR_HANDLED(cmd); #else /* no regular expression support at the moment */ CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "The ", param, " directive cannot be " "used on this system, as you do not have POSIX compliant regex support", NULL)); #endif } /* usage: ProxyLog path|"none" */ MODRET set_proxylog(cmd_rec *cmd) { CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); (void) add_config_param_str(cmd->argv[0], 1, cmd->argv[1]); return PR_HANDLED(cmd); } /* usage: ProxyOptions opt1 ... optN */ MODRET set_proxyoptions(cmd_rec *cmd) { register unsigned int i; config_rec *c; unsigned long opts = 0UL; if (cmd->argc-1 == 0) { CONF_ERROR(cmd, "wrong number of parameters"); } CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); c = add_config_param(cmd->argv[0], 1, NULL); for (i = 1; i < cmd->argc; i++) { if (strcmp(cmd->argv[i], "UseProxyProtocol") == 0 || strcmp(cmd->argv[i], "UseProxyProtocolV1") == 0) { opts |= PROXY_OPT_USE_PROXY_PROTOCOL_V1; } else if (strcmp(cmd->argv[i], "UseProxyProtocolV2") == 0) { opts |= PROXY_OPT_USE_PROXY_PROTOCOL_V2; } else if (strcmp(cmd->argv[i], "UseProxyProtocolV2TLVs") == 0) { opts |= PROXY_OPT_USE_PROXY_PROTOCOL_V2_TLVS; } else if (strcmp(cmd->argv[i], "ShowFeatures") == 0) { opts |= PROXY_OPT_SHOW_FEATURES; } else if (strcmp(cmd->argv[i], "UseReverseProxyAuth") == 0) { opts |= PROXY_OPT_USE_REVERSE_PROXY_AUTH; } else if (strcmp(cmd->argv[i], "UseDirectDataTransfers") == 0) { opts |= PROXY_OPT_USE_DIRECT_DATA_TRANSFERS; } else if (strcmp(cmd->argv[i], "IgnoreConfigPerms") == 0) { opts |= PROXY_OPT_IGNORE_CONFIG_PERMS; } else if (strcmp(cmd->argv[i], "AllowForeignAddress") == 0) { opts |= PROXY_OPT_ALLOW_FOREIGN_ADDRESS; } else if (strcmp(cmd->argv[i], "IgnoreForeignAddress") == 0) { opts |= PROXY_OPT_IGNORE_FOREIGN_ADDRESS; } else { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown ProxyOption '", (char *) cmd->argv[i], "'", NULL)); } } c->argv[0] = pcalloc(c->pool, sizeof(unsigned long)); *((unsigned long *) c->argv[0]) = opts; if (pr_module_exists("mod_ifsession.c")) { /* These are needed in case this directive is used with mod_ifsession * configuration. */ c->flags |= CF_MULTI; } return PR_HANDLED(cmd); } /* usage: ProxyRetryCount count */ MODRET set_proxyretrycount(cmd_rec *cmd) { config_rec *c; int retry_count = -1; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); retry_count = atoi(cmd->argv[1]); if (retry_count < 1) { CONF_ERROR(cmd, "retry count must be one or more"); } c = add_config_param(cmd->argv[0], 1, NULL); c->argv[0] = palloc(c->pool, sizeof(int)); *((int *) c->argv[0]) = retry_count; return PR_HANDLED(cmd); } /* usage: ProxyReverseConnectPolicy [policy] */ MODRET set_proxyreverseconnectpolicy(cmd_rec *cmd) { config_rec *c; int connect_policy = -1; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); connect_policy = proxy_reverse_connect_get_policy_id(cmd->argv[1]); if (connect_policy < 0) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unknown/unsupported connect policy: ", (char *) cmd->argv[1], NULL)); } c = add_config_param(cmd->argv[0], 1, NULL); c->argv[0] = palloc(c->pool, sizeof(int)); *((int *) c->argv[0]) = connect_policy; return PR_HANDLED(cmd); } /* usage: ProxyReverseServers server1 ... server N * file:/path/to/server/list.txt * sql:/SQLNamedQuery */ MODRET set_proxyreverseservers(cmd_rec *cmd) { config_rec *c; array_header *backend_servers; char *uri = NULL; unsigned int flags = PROXY_CONN_CREATE_FL_USE_DNS_TTL; if (cmd->argc-1 < 1) { CONF_ERROR(cmd, "wrong number of parameters"); } CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); c = add_config_param(cmd->argv[0], 2, NULL, NULL); backend_servers = make_array(c->pool, 1, sizeof(struct proxy_conn *)); if (cmd->argc-1 == 1) { /* We are dealing with one of the following possibilities: * * file:/path/to/file.txt * sql://SQLNamedQuery/... * */ if (strncmp(cmd->argv[1], "file:", 5) == 0) { char *param, *path; param = cmd->argv[1]; path = param + 5; /* If the path contains the %U or %g variables, then defer loading of * this file until the USER name is known. */ if (strstr(path, "%U") == NULL && strstr(path, "%g") == NULL) { int xerrno; /* For now, load the list of servers at sess init time. In * the future, we will want to load it at postparse time, mapped * to the appropriate server_rec, and clear/reload on 'core.restart'. */ PRIVS_ROOT backend_servers = proxy_reverse_json_parse_uris(cmd->server->pool, path, flags); xerrno = errno; PRIVS_RELINQUISH if (backend_servers == NULL) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error reading ProxyReverseServers file '", path, "': ", strerror(xerrno), NULL)); } if (backend_servers->nelts == 0) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "no usable URLs found in file '", path, "'", NULL)); } } else { /* Only provide a URI for dynamic lookup, e.g. per-user/group/etc. */ uri = cmd->argv[1]; } } else if (strncmp(cmd->argv[1], "sql:/", 5) == 0) { /* Unfortunately there's not very much we can do to validate these * SQL URIs at the moment. They point to a SQLNamedQuery, which * may not have been parsed yet from the config file, or which may be * in a scope. Thus we simply store them for now, and * let the lookup routines do the necessary validation. */ uri = cmd->argv[1]; } else { /* Treat it as a server-spec (i.e. a URI) */ const struct proxy_conn *pconn; pconn = proxy_conn_create(c->pool, cmd->argv[1], flags); if (pconn == NULL) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error parsing '", (char *) cmd->argv[1], "': ", strerror(errno), NULL)); } *((const struct proxy_conn **) push_array(backend_servers)) = pconn; } } else { register unsigned int i; /* More than one parameter, which means they are all URIs. */ for (i = 1; i < cmd->argc; i++) { const struct proxy_conn *pconn; pconn = proxy_conn_create(c->pool, cmd->argv[i], flags); if (pconn == NULL) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error parsing '", (char *) cmd->argv[i], "': ", strerror(errno), NULL)); } *((const struct proxy_conn **) push_array(backend_servers)) = pconn; } } c->argv[0] = backend_servers; if (uri != NULL) { c->argv[1] = pstrdup(c->pool, uri); } if (pr_module_exists("mod_ifsession.c")) { /* These are needed in case this directive is used with mod_ifsession * configuration. */ c->flags |= CF_MULTI; } return PR_HANDLED(cmd); } /* usage: ProxyRole "forward"|"reverse" */ MODRET set_proxyrole(cmd_rec *cmd) { int role = 0; config_rec *c; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); if (strcasecmp(cmd->argv[1], "forward") == 0) { role = PROXY_ROLE_FORWARD; } else if (strcasecmp(cmd->argv[1], "reverse") == 0) { role = PROXY_ROLE_REVERSE; } else { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unknown proxy role '", (char *) cmd->argv[1], "'", NULL)); } c = add_config_param(cmd->argv[0], 1, NULL); c->argv[0] = palloc(c->pool, sizeof(int)); *((int *) c->argv[0]) = role; return PR_HANDLED(cmd); } /* usage: ProxySFTPCiphers algos */ MODRET set_proxysftpciphers(cmd_rec *cmd) { #if defined(PR_USE_OPENSSL) register unsigned int i; config_rec *c; if (cmd->argc < 2) { CONF_ERROR(cmd, "Wrong number of parameters"); } CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); for (i = 1; i < cmd->argc; i++) { if (proxy_ssh_crypto_get_cipher(cmd->argv[i], NULL, NULL, NULL) == NULL) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unsupported cipher algorithm: ", (char *) cmd->argv[i], NULL)); } } c = add_config_param(cmd->argv[0], cmd->argc-1, NULL); for (i = 1; i < cmd->argc; i++) { c->argv[i-1] = pstrdup(c->pool, cmd->argv[i]); } return PR_HANDLED(cmd); #else CONF_ERROR(cmd, "Use of the ProxySFTPCiphers directive requires OpenSSL support (--enable-openssl)"); #endif /* PR_USE_OPENSSL */ } /* usage: ProxySFTPCompression on|off|delayed */ MODRET set_proxysftpcompression(cmd_rec *cmd) { config_rec *c; int comp; if (cmd->argc != 2) { CONF_ERROR(cmd, "Wrong number of parameters"); } CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); #if defined(HAVE_ZLIB_H) comp = get_boolean(cmd, 1); if (comp == -1) { if (strcasecmp(cmd->argv[1], "delayed") != 0) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unknown compression setting: ", cmd->argv[1], NULL)); } comp = 2; } #else pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": platform lacks zlib support, ignoring ProxySFTPCompression"); comp = 0; #endif /* !HAVE_ZLIB_H */ c = add_config_param(cmd->argv[0], 1, NULL); c->argv[0] = pcalloc(c->pool, sizeof(int)); *((int *) c->argv[0]) = comp; return PR_HANDLED(cmd); } /* usage: ProxySFTPDigests algos */ MODRET set_proxysftpdigests(cmd_rec *cmd) { #if defined(PR_USE_OPENSSL) register unsigned int i; config_rec *c; if (cmd->argc < 2) { CONF_ERROR(cmd, "Wrong number of parameters"); } CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); for (i = 1; i < cmd->argc; i++) { if (proxy_ssh_crypto_get_digest(cmd->argv[i], NULL) == NULL) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unsupported digest algorithm: ", cmd->argv[i], NULL)); } } c = add_config_param(cmd->argv[0], cmd->argc-1, NULL); for (i = 1; i < cmd->argc; i++) { c->argv[i-1] = pstrdup(c->pool, cmd->argv[i]); } return PR_HANDLED(cmd); #else CONF_ERROR(cmd, "Use of the ProxySFTPDigests directive requires OpenSSL support (--enable-openssl)"); #endif /* PR_USE_OPENSSL */ } /* usage: ProxySFTPHostKey path|"agent:/..." */ MODRET set_proxysftphostkey(cmd_rec *cmd) { struct stat st; int flags = 0; config_rec *c; const char *path = NULL; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); path = cmd->argv[1]; if (strncmp(cmd->argv[1], "agent:", 6) != 0 && flags == 0) { int res, xerrno; path = cmd->argv[1]; if (*path != '/') { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "must be an absolute path: ", path, NULL)); } PRIVS_ROOT res = stat(path, &st); xerrno = errno; PRIVS_RELINQUISH if (res < 0) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable to check '", path, "': ", strerror(xerrno), NULL)); } if ((st.st_mode & S_IRWXG) || (st.st_mode & S_IRWXO)) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable to use '", path, "' as host key, as it is group- or world-accessible", NULL)); } } c = add_config_param_str(cmd->argv[0], 2, NULL, NULL); c->argv[0] = pstrdup(c->pool, path); c->argv[1] = palloc(c->pool, sizeof(int)); *((int *) c->argv[1]) = flags; return PR_HANDLED(cmd); } /* usage: ProxySFTPKeyExchanges algos */ MODRET set_proxysftpkeyexchanges(cmd_rec *cmd) { #if defined(PR_USE_OPENSSL) register unsigned int i; config_rec *c; char *exchanges = ""; if (cmd->argc < 2) { CONF_ERROR(cmd, "Wrong number of parameters"); } CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); for (i = 1; i < cmd->argc; i++) { if (strcmp(cmd->argv[i], "diffie-hellman-group1-sha1") != 0 && strcmp(cmd->argv[i], "diffie-hellman-group14-sha1") != 0 && #if (OPENSSL_VERSION_NUMBER > 0x000907000L && defined(OPENSSL_FIPS)) || \ (OPENSSL_VERSION_NUMBER > 0x000908000L) strcmp(cmd->argv[i], "diffie-hellman-group14-sha256") != 0 && strcmp(cmd->argv[i], "diffie-hellman-group16-sha512") != 0 && strcmp(cmd->argv[i], "diffie-hellman-group18-sha512") != 0 && strcmp(cmd->argv[i], "diffie-hellman-group-exchange-sha256") != 0 && #endif strcmp(cmd->argv[i], "diffie-hellman-group-exchange-sha1") != 0 && #if defined(PR_USE_OPENSSL_ECC) strcmp(cmd->argv[i], "ecdh-sha2-nistp256") != 0 && strcmp(cmd->argv[i], "ecdh-sha2-nistp384") != 0 && strcmp(cmd->argv[i], "ecdh-sha2-nistp521") != 0 && #endif /* PR_USE_OPENSSL_ECC */ #if defined(HAVE_SODIUM_H) && defined(HAVE_SHA256_OPENSSL) strcmp(cmd->argv[i], "curve25519-sha256") != 0 && strcmp(cmd->argv[i], "curve25519-sha256@libssh.org") != 0 && #endif /* HAVE_SODIUM_H and HAVE_SHA256_OPENSSL */ #if defined(HAVE_X448_OPENSSL) && defined(HAVE_SHA512_OPENSSL) strcmp(cmd->argv[i], "curve448-sha512") != 0 && #endif /* HAVE_X448_OPENSSL and HAVE_SHA512_OPENSSL */ strcmp(cmd->argv[i], "rsa1024-sha1") != 0) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unsupported key exchange algorithm: ", cmd->argv[i], NULL)); } } c = add_config_param(cmd->argv[0], 1, NULL); for (i = 1; i < cmd->argc; i++) { exchanges = pstrcat(c->pool, exchanges, *exchanges ? "," : "", cmd->argv[i], NULL); } c->argv[0] = exchanges; return PR_HANDLED(cmd); #else CONF_ERROR(cmd, "Use of the ProxySFTPKeyExchanges directive requires OpenSSL support (--enable-openssl)"); #endif /* PR_USE_OPENSSL */ } /* usage: ProxySFTPOptions opts */ MODRET set_proxysftpoptions(cmd_rec *cmd) { register unsigned int i; config_rec *c; unsigned long opts = 0UL; if (cmd->argc-1 == 0) { CONF_ERROR(cmd, "wrong number of parameters"); } CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); c = add_config_param(cmd->argv[0], 1, NULL); for (i = 1; i < cmd->argc; i++) { if (strcmp(cmd->argv[i], "OldProtocolCompat") == 0) { opts |= PROXY_OPT_SSH_OLD_PROTO_COMPAT; /* This option also automatically enables PessimisticKexint, * as per the comments in RFC4253, Section 5.1. */ opts |= PROXY_OPT_SSH_PESSIMISTIC_KEXINIT; } else if (strcmp(cmd->argv[i], "PessimisticKexinit") == 0) { opts |= PROXY_OPT_SSH_PESSIMISTIC_KEXINIT; } else if (strcmp(cmd->argv[i], "AllowWeakDH") == 0) { opts |= PROXY_OPT_SSH_ALLOW_WEAK_DH; } else if (strcmp(cmd->argv[i], "NoExtensionNegotiation") == 0) { opts |= PROXY_OPT_SSH_NO_EXT_INFO; } else if (strcmp(cmd->argv[i], "NoHostkeyRotation") == 0) { opts |= PROXY_OPT_SSH_NO_HOSTKEY_ROTATION; } else if (strcmp(cmd->argv[i], "NoStrictKex") == 0) { opts |= PROXY_OPT_SSH_NO_STRICT_KEX; } else { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown ProxySFTPOption '", cmd->argv[i], "'", NULL)); } } c->argv[0] = pcalloc(c->pool, sizeof(unsigned long)); *((unsigned long *) c->argv[0]) = opts; if (pr_module_exists("mod_ifsession.c")) { /* These are needed in case this directive is used with mod_ifsession * configuration. */ c->flags |= CF_MULTI; } return PR_HANDLED(cmd); } /* usage: ProxySFTPPassPhraseProvider path */ MODRET set_proxysftppassphraseprovider(cmd_rec *cmd) { struct stat st; char *path; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT); path = cmd->argv[1]; if (*path != '/') { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "must be a full path: '", path, "'", NULL)); } if (stat(path, &st) < 0) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error checking '", path, "': ", strerror(errno), NULL)); } if (!S_ISREG(st.st_mode)) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable to use '", path, ": Not a regular file", NULL)); } add_config_param_str(cmd->argv[0], 1, path); return PR_HANDLED(cmd); } /* usage: ProxySFTPServerAlive count interval */ MODRET set_proxysftpserveralive(cmd_rec *cmd) { int count, interval; config_rec *c; CHECK_ARGS(cmd, 2); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); count = atoi(cmd->argv[1]); if (count < 0) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "max count '", (char *) cmd->argv[1], "' must be equal to or greater than zero", NULL)); } interval = atoi(cmd->argv[2]); if (interval < 0) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "interval '", (char *) cmd->argv[2], "' must be equal to or greater than zero", NULL)); } c = add_config_param(cmd->argv[0], 2, NULL, NULL); c->argv[0] = palloc(c->pool, sizeof(unsigned int)); *((unsigned int *) c->argv[0]) = count; c->argv[1] = palloc(c->pool, sizeof(unsigned int)); *((unsigned int *) c->argv[1]) = interval; return PR_HANDLED(cmd); } /* usage: ProxySFTPServerMatch pattern key1 val1 ... */ MODRET set_proxysftpservermatch(cmd_rec *cmd) { #if defined(PR_USE_REGEX) register unsigned int i; config_rec *c; pr_table_t *tab; pr_regex_t *pre; int res; if (cmd->argc < 4) { CONF_ERROR(cmd, "Wrong number of parameters"); } else { int npairs; /* Make sure we have an even number of args for the key/value pairs. */ npairs = cmd->argc - 2; if (npairs % 2 != 0) { CONF_ERROR(cmd, "Wrong number of parameters"); } } CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); pre = pr_regexp_alloc(&proxy_module); res = pr_regexp_compile(pre, cmd->argv[1], REG_EXTENDED|REG_NOSUB); if (res != 0) { char errstr[200]; memset(errstr, '\0', sizeof(errstr)); pr_regexp_error(res, pre, errstr, sizeof(errstr)); pr_regexp_free(NULL, pre); CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "'", (char *) cmd->argv[1], "' failed regex compilation: ", errstr, NULL)); } c = add_config_param(cmd->argv[0], 3, NULL, NULL, NULL); c->argv[0] = pstrdup(c->pool, cmd->argv[1]); c->argv[1] = pre; tab = pr_table_alloc(c->pool, 0); c->argv[2] = tab; for (i = 2; i < cmd->argc; i++) { if (strcmp(cmd->argv[i], "pessimisticNewkeys") == 0) { int pessimistic_newkeys; void *value; pessimistic_newkeys = get_boolean(cmd, i+1); if (pessimistic_newkeys == -1) { CONF_ERROR(cmd, "expected Boolean parameter"); } value = palloc(c->pool, sizeof(int)); *((int *) value) = pessimistic_newkeys; if (pr_table_add(tab, pstrdup(c->pool, "pessimisticNewkeys"), value, sizeof(int)) < 0) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error storing 'pessimisticNewkeys' value: ", strerror(errno), NULL)); } /* Don't forget to advance i past the value. */ i++; } else { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown ProxySFTPServerMatch key: '", cmd->argv[i], "'", NULL)); } } return PR_HANDLED(cmd); #else /* no regular expression support at the moment */ CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "The ", cmd->argv[0], " directive cannot be used on this system, as you do not have POSIX " "compliant regex support", NULL)); #endif } /* usage: ProxySFTPVerifyServer on|off */ MODRET set_proxysftpverifyserver(cmd_rec *cmd) { int setting = -1; config_rec *c = NULL; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); setting = get_boolean(cmd, 1); if (setting == -1) { CONF_ERROR(cmd, "expected Boolean parameter"); } c = add_config_param(cmd->argv[0], 1, NULL); c->argv[0] = pcalloc(c->pool, sizeof(int)); *((int *) c->argv[0]) = setting; return PR_HANDLED(cmd); } /* usage: ProxySourceAddress address */ MODRET set_proxysourceaddress(cmd_rec *cmd) { config_rec *c = NULL; const pr_netaddr_t *src_addr = NULL; unsigned int addr_flags = PR_NETADDR_GET_ADDR_FL_INCL_DEVICE; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); src_addr = pr_netaddr_get_addr2(cmd->server->pool, cmd->argv[1], NULL, addr_flags); if (src_addr == NULL) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable to resolve '", (char *) cmd->argv[1], "'", NULL)); } c = add_config_param(cmd->argv[0], 1, NULL); c->argv[0] = (void *) src_addr; return PR_HANDLED(cmd); } /* Helper function to extract the just the mode/perms from st.st_mode. */ static mode_t extract_mode(struct stat *st) { mode_t mode; mode = st->st_mode; mode &= ~S_IFMT; #ifdef S_IFJOURNAL /* AIX uses this non-standard bit, which can cause issues. */ mode &= ~S_IFJOURNAL; #endif /* S_IFJOURNAL */ return mode; } /* usage: ProxyTables path */ MODRET set_proxytables(cmd_rec *cmd) { int res; struct stat st; char *path; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT); path = cmd->argv[1]; if (*path != '/') { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "must be a full path: '", path, "'", NULL)); } res = stat(path, &st); if (res < 0) { char *proxy_chroot; if (errno != ENOENT) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable to stat '", path, "': ", strerror(errno), NULL)); } pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": ProxyTables directory '%s' does not exist, creating it", path); /* Create the directory. */ res = proxy_mkpath(cmd->tmp_pool, path, geteuid(), getegid(), 0755); if (res < 0) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable to create directory '", path, "': ", strerror(errno), NULL)); } /* Also create the empty/ directory underneath, for the chroot. */ proxy_chroot = pdircat(cmd->tmp_pool, path, "empty", NULL); res = proxy_mkpath(cmd->tmp_pool, proxy_chroot, geteuid(), getegid(), 0111); if (res < 0) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable to create directory '", proxy_chroot, "': ", strerror(errno), NULL)); } pr_log_debug(DEBUG2, MOD_PROXY_VERSION ": created ProxyTables directory '%s'", path); } else { char *proxy_chroot; if (!S_ISDIR(st.st_mode)) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable to use '", path, "': Not a directory", NULL)); } /* See if the chroot directory empty/ already exists as well. And enforce * the permissions on that directory. */ proxy_chroot = pdircat(cmd->tmp_pool, path, "empty", NULL); res = stat(proxy_chroot, &st); if (res < 0) { if (errno != ENOENT) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable to stat '", proxy_chroot, "': ", strerror(errno), NULL)); } res = proxy_mkpath(cmd->tmp_pool, proxy_chroot, geteuid(), getegid(), 0111); if (res < 0) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable to create directory '", proxy_chroot, "': ", strerror(errno), NULL)); } } else { mode_t dir_mode, expected_mode; if (!S_ISDIR(st.st_mode)) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "path '", proxy_chroot, "' is not a directory as expected", NULL)); } dir_mode = extract_mode(&st); expected_mode = (S_IXUSR|S_IXGRP|S_IXOTH); if (dir_mode != expected_mode) { char mode_text[32]; memset(mode_text, '\0', sizeof(mode_text)); snprintf(mode_text, sizeof(mode_text)-1, "%04o", (int) dir_mode); CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "directory '", proxy_chroot, "' has incorrect permissions (", mode_text, ", not 0111 as required)", NULL)); } } } add_config_param_str(cmd->argv[0], 1, cmd->argv[1]); return PR_HANDLED(cmd); } /* usage: ProxyTimeoutConnect secs */ MODRET set_proxytimeoutconnect(cmd_rec *cmd) { int timeout = -1; config_rec *c = NULL; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); if (pr_str_get_duration(cmd->argv[1], &timeout) < 0) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error parsing timeout value '", (char *) cmd->argv[1], "': ", strerror(errno), NULL)); } c = add_config_param(cmd->argv[0], 1, NULL); c->argv[0] = pcalloc(c->pool, sizeof(int)); *((int *) c->argv[0]) = timeout; return PR_HANDLED(cmd); } /* usage: ProxyTimeoutLinger secs */ MODRET set_proxytimeoutlinger(cmd_rec *cmd) { int timeout = -1; config_rec *c = NULL; char *timespec; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); timespec = cmd->argv[1]; if (pr_str_get_duration(timespec, &timeout) < 0) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error parsing timeout value '", timespec, "': ", strerror(errno), NULL)); } c = add_config_param(cmd->argv[0], 1, NULL); c->argv[0] = pcalloc(c->pool, sizeof(int)); *((int *) c->argv[0]) = timeout; return PR_HANDLED(cmd); } /* usage: ProxyTLSCACertificateFile path */ MODRET set_proxytlscacertfile(cmd_rec *cmd) { #ifdef PR_USE_OPENSSL int res; char *path; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); path = cmd->argv[1]; PRIVS_ROOT res = file_exists2(cmd->tmp_pool, path); PRIVS_RELINQUISH if (!res) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "'", path, "' does not exist", NULL)); } if (*path != '/') { CONF_ERROR(cmd, "parameter must be an absolute path"); } add_config_param_str(cmd->argv[0], 1, path); return PR_HANDLED(cmd); #else CONF_ERROR(cmd, "Missing required OpenSSL support (see --enable-openssl configure option)"); #endif /* PR_USE_OPENSSL */ } /* usage: ProxyTLSCACertificatePath path */ MODRET set_proxytlscacertpath(cmd_rec *cmd) { #ifdef PR_USE_OPENSSL int res; char *path; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); path = cmd->argv[1]; PRIVS_ROOT res = dir_exists2(cmd->tmp_pool, path); PRIVS_RELINQUISH if (!res) { CONF_ERROR(cmd, "parameter must be a directory path"); } if (*path != '/') { CONF_ERROR(cmd, "parameter must be an absolute path"); } add_config_param_str(cmd->argv[0], 1, path); return PR_HANDLED(cmd); #else CONF_ERROR(cmd, "Missing required OpenSSL support (see --enable-openssl configure option)"); #endif /* PR_USE_OPENSSL */ } /* usage: ProxyTLSCARevocationFile path */ MODRET set_proxytlscacrlfile(cmd_rec *cmd) { #ifdef PR_USE_OPENSSL int res; char *path; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); path = cmd->argv[1]; PRIVS_ROOT res = file_exists2(cmd->tmp_pool, path); PRIVS_RELINQUISH if (!res) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "'", path, "' does not exist", NULL)); } if (*path != '/') { CONF_ERROR(cmd, "parameter must be an absolute path"); } add_config_param_str(cmd->argv[0], 1, path); return PR_HANDLED(cmd); #else CONF_ERROR(cmd, "Missing required OpenSSL support (see --enable-openssl configure option)"); #endif /* PR_USE_OPENSSL */ } /* usage: ProxyTLSCARevocationPath path */ MODRET set_proxytlscacrlpath(cmd_rec *cmd) { #ifdef PR_USE_OPENSSL int res; char *path; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); path = cmd->argv[1]; PRIVS_ROOT res = dir_exists2(cmd->tmp_pool, path); PRIVS_RELINQUISH if (!res) { CONF_ERROR(cmd, "parameter must be a directory path"); } if (*path != '/') { CONF_ERROR(cmd, "parameter must be an absolute path"); } add_config_param_str(cmd->argv[0], 1, path); return PR_HANDLED(cmd); #else CONF_ERROR(cmd, "Missing required OpenSSL support (see --enable-openssl configure option)"); #endif /* PR_USE_OPENSSL */ } /* usage: ProxyTLSCertificateFile path */ MODRET set_proxytlscertfile(cmd_rec *cmd) { #ifdef PR_USE_OPENSSL int res; char *path; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); path = cmd->argv[1]; PRIVS_ROOT res = file_exists2(cmd->tmp_pool, path); PRIVS_RELINQUISH if (!res) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "'", path, "' does not exist", NULL)); } if (*path != '/') { CONF_ERROR(cmd, "parameter must be an absolute path"); } add_config_param_str(cmd->argv[0], 1, path); return PR_HANDLED(cmd); #else CONF_ERROR(cmd, "Missing required OpenSSL support (see --enable-openssl configure option)"); #endif /* PR_USE_OPENSSL */ } /* usage: ProxyTLSCertificateKeyFile path */ MODRET set_proxytlscertkeyfile(cmd_rec *cmd) { #ifdef PR_USE_OPENSSL int res; char *path; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); path = cmd->argv[1]; PRIVS_ROOT res = file_exists2(cmd->tmp_pool, path); PRIVS_RELINQUISH if (!res) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "'", path, "' does not exist", NULL)); } if (*path != '/') { CONF_ERROR(cmd, "parameter must be an absolute path"); } add_config_param_str(cmd->argv[0], 1, path); return PR_HANDLED(cmd); #else CONF_ERROR(cmd, "Missing required OpenSSL support (see --enable-openssl configure option)"); #endif /* PR_USE_OPENSSL */ } /* usage: ProxyTLSCipherSuite [protocol] ciphers */ MODRET set_proxytlsciphersuite(cmd_rec *cmd) { #if defined(PR_USE_OPENSSL) config_rec *c = NULL; char *ciphersuite = NULL; int protocol = 0; SSL_CTX *ctx; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); if (cmd->argc-1 == 1) { ciphersuite = cmd->argv[1]; /* Currently, OpenSSL ciphersuite names for TLSv1.3 all use underscores; * ciphersuite names for TLSv1.2 and older do NOT use underscores. * * So if we see an underscore in the configured ciphersuites here, we * know that the optional protocol parameter has NOT been used, and that * a TLSv1.3 ciphersuite is being configured -- and that this situation * will be silently ignored by OpenSSL. */ if (strchr(ciphersuite, '_') != NULL) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "use of TLSv1.3 ciphersuite in '", ciphersuite, "' requires protocol parameter; use 'ProxyTLSCipherSuite TLSv1.3 ", ciphersuite, "'", NULL)); } } else if (cmd->argc-1 == 2) { char *protocol_text; protocol_text = cmd->argv[1]; if (strcasecmp(protocol_text, "TLSv1.3") == 0) { protocol = PROXY_TLS_PROTO_TLS_V1_3; } else if (strcasecmp(protocol_text, "TLSv1.2") == 0) { protocol = PROXY_TLS_PROTO_TLS_V1_2; } else if (strcasecmp(protocol_text, "TLSv1.1") == 0) { protocol = PROXY_TLS_PROTO_TLS_V1_1; } else if (strcasecmp(protocol_text, "TLSv1.0") == 0) { protocol = PROXY_TLS_PROTO_TLS_V1; } else { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unknown/unsupported protocol specifier: ", protocol_text, NULL)); } ciphersuite = cmd->argv[2]; } c = add_config_param(cmd->argv[0], 2, NULL, NULL); if (protocol == PROXY_TLS_PROTO_TLS_V1_3) { ciphersuite = pstrdup(c->pool, ciphersuite); } else { /* Make sure that EXPORT ciphers cannot be used, per Bug#4163. Note that * this breaks system profiles, so handle them specially. */ if (strncmp(ciphersuite, "PROFILE=", 8) == 0) { ciphersuite = pstrdup(c->pool, ciphersuite); } else { ciphersuite = pstrcat(c->pool, "!EXPORT:", ciphersuite, NULL); } } /* Check that our construct ciphersuite is acceptable. */ ctx = SSL_CTX_new(SSLv23_client_method()); if (ctx != NULL) { int res = 1; if (protocol == PROXY_TLS_PROTO_TLS_V1_3) { #if OPENSSL_VERSION_NUMBER >= 0x10101000L && \ defined(TLS1_3_VERSION) res = SSL_CTX_set_ciphersuites(ctx, ciphersuite); #endif /* TLS1_3_VERSION */ } else { res = SSL_CTX_set_cipher_list(ctx, ciphersuite); } if (res != 1) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable to use ciphersuite '", ciphersuite, "': ", proxy_tls_get_errors(), NULL)); } SSL_CTX_free(ctx); } c->argv[0] = ciphersuite; c->argv[1] = palloc(c->pool, sizeof(int)); *((int *) c->argv[1]) = protocol; if (pr_module_exists("mod_ifsession.c")) { /* These are needed in case this directive is used with mod_ifsession * configuration. */ c->flags |= CF_MULTI; } return PR_HANDLED(cmd); #else CONF_ERROR(cmd, "Missing required OpenSSL support (see --enable-openssl configure option)"); #endif /* PR_USE_OPENSSL */ } /* usage: ProxyTLSEngine on|off|auto|MatchClient */ MODRET set_proxytlsengine(cmd_rec *cmd) { #ifdef PR_USE_OPENSSL int engine = -1; config_rec *c; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_GLOBAL|CONF_VIRTUAL); engine = get_boolean(cmd, 1); if (engine == -1) { if (strcasecmp(cmd->argv[1], "auto") == 0) { engine = PROXY_TLS_ENGINE_AUTO; } else if (strcasecmp(cmd->argv[1], "MatchClient") == 0) { engine = PROXY_TLS_ENGINE_MATCH_CLIENT; } else { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unknown ProxyTLSEngine value: '", cmd->argv[1], "'", NULL)); } } else { if (engine == TRUE) { engine = PROXY_TLS_ENGINE_ON; } else { engine = PROXY_TLS_ENGINE_OFF; } } c = add_config_param(cmd->argv[0], 1, NULL); c->argv[0] = palloc(c->pool, sizeof(int)); *((int *) c->argv[0]) = engine; return PR_HANDLED(cmd); #else CONF_ERROR(cmd, "Missing required OpenSSL support (see --enable-openssl configure option)"); #endif /* PR_USE_OPENSSL */ } /* usage: ProxyTLSOptions ... */ MODRET set_proxytlsoptions(cmd_rec *cmd) { #if defined(PR_USE_OPENSSL) config_rec *c = NULL; register unsigned int i = 0; unsigned long opts = 0UL; if (cmd->argc-1 == 0) { CONF_ERROR(cmd, "wrong number of parameters"); } CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); c = add_config_param(cmd->argv[0], 1, NULL); for (i = 1; i < cmd->argc; i++) { if (strcmp(cmd->argv[i], "AllowWeakSecurity") == 0) { #if OPENSSL_VERSION_NUMBER >= 0x10100000L opts |= PROXY_TLS_OPT_ALLOW_WEAK_SECURITY; #else CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "The ", cmd->argv[i], " option cannot be used on this system, as your OpenSSL version " "is too old; requires OpenSSL-1.1.0 or later", NULL)); #endif /* OpenSSL-1.1.0 and later */ } else if (strcmp(cmd->argv[i], "EnableDiags") == 0) { opts |= PROXY_TLS_OPT_ENABLE_DIAGS; } else if (strcmp(cmd->argv[i], "NoSessionCache") == 0) { opts |= PROXY_TLS_OPT_NO_SESSION_CACHE; } else if (strcmp(cmd->argv[i], "NoSessionTickets") == 0) { opts |= PROXY_TLS_OPT_NO_SESSION_TICKETS; } else { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown ProxyTLSOption '", cmd->argv[i], "'", NULL)); } } c->argv[0] = pcalloc(c->pool, sizeof(unsigned long)); *((unsigned long *) c->argv[0]) = opts; if (pr_module_exists("mod_ifsession.c")) { /* These are needed in case this directive is used with mod_ifsession * configuration. */ c->flags |= CF_MULTI; } return PR_HANDLED(cmd); #else CONF_ERROR(cmd, "Missing required OpenSSL support (see --enable-openssl configure option)"); #endif /* PR_USE_OPENSSL */ } /* usage: ProxyTLSPreSharedKey name path */ MODRET set_proxytlspresharedkey(cmd_rec *cmd) { #ifdef PR_USE_OPENSSL # if defined(PSK_MAX_PSK_LEN) size_t identity_len, path_len; char *path; CHECK_ARGS(cmd, 2); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); identity_len = strlen(cmd->argv[1]); if (identity_len > PSK_MAX_IDENTITY_LEN) { char buf[32]; memset(buf, '\0', sizeof(buf)); snprintf(buf, sizeof(buf)-1, "%d", (int) PSK_MAX_IDENTITY_LEN); CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "ProxyTLSPreSharedKey identity '", cmd->argv[1], "' exceeds maximum length ", buf, cmd->argv[2], NULL)) } /* Ensure that the given path starts with "hex:", denoting the * format of the key at the given path. Support for other formats, e.g. * bcrypt or somesuch, will be added later. */ path = cmd->argv[2]; path_len = strlen(path); if (path_len < 5 || strncmp(cmd->argv[2], "hex:", 4) != 0) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unsupported ProxyTLSPreSharedKey format: ", path, NULL)) } (void) add_config_param_str(cmd->argv[0], 2, cmd->argv[1], path); # else pr_log_debug(DEBUG0, "%s is not supported by this build/version of OpenSSL, ignoring", (char *) cmd->argv[0]); # endif /* PSK support */ return PR_HANDLED(cmd); #else CONF_ERROR(cmd, "Missing required OpenSSL support (see --enable-openssl configure option)"); #endif /* PR_USE_OPENSSL */ } /* usage: ProxyTLSProtocol protocols */ MODRET set_proxytlsprotocol(cmd_rec *cmd) { #ifdef PR_USE_OPENSSL register unsigned int i; config_rec *c; unsigned int tls_protocol = 0; if (cmd->argc-1 == 0) { CONF_ERROR(cmd, "wrong number of parameters"); } CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); if (strcasecmp(cmd->argv[1], "all") == 0) { /* We're in an additive/subtractive type of configuration. */ tls_protocol = PROXY_TLS_PROTO_ALL; for (i = 2; i < cmd->argc; i++) { int disable = FALSE; char *proto_name; proto_name = cmd->argv[i]; if (*proto_name == '+') { proto_name++; } else if (*proto_name == '-') { disable = TRUE; proto_name++; } else { /* Using the additive/subtractive approach requires a +/- prefix; * it's malformed without such prefaces. */ CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "missing required +/- prefix: ", proto_name, NULL)); } if (strcasecmp(proto_name, "SSLv3") == 0) { if (disable) { tls_protocol &= ~PROXY_TLS_PROTO_SSL_V3; } else { tls_protocol |= PROXY_TLS_PROTO_SSL_V3; } } else if (strcasecmp(proto_name, "TLSv1") == 0 || strcasecmp(proto_name, "TLSv1.0") == 0) { if (disable) { tls_protocol &= ~PROXY_TLS_PROTO_TLS_V1; } else { tls_protocol |= PROXY_TLS_PROTO_TLS_V1; } } else if (strcasecmp(proto_name, "TLSv1.1") == 0) { # if defined(TLS1_1_VERSION) if (disable) { tls_protocol &= ~PROXY_TLS_PROTO_TLS_V1_1; } else { tls_protocol |= PROXY_TLS_PROTO_TLS_V1_1; } # else CONF_ERROR(cmd, "Your OpenSSL installation does not support TLSv1.1"); # endif /* OpenSSL 1.0.1 or later */ } else if (strcasecmp(proto_name, "TLSv1.2") == 0) { # if defined(TLS1_2_VERSION) if (disable) { tls_protocol &= ~PROXY_TLS_PROTO_TLS_V1_2; } else { tls_protocol |= PROXY_TLS_PROTO_TLS_V1_2; } # else CONF_ERROR(cmd, "Your OpenSSL installation does not support TLSv1.2"); # endif /* OpenSSL 1.0.1 or later */ } else if (strcasecmp(proto_name, "TLSv1.3") == 0) { # if defined(TLS1_3_VERSION) if (disable) { tls_protocol &= ~PROXY_TLS_PROTO_TLS_V1_3; } else { tls_protocol |= PROXY_TLS_PROTO_TLS_V1_3; } # else CONF_ERROR(cmd, "Your OpenSSL installation does not support TLSv1.3"); # endif /* OpenSSL 1.1.1 or later */ } else { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unknown protocol: '", cmd->argv[i], "'", NULL)); } } } else { for (i = 1; i < cmd->argc; i++) { if (strcasecmp(cmd->argv[i], "SSLv23") == 0) { tls_protocol |= PROXY_TLS_PROTO_SSL_V3; tls_protocol |= PROXY_TLS_PROTO_TLS_V1; } else if (strcasecmp(cmd->argv[i], "SSLv3") == 0) { tls_protocol |= PROXY_TLS_PROTO_SSL_V3; } else if (strcasecmp(cmd->argv[i], "TLSv1") == 0 || strcasecmp(cmd->argv[i], "TLSv1.0") == 0) { tls_protocol |= PROXY_TLS_PROTO_TLS_V1; } else if (strcasecmp(cmd->argv[i], "TLSv1.1") == 0) { # if defined(TLS1_1_VERSION) tls_protocol |= PROXY_TLS_PROTO_TLS_V1_1; # else CONF_ERROR(cmd, "Your OpenSSL installation does not support TLSv1.1"); # endif /* OpenSSL 1.0.1 or later */ } else if (strcasecmp(cmd->argv[i], "TLSv1.2") == 0) { # if defined(TLS1_2_VERSION) tls_protocol |= PROXY_TLS_PROTO_TLS_V1_2; # else CONF_ERROR(cmd, "Your OpenSSL installation does not support TLSv1.2"); # endif /* OpenSSL 1.0.1 or later */ } else if (strcasecmp(cmd->argv[i], "TLSv1.3") == 0) { # if defined(TLS1_3_VERSION) tls_protocol |= PROXY_TLS_PROTO_TLS_V1_3; # else CONF_ERROR(cmd, "Your OpenSSL installation does not support TLSv1.3"); # endif /* OpenSSL 1.1.1 or later */ } else { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unknown protocol: '", cmd->argv[i], "'", NULL)); } } } c = add_config_param(cmd->argv[0], 1, NULL); c->argv[0] = palloc(c->pool, sizeof(unsigned int)); *((unsigned int *) c->argv[0]) = tls_protocol; return PR_HANDLED(cmd); #else CONF_ERROR(cmd, "Missing required OpenSSL support (see --enable-openssl configure option)"); #endif /* PR_USE_OPENSSL */ } /* usage: ProxyTLSTimeoutHandshake timeout */ MODRET set_proxytlstimeouthandshake(cmd_rec *cmd) { #ifdef PR_USE_OPENSSL int timeout = -1; config_rec *c = NULL; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); if (pr_str_get_duration(cmd->argv[1], &timeout) < 0) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error parsing timeout value '", cmd->argv[1], "': ", strerror(errno), NULL)); } c = add_config_param(cmd->argv[0], 1, NULL); c->argv[0] = pcalloc(c->pool, sizeof(unsigned int)); *((unsigned int *) c->argv[0]) = timeout; return PR_HANDLED(cmd); #else CONF_ERROR(cmd, "Missing required OpenSSL support (see --enable-openssl configure option)"); #endif /* PR_USE_OPENSSL */ } /* usage: ProxyTLSTransferProtectionPolicy client|required|clear */ MODRET set_proxytlsxferprotpolicy(cmd_rec *cmd) { #ifdef PR_USE_OPENSSL config_rec *c; const char *policy; int require_prot = 0; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); policy = cmd->argv[1]; if (strcasecmp(policy, "required") == 0) { require_prot = PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_REQUIRED; } else if (strcasecmp(policy, "client") == 0) { require_prot = PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_CLIENT; } else if (strcasecmp(policy, "clear") == 0) { require_prot = PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_CLEAR; } else { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unsupported ProxyTLSTransferProtectionPolicy: ", policy, NULL)); } c = add_config_param(cmd->argv[0], 1, NULL); c->argv[0] = palloc(c->pool, sizeof(int)); *((int *) c->argv[0]) = require_prot; return PR_HANDLED(cmd); #else CONF_ERROR(cmd, "Missing required OpenSSL support (see --enable-openssl configure option)"); #endif /* PR_USE_OPENSSL */ } /* usage: ProxyTLSVerifyServer on|off */ MODRET set_proxytlsverifyserver(cmd_rec *cmd) { #ifdef PR_USE_OPENSSL int verify = -1; config_rec *c = NULL; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); verify = get_boolean(cmd, 1); if (verify == -1) { CONF_ERROR(cmd, "expected Boolean parameter"); } c = add_config_param(cmd->argv[0], 1, NULL); c->argv[0] = pcalloc(c->pool, sizeof(int)); *((int *) c->argv[0]) = verify; return PR_HANDLED(cmd); #else CONF_ERROR(cmd, "Missing required OpenSSL support (see --enable-openssl configure option)"); #endif /* PR_USE_OPENSSL */ } static void proxy_process_cmd(void) { int res = 0; cmd_rec *cmd = NULL; /* TODO: Insert select(2) call here, where we wait for readability on * the client control connection. * * Bonus points for handling aborts on either control connection, * broken data connections, blocked/slow writes to client (how much * can/should we buffer? what about short writes to the client?), * timeouts, etc. */ res = pr_cmd_read(&cmd); if (res < 0) { if (PR_NETIO_ERRNO(session.c->instrm) == EINTR) { /* Simple interrupted syscall */ return; } #ifndef PR_DEVEL_NO_DAEMON /* Otherwise, EOF */ pr_session_disconnect(NULL, PR_SESS_DISCONNECT_CLIENT_EOF, NULL); #else return; #endif /* PR_DEVEL_NO_DAEMON */ } pr_timer_reset(PR_TIMER_IDLE, ANY_MODULE); if (cmd != NULL) { /* We unblock responses here so that if any PRE_CMD handlers generate * responses (usually errors), those responses are sent to the * connecting client. */ pr_response_block(FALSE); /* TODO: If we need to, we can exert finer-grained control over * command dispatching/routing here. For example, this is where we * could block responses for PRE_CMD handlers, or skip certain * modules' handlers. */ pr_cmd_dispatch(cmd); destroy_pool(cmd->pool); pr_response_block(TRUE); } else { pr_event_generate("core.invalid-command", NULL); pr_response_send(R_500, _("Invalid command: try being more creative")); } /* Release any working memory allocated in inet */ pr_inet_clear(); } /* mod_proxy event/dispatch loop. */ static void proxy_cmd_loop(server_rec *s, conn_t *conn) { while (TRUE) { pr_signals_handle(); proxy_process_cmd(); } } /* Command handlers */ static int proxy_data_handle_resp(pool *p, struct proxy_session *proxy_sess, cmd_rec *cmd) { int res, xerrno = 0; pr_response_t *resp; unsigned int resp_nlines = 0; resp = proxy_ftp_ctrl_recv_resp(p, proxy_sess->backend_ctrl_conn, &resp_nlines, 0); if (resp == NULL) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error receiving %s response from backend: %s", (char *) cmd->argv[0], strerror(xerrno)); pr_response_add_err(R_500, _("%s: %s"), (char *) cmd->argv[0], strerror(xerrno)); pr_response_flush(&resp_err_list); if (proxy_sess->backend_data_conn != NULL) { proxy_inet_close(session.pool, proxy_sess->backend_data_conn); pr_inet_close(session.pool, proxy_sess->backend_data_conn); proxy_sess->backend_data_conn = NULL; } errno = xerrno; return -1; } /* If the backend server responds with 4xx/5xx here, close the frontend * data connection. */ if (resp->num[0] == '4' || resp->num[0] == '5') { res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool, proxy_sess->frontend_ctrl_conn, resp, resp_nlines); if (session.d != NULL) { pr_inet_close(session.pool, proxy_sess->frontend_data_conn); proxy_sess->frontend_data_conn = session.d = NULL; } if (proxy_sess->backend_data_conn != NULL) { proxy_inet_close(session.pool, proxy_sess->backend_data_conn); pr_inet_close(session.pool, proxy_sess->backend_data_conn); proxy_sess->backend_data_conn = NULL; } errno = EPERM; return -1; } res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool, proxy_sess->frontend_ctrl_conn, resp, resp_nlines); if (res < 0) { xerrno = errno; if (session.d != NULL) { pr_inet_close(session.pool, proxy_sess->frontend_data_conn); proxy_sess->frontend_data_conn = session.d = NULL; } if (proxy_sess->backend_data_conn != NULL) { proxy_inet_close(session.pool, proxy_sess->backend_data_conn); pr_inet_close(session.pool, proxy_sess->backend_data_conn); proxy_sess->backend_data_conn = NULL; } pr_response_block(TRUE); pr_response_add_err(R_500, _("%s: %s"), (char *) cmd->argv[0], strerror(xerrno)); pr_response_flush(&resp_err_list); errno = xerrno; return -1; } return 0; } static int proxy_data_prepare_backend_conn(struct proxy_session *proxy_sess, cmd_rec *cmd, conn_t **backend) { int res, xerrno; conn_t *backend_conn = NULL; /* XXX Should handle EPSV_ALL here, too. */ if (proxy_sess->backend_sess_flags & SF_PASSIVE) { const pr_netaddr_t *bind_addr = NULL, *local_addr = NULL; /* Connect to the backend server now. We won't receive the initial * response until we connect to the backend data address/port. */ /* Specify the specific address/interface to use as the source address for * connections to the destination server. */ bind_addr = proxy_sess->src_addr; /* Check the family of the remote address vs what we'll be using to connect. * If there's a mismatch, we need to get an addr with the matching family. */ if (bind_addr != NULL && pr_netaddr_get_family(bind_addr) != pr_netaddr_get_family(proxy_sess->backend_data_addr)) { /* In this scenario, the proxy has an IPv6 socket, but the remote/backend * server has an IPv4 (or IPv4-mapped IPv6) address. OR it's the proxy * which has an IPv4 socket, and the remote/backend server has an IPv6 * address. */ if (pr_netaddr_get_family(session.c->local_addr) == AF_INET) { char *ip_str; /* Convert the local address from an IPv4 to an IPv6 addr. */ ip_str = pcalloc(cmd->tmp_pool, INET6_ADDRSTRLEN + 1); snprintf(ip_str, INET6_ADDRSTRLEN, "::ffff:%s", pr_netaddr_get_ipstr(session.c->local_addr)); local_addr = pr_netaddr_get_addr(cmd->tmp_pool, ip_str, NULL); } else { local_addr = pr_netaddr_v6tov4(cmd->tmp_pool, session.c->local_addr); if (local_addr == NULL) { pr_trace_msg(trace_channel, 4, "error converting IPv6 local address %s to IPv4 address: %s", pr_netaddr_get_ipstr(session.c->local_addr), strerror(errno)); if (proxy_sess->src_addr == NULL) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "local address '%s' is an IPv6 address, and remote address " "'%s' is an IPv4 address; consider using ProxySourceAddress " "directive to configure an IPv4 address", pr_netaddr_get_ipstr(session.c->local_addr), pr_netaddr_get_ipstr(proxy_sess->backend_data_addr)); } } } if (local_addr != NULL) { pr_trace_msg(trace_channel, 7, "converted local address %s to %s for passive backend transfer", pr_netaddr_get_ipstr(session.c->local_addr), pr_netaddr_get_ipstr(local_addr)); } else { local_addr = session.c->local_addr; } } if (bind_addr == NULL) { bind_addr = local_addr; } if (pr_netaddr_is_loopback(bind_addr) == TRUE && pr_netaddr_is_loopback(proxy_sess->backend_ctrl_conn->remote_addr) != TRUE) { const char *local_name; local_name = pr_netaddr_get_localaddr_str(cmd->pool); local_addr = pr_netaddr_get_addr(cmd->pool, local_name, NULL); if (local_addr != NULL) { int local_family, remote_family; /* We need to make sure our local address family matches that * of the remote address. */ local_family = pr_netaddr_get_family(local_addr); remote_family = pr_netaddr_get_family(proxy_sess->backend_ctrl_conn->remote_addr); if (local_family != remote_family) { pr_netaddr_t *new_addr = NULL; #if defined(PR_USE_IPV6) if (local_family == AF_INET) { new_addr = pr_netaddr_v4tov6(cmd->pool, local_addr); } else { new_addr = pr_netaddr_v6tov4(cmd->pool, local_addr); } #endif /* PR_USE_IPV6 */ if (new_addr != NULL) { local_addr = new_addr; } } pr_trace_msg(trace_channel, 14, "%s is a loopback address, and unable to reach %s; using %s instead", pr_netaddr_get_ipstr(bind_addr), pr_netaddr_get_ipstr(proxy_sess->backend_data_addr), pr_netaddr_get_ipstr(local_addr)); bind_addr = local_addr; } } pr_trace_msg(trace_channel, 17, "connecting to backend server for passive data transfer for %s", (char *) cmd->argv[0]); backend_conn = proxy_ftp_conn_connect(cmd->pool, bind_addr, proxy_sess->backend_data_addr, FALSE); if (backend_conn == NULL) { pr_trace_msg(trace_channel, 9, "error connecting to backend server for passive data transfer: %s", strerror(errno)); } proxy_sess->backend_data_conn = backend_conn; /* Note that we delay the post-open on the backend data connection until * after we have received the backend response. In the case of a 4xx/5xx * response, then such a post-open (as for a TLS handshake) will fail * (Issue #244). */ res = proxy_data_handle_resp(cmd->tmp_pool, proxy_sess, cmd); if (res < 0) { return -1; } if (proxy_netio_postopen(backend_conn->instrm) < 0) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "postopen error for backend data connection input stream: %s", strerror(xerrno)); proxy_inet_close(session.pool, backend_conn); pr_inet_close(session.pool, backend_conn); proxy_sess->backend_data_conn = NULL; errno = xerrno; return -1; } if (proxy_netio_postopen(backend_conn->outstrm) < 0) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "postopen error for backend data connection output stream: %s", strerror(xerrno)); proxy_inet_close(session.pool, backend_conn); pr_inet_close(session.pool, backend_conn); proxy_sess->backend_data_conn = NULL; errno = xerrno; return -1; } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "passive backend data connection opened - local : %s:%d", pr_netaddr_get_ipstr(backend_conn->local_addr), backend_conn->local_port); (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "passive backend data connection opened - remote : %s:%d", pr_netaddr_get_ipstr(backend_conn->remote_addr), backend_conn->remote_port); } else if (proxy_sess->backend_sess_flags & SF_PORT) { res = proxy_data_handle_resp(cmd->tmp_pool, proxy_sess, cmd); if (res < 0) { return -1; } pr_trace_msg(trace_channel, 17, "accepting connection from backend server for active data " "transfer for %s", (char *) cmd->argv[0]); backend_conn = proxy_ftp_conn_accept(cmd->pool, proxy_sess->backend_data_conn, proxy_sess->backend_ctrl_conn, FALSE); if (backend_conn == NULL) { xerrno = errno; if (proxy_sess->backend_data_conn != NULL) { proxy_inet_close(session.pool, proxy_sess->backend_data_conn); pr_inet_close(session.pool, proxy_sess->backend_data_conn); proxy_sess->backend_data_conn = NULL; } pr_response_add_err(R_425, _("%s: %s"), (char *) cmd->argv[0], strerror(xerrno)); pr_response_flush(&resp_err_list); errno = xerrno; return -1; } /* We can close our listening socket now. */ proxy_inet_close(session.pool, proxy_sess->backend_data_conn); pr_inet_close(session.pool, proxy_sess->backend_data_conn); proxy_sess->backend_data_conn = backend_conn; if (proxy_netio_postopen(backend_conn->instrm) < 0) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "postopen error for backend data connection input stream: %s", strerror(xerrno)); proxy_inet_close(session.pool, backend_conn); pr_inet_close(session.pool, backend_conn); proxy_sess->backend_data_conn = NULL; errno = xerrno; return -1; } if (proxy_netio_postopen(backend_conn->outstrm) < 0) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "postopen error for backend data connection output stream: %s", strerror(xerrno)); proxy_inet_close(session.pool, backend_conn); pr_inet_close(session.pool, backend_conn); proxy_sess->backend_data_conn = NULL; errno = xerrno; return -1; } pr_inet_set_nonblock(session.pool, backend_conn); (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "active backend data connection opened - local : %s:%d", pr_netaddr_get_ipstr(backend_conn->local_addr), backend_conn->local_port); (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "active backend data connection opened - remote : %s:%d", pr_netaddr_get_ipstr(backend_conn->remote_addr), backend_conn->remote_port); } *backend = backend_conn; return 0; } static int proxy_data_prepare_frontend_conn(struct proxy_session *proxy_sess, cmd_rec *cmd, conn_t **frontend) { int xerrno; conn_t *frontend_conn = NULL; if (proxy_sess->frontend_sess_flags & SF_PASSIVE) { pr_trace_msg(trace_channel, 17, "accepting connection from frontend client for passive data " "transfer for %s", (char *) cmd->argv[0]); frontend_conn = proxy_ftp_conn_accept(cmd->pool, proxy_sess->frontend_data_conn, proxy_sess->frontend_ctrl_conn, TRUE); if (frontend_conn == NULL) { xerrno = errno; if (proxy_sess->frontend_data_conn != NULL) { pr_inet_close(session.pool, proxy_sess->frontend_data_conn); proxy_sess->frontend_data_conn = session.d = NULL; } pr_response_add_err(R_425, _("%s: %s"), (char *) cmd->argv[0], strerror(xerrno)); pr_response_flush(&resp_err_list); errno = xerrno; return -1; } /* Note that we need to set session.d here with the opened conn, for the * benefit of other callbacks (e.g. in mod_tls) invoked via these * NetIO calls. */ session.d = frontend_conn; if (pr_netio_postopen(frontend_conn->instrm) < 0) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "postopen error for frontend data connection input stream: %s", strerror(xerrno)); pr_inet_close(session.pool, frontend_conn); session.d = NULL; errno = xerrno; return -1; } if (pr_netio_postopen(frontend_conn->outstrm) < 0) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "postopen error for frontend data connection output stream: %s", strerror(xerrno)); pr_inet_close(session.pool, frontend_conn); session.d = NULL; errno = xerrno; return -1; } /* We can close our listening socket now. */ pr_inet_close(session.pool, proxy_sess->frontend_data_conn); proxy_sess->frontend_data_conn = session.d = frontend_conn; pr_inet_set_nonblock(session.pool, frontend_conn); (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "passive frontend data connection opened - local : %s:%d", pr_netaddr_get_ipstr(frontend_conn->local_addr), frontend_conn->local_port); (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "passive frontend data connection opened - remote : %s:%d", pr_netaddr_get_ipstr(frontend_conn->remote_addr), frontend_conn->remote_port); } else if (proxy_sess->frontend_sess_flags & SF_PORT) { const pr_netaddr_t *bind_addr; /* Connect to the frontend server now. */ if (pr_netaddr_get_family(session.c->local_addr) == pr_netaddr_get_family(session.c->remote_addr)) { bind_addr = session.c->local_addr; } else { /* In this scenario, the server has an IPv6 socket, but the remote client * is an IPv4 (or IPv4-mapped IPv6) peer. */ bind_addr = pr_netaddr_v6tov4(session.xfer.p, session.c->local_addr); } pr_trace_msg(trace_channel, 17, "connecting to frontend server for active data transfer for %s", (char *) cmd->argv[0]); frontend_conn = proxy_ftp_conn_connect(cmd->pool, bind_addr, proxy_sess->frontend_data_addr, TRUE); if (frontend_conn == NULL) { xerrno = errno; pr_response_add_err(R_425, _("%s: %s"), (char *) cmd->argv[0], strerror(xerrno)); pr_response_flush(&resp_err_list); errno = xerrno; return -1; } /* Note that we need to set session.d here with the opened conn, for the * benefit of other callbacks (e.g. in mod_tls) invoked via these * NetIO calls. */ session.d = frontend_conn; if (pr_netio_postopen(frontend_conn->instrm) < 0) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "postopen error for frontend data connection input stream: %s", strerror(xerrno)); pr_inet_close(session.pool, frontend_conn); session.d = NULL; errno = xerrno; return -1; } if (pr_netio_postopen(frontend_conn->outstrm) < 0) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "postopen error for frontend data connection output stream: %s", strerror(xerrno)); pr_inet_close(session.pool, frontend_conn); session.d = NULL; errno = xerrno; return -1; } proxy_sess->frontend_data_conn = session.d = frontend_conn; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "active frontend data connection opened - local : %s:%d", pr_netaddr_get_ipstr(frontend_conn->local_addr), frontend_conn->local_port); (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "active frontend data connection opened - remote : %s:%d", pr_netaddr_get_ipstr(frontend_conn->remote_addr), frontend_conn->remote_port); } *frontend = frontend_conn; return 0; } static int proxy_data_prepare_conns(struct proxy_session *proxy_sess, cmd_rec *cmd, conn_t **frontend, conn_t **backend) { int res, xerrno = 0; /* First we proxy the command to the backend server, which starts the * process. */ res = proxy_ftp_ctrl_send_cmd(cmd->tmp_pool, proxy_sess->backend_ctrl_conn, cmd); if (res < 0) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error sending %s to backend: %s", (char *) cmd->argv[0], strerror(xerrno)); pr_response_add_err(R_500, _("%s: %s"), (char *) cmd->argv[0], strerror(xerrno)); pr_response_flush(&resp_err_list); errno = xerrno; return -1; } res = proxy_data_prepare_backend_conn(proxy_sess, cmd, backend); if (res < 0) { return -1; } res = proxy_data_prepare_frontend_conn(proxy_sess, cmd, frontend); if (res < 0) { return -1; } return 0; } MODRET proxy_data(struct proxy_session *proxy_sess, cmd_rec *cmd) { int data_eof = FALSE, dst_xerrno = 0, res, xerrno; int xfer_direction, xfer_ok = TRUE; unsigned int resp_nlines = 0; pr_response_t *resp; conn_t *frontend_conn = NULL, *backend_conn = NULL; off_t bytes_transferred = 0; /* We are handling a data transfer command (e.g. LIST, RETR, etc). * * Thus we need to check the proxy_session->backend_sess_flags, and * determine whether we are to connect to the backend server, or open a * listening socket to which the backend will connect. Then we send the * given command to the backend. * * At the same time, we will need to be managing the data connection * from the frontend client separately; we will need to multiplex * across the four connections: frontend control, frontend data, * backend control, backend data. */ if (pr_cmd_cmp(cmd, PR_CMD_APPE_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_STOR_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_STOU_ID) == 0) { /* Uploading, i.e. writing to backend data conn. */ xfer_direction = PR_NETIO_IO_WR; session.xfer.path = pr_table_get(cmd->notes, "mod_xfer.store-path", NULL); if (session.xfer.path == NULL) { /* Add this note for the Jot API, and resolving %F/%f variables. */ (void) pr_table_add_dup(cmd->notes, "mod_xfer.store-path", cmd->arg, 0); session.xfer.path = cmd->arg; } } else { /* Downloading, i.e. reading from backend data conn.*/ xfer_direction = PR_NETIO_IO_RD; session.xfer.path = pr_table_get(cmd->notes, "mod_xfer.retr-path", NULL); if (session.xfer.path == NULL) { /* Add this note for the Jot API, and resolving %F/%f variables. */ (void) pr_table_add_dup(cmd->notes, "mod_xfer.retr-path", cmd->arg, 0); session.xfer.path = cmd->arg; } } res = proxy_data_prepare_conns(proxy_sess, cmd, &frontend_conn, &backend_conn); if (res < 0) { return PR_ERROR(cmd); } /* If we don't have our frontend/backend connections by now, it's a * problem. */ if (frontend_conn == NULL || backend_conn == NULL) { xerrno = EPERM; pr_response_block(TRUE); pr_response_add_err(R_425, _("%s: %s"), (char *) cmd->argv[0], strerror(xerrno)); pr_response_flush(&resp_err_list); errno = xerrno; return PR_ERROR(cmd); } /* Allow aborts -- set the current NetIO stream to allow interrupted * syscalls, so our SIGURG handler can interrupt it */ switch (xfer_direction) { case PR_NETIO_IO_RD: proxy_netio_set_poll_interval(backend_conn->instrm, 1); pr_netio_set_poll_interval(frontend_conn->outstrm, 1); break; case PR_NETIO_IO_WR: proxy_netio_set_poll_interval(backend_conn->outstrm, 1); pr_netio_set_poll_interval(frontend_conn->instrm, 1); break; } proxy_sess->frontend_sess_flags |= SF_XFER; proxy_sess->backend_sess_flags |= SF_XFER; /* Honor TransferRate directives. */ pr_throttle_init(cmd); if (pr_data_get_timeout(PR_DATA_TIMEOUT_NO_TRANSFER) > 0) { pr_timer_reset(PR_TIMER_NOXFER, ANY_MODULE); } if (pr_data_get_timeout(PR_DATA_TIMEOUT_STALLED) > 0) { pr_timer_add(pr_data_get_timeout(PR_DATA_TIMEOUT_STALLED), PR_TIMER_STALLED, &proxy_module, proxy_stalled_timeout_cb, "TimeoutStalled"); } /* XXX Note: when reading/writing data from data connections, do NOT * perform any sort of ASCII translation; we leave the data as is. * (Or maybe we SHOULD perform the ASCII translation here, in case of * ASCII translation error; the backend server can then be told that * the data are binary, and thus relieve the backend of the translation * burden. Configurable?) */ while (TRUE) { fd_set rfds; struct timeval tv; int backend_ctrlfd = -1, frontend_ctrlfd = -1, datafd = -1, maxfd = -1; int frontend_data = FALSE; conn_t *src_data_conn = NULL, *dst_data_conn = NULL; pr_signals_handle(); if (data_eof == TRUE || xfer_ok == FALSE) { tv.tv_sec = proxy_sess->linger_timeout; } else { tv.tv_sec = 15; } tv.tv_usec = 0; FD_ZERO(&rfds); /* The source/origin data connection depends on our direction: * downloads (IO_RD) from the backend, uploads (IO_WR) to the backend. */ switch (xfer_direction) { case PR_NETIO_IO_RD: src_data_conn = proxy_sess->backend_data_conn; dst_data_conn = proxy_sess->frontend_data_conn; frontend_data = FALSE; break; case PR_NETIO_IO_WR: src_data_conn = proxy_sess->frontend_data_conn; dst_data_conn = proxy_sess->backend_data_conn; frontend_data = TRUE; break; } /* Note: don't start listening for responses from the backend control * connection until we have all of the data (data_eof = TRUE), OR if * encountered some other error with the transfer. */ if (data_eof == TRUE || xfer_ok == FALSE) { backend_ctrlfd = PR_NETIO_FD(proxy_sess->backend_ctrl_conn->instrm); FD_SET(backend_ctrlfd, &rfds); if (backend_ctrlfd > maxfd) { maxfd = backend_ctrlfd; } } frontend_ctrlfd = PR_NETIO_FD(proxy_sess->frontend_ctrl_conn->instrm); FD_SET(frontend_ctrlfd, &rfds); if (frontend_ctrlfd > maxfd) { maxfd = frontend_ctrlfd; } if (src_data_conn != NULL) { datafd = PR_NETIO_FD(src_data_conn->instrm); FD_SET(datafd, &rfds); if (datafd > maxfd) { maxfd = datafd; } } res = select(maxfd + 1, &rfds, NULL, NULL, &tv); if (res < 0) { xerrno = errno; if (xerrno == EINTR) { pr_signals_handle(); continue; } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error calling select(2) while transferring data: %s", strerror(xerrno)); if (proxy_sess->frontend_data_conn != NULL) { pr_inet_close(session.pool, proxy_sess->frontend_data_conn); proxy_sess->frontend_data_conn = session.d = NULL; } if (proxy_sess->backend_data_conn != NULL) { proxy_inet_close(session.pool, proxy_sess->backend_data_conn); pr_inet_close(session.pool, proxy_sess->backend_data_conn); proxy_sess->backend_data_conn = NULL; } pr_timer_remove(PR_TIMER_STALLED, ANY_MODULE); proxy_sess->frontend_sess_flags &= ~SF_XFER; proxy_sess->backend_sess_flags &= ~SF_XFER; pr_response_add_err(R_500, _("%s: %s"), (char *) cmd->argv[0], strerror(xerrno)); pr_response_flush(&resp_err_list); pr_response_block(TRUE); errno = xerrno; return PR_ERROR(cmd); } if (res == 0) { if (data_eof == TRUE || xfer_ok == FALSE) { if (data_eof == TRUE) { /* We've timed out waiting for the end-of-transfer response on the * backend control connection. */ (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "timed out waiting for end-of-transfer response from backend " "server, terminating transfer"); } pr_timer_remove(PR_TIMER_STALLED, ANY_MODULE); proxy_sess->frontend_sess_flags &= ~SF_XFER; proxy_sess->backend_sess_flags &= ~SF_XFER; xerrno = data_eof ? ETIMEDOUT : dst_xerrno; pr_response_add_err(R_500, _("%s: %s"), (char *) cmd->argv[0], strerror(xerrno)); pr_response_flush(&resp_err_list); pr_response_block(TRUE); errno = xerrno; return PR_ERROR(cmd); } /* XXX Have MAX_RETRIES logic here. */ (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "timed out waiting for readability on control/data connections, " "trying again"); continue; } /* Any commands from the frontend client take priority */ if (frontend_ctrlfd >= 0 && FD_ISSET(frontend_ctrlfd, &rfds)) { proxy_process_cmd(); pr_response_block(FALSE); /* Check for closed frontend/backend data connections, as from an ABOR * command. */ if (proxy_sess->frontend_data_conn == NULL || proxy_sess->backend_data_conn == NULL) { if (pr_data_get_timeout(PR_DATA_TIMEOUT_STALLED) > 0) { pr_timer_remove(PR_TIMER_STALLED, ANY_MODULE); } #if PROFTPD_VERSION_NUMBER >= 0x0001030901 pr_throttle_pause(bytes_transferred, TRUE, bytes_transferred); #else pr_throttle_pause(bytes_transferred, TRUE); #endif /* Prior to ProFTPD 1.3.9rc1 */ pr_response_clear(&resp_list); pr_response_clear(&resp_err_list); return PR_HANDLED(cmd); } } if (src_data_conn != NULL && datafd >= 0 && FD_ISSET(datafd, &rfds)) { /* Some data arrived on the data connection... */ pr_buffer_t *pbuf = NULL; pr_trace_msg(trace_channel, 19, "handling data connection during data transfer"); pr_timer_reset(PR_TIMER_IDLE, ANY_MODULE); pbuf = proxy_ftp_data_recv(cmd->tmp_pool, src_data_conn, frontend_data); if (pbuf == NULL) { xerrno = errno; if (xerrno == EAGAIN) { /* We have not yet received enough data from the backend to proceed; * loop around to wait for more data. */ continue; } (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error receiving from source data connection: %s", strerror(xerrno)); } else { size_t nread; pr_timer_reset(PR_TIMER_NOXFER, ANY_MODULE); pr_timer_reset(PR_TIMER_STALLED, ANY_MODULE); nread = pbuf->current - pbuf->buf; if (nread == 0) { /* EOF on the data connection; close BOTH of them. In many * cases, closing these connections causes any buffered data to * be flushed out to the waiting peer. */ pr_trace_msg(trace_channel, 19, "read EOF on data connection, closing frontend/backend data " "connections"); proxy_inet_close(session.pool, proxy_sess->backend_data_conn); pr_inet_close(session.pool, proxy_sess->backend_data_conn); proxy_sess->backend_data_conn = NULL; if (proxy_sess->frontend_data_conn != NULL) { pr_inet_close(session.pool, proxy_sess->frontend_data_conn); proxy_sess->frontend_data_conn = session.d = NULL; } proxy_sess->frontend_sess_flags &= ~SF_XFER; proxy_sess->backend_sess_flags &= ~SF_XFER; data_eof = TRUE; } else { size_t nwrote = 0; char *ptr; pr_trace_msg(trace_channel, 9, "received %lu bytes of data from source data connection", (unsigned long) nread); session.xfer.total_bytes += nread; bytes_transferred += nread; #if PROFTPD_VERSION_NUMBER >= 0x0001030901 pr_throttle_pause(bytes_transferred, FALSE, bytes_transferred); #else pr_throttle_pause(bytes_transferred, FALSE); #endif /* Prior to ProFTPD 1.3.9rc1 */ /* We use a loop in order to properly handle short writes. * * Since we are writing the pbuf from the head, we need to advance * that pointer for every write. So we store a pointer to the * original buffer here, to be restored after the writes. */ ptr = pbuf->buf; while (nwrote != nread) { int len; len = proxy_ftp_data_send(cmd->tmp_pool, dst_data_conn, pbuf, !frontend_data); if (len < 0) { xerrno = errno; if (xerrno == EINTR) { errno = EINTR; pr_signals_handle(); continue; } errno = xerrno; res = -1; break; } nwrote += len; pbuf->buf += len; res = len; } /* Restore the pbuf. */ pbuf->buf = ptr; if (nwrote == nread) { pbuf->current = pbuf->buf; pbuf->remaining = pbuf->buflen; } if (res < 0) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error writing %lu bytes of data to destination data " "connection: %s", (unsigned long) nread, strerror(xerrno)); /* If this happens, close our connection prematurely. * XXX Should we try to send an ABOR here, too? Or SIGURG? * XXX Should we only do this for e.g. Broken pipe? */ (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "unable to proxy data between frontend/backend, " "closing data connections"); proxy_inet_close(session.pool, proxy_sess->backend_data_conn); pr_inet_close(session.pool, proxy_sess->backend_data_conn); proxy_sess->backend_data_conn = NULL; if (proxy_sess->frontend_data_conn != NULL) { pr_inet_close(session.pool, proxy_sess->frontend_data_conn); proxy_sess->frontend_data_conn = session.d = NULL; } proxy_sess->frontend_sess_flags &= ~SF_XFER; proxy_sess->backend_sess_flags &= ~SF_XFER; xfer_ok = FALSE; dst_xerrno = xerrno; } } } } /* Look for a response on the backend control connection if we've received * EOF on the data connection. * * Note that the backend control connection might be readable before we've * reached EOF on the data connection, but if we read its response in the * middle of the transfer, we risk data truncation. I.e. the backend * control response of e.g. 226 might be racing the data connection EOF, and * we don't want to read the 226 response and ASSUME that we have all of * the data; we need the explicit EOF for that. */ if (data_eof == TRUE || xfer_ok == FALSE) { /* Hopefully we have some data on the ctrl connection... */ pr_trace_msg(trace_channel, 19, "handling control connection after data transfer"); pr_timer_reset(PR_TIMER_IDLE, ANY_MODULE); resp = proxy_ftp_ctrl_recv_resp(cmd->tmp_pool, proxy_sess->backend_ctrl_conn, &resp_nlines, 0); if (resp == NULL) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error receiving response from backend control connection: %s", strerror(xerrno)); if (proxy_sess->frontend_data_conn != NULL) { pr_inet_close(session.pool, proxy_sess->frontend_data_conn); proxy_sess->frontend_data_conn = session.d = NULL; } if (proxy_sess->backend_data_conn != NULL) { proxy_inet_close(session.pool, proxy_sess->backend_data_conn); pr_inet_close(session.pool, proxy_sess->backend_data_conn); proxy_sess->backend_data_conn = NULL; } /* For a certain number of conditions, if we cannot read the response * from the backend, then we should just close the frontend, otherwise * we might "leak" to the client the fact that we are fronting some * backend server rather than being the server. */ if (xerrno == ECONNRESET || xerrno == ECONNABORTED || xerrno == ENOENT || xerrno == EPIPE || xerrno == EPERM) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "backend control connection closed (%s), closing proxy session", strerror(xerrno)); pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION, "Backend control connection lost"); } xfer_ok = FALSE; break; } else { /* If not a 1xx response, close the destination data connection, * BEFORE we send the response from the backend to the connected client. */ if (resp->num[0] != '1') { switch (xfer_direction) { case PR_NETIO_IO_RD: if (proxy_sess->frontend_data_conn != NULL) { pr_inet_close(session.pool, proxy_sess->frontend_data_conn); proxy_sess->frontend_data_conn = session.d = NULL; } break; case PR_NETIO_IO_WR: if (proxy_sess->backend_data_conn != NULL) { proxy_inet_close(session.pool, proxy_sess->backend_data_conn); pr_inet_close(session.pool, proxy_sess->backend_data_conn); proxy_sess->backend_data_conn = NULL; } break; } /* If the response was a 4xx or 5xx, then we need to note that as * a failed transfer. */ /* XXX What about ABOR/aborted transfers? */ if (resp->num[0] == '4' || resp->num[0] == '5') { xfer_ok = FALSE; } } res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool, proxy_sess->frontend_ctrl_conn, resp, resp_nlines); if (res < 0) { xerrno = errno; pr_response_add_err(R_500, _("%s: %s"), (char *) cmd->argv[0], strerror(xerrno)); pr_response_flush(&resp_err_list); pr_timer_remove(PR_TIMER_STALLED, ANY_MODULE); errno = xerrno; return PR_ERROR(cmd); } /* If we get a 1xx response here, keep going. Otherwise, we're * done with this data transfer. */ if (src_data_conn == NULL || (resp->num)[0] != '1') { proxy_sess->frontend_sess_flags &= (SF_ALL^SF_PASSIVE); proxy_sess->frontend_sess_flags &= (SF_ALL^(SF_ABORT|SF_XFER|SF_PASSIVE|SF_ASCII_OVERRIDE)); proxy_sess->backend_sess_flags &= (SF_ALL^SF_PASSIVE); proxy_sess->backend_sess_flags &= (SF_ALL^(SF_ABORT|SF_XFER|SF_PASSIVE|SF_ASCII_OVERRIDE)); break; } } } } if (pr_data_get_timeout(PR_DATA_TIMEOUT_STALLED) > 0) { pr_timer_remove(PR_TIMER_STALLED, ANY_MODULE); } #if PROFTPD_VERSION_NUMBER >= 0x0001030901 pr_throttle_pause(bytes_transferred, TRUE, bytes_transferred); #else pr_throttle_pause(bytes_transferred, TRUE); #endif /* Prior to ProFTPD 1.3.9rc1 */ proxy_sess->frontend_sess_flags &= ~SF_XFER; proxy_sess->backend_sess_flags &= ~SF_XFER; pr_response_clear(&resp_list); pr_response_clear(&resp_err_list); return (xfer_ok ? PR_HANDLED(cmd) : PR_ERROR(cmd)); } static void proxy_dirlist_data_ev(const void *event_data, void *user_data) { int res; pr_buffer_t *pbuf; char *buf, *text = NULL; size_t buflen, textlen = 0; pbuf = (pr_buffer_t *) event_data; buf = pbuf->buf; buflen = pbuf->current - pbuf->buf; pr_trace_msg(trace_channel, 25, "received directory data (%lu bytes)", (unsigned long) buflen); res = proxy_ftp_dirlist_to_text(session.xfer.p, buf, buflen, pbuf->buflen, &text, &textlen, user_data); if (res < 0) { pr_trace_msg(trace_channel, 3, "unable to handle directory data: %s", strerror(errno)); return; } /* If we have no text to emit, it means we consumed the given buffer, but * it was insufficient to generate output. Thus we empty the pbuf, and * let the caller read more. */ if (text == NULL && textlen == 0) { pbuf->current = pbuf->buf; pbuf->remaining = pbuf->buflen; return; } memcpy(pbuf->buf, text, textlen); pbuf->current = pbuf->buf + textlen; pbuf->remaining = pbuf->buflen - textlen; } MODRET proxy_directory_data(struct proxy_session *proxy_sess, cmd_rec *cmd) { int xerrno; char *list_opts, *list_path, *list_arg; cmd_rec *list_cmd; modret_t *mr; unsigned long facts_opts; /* We currently implement directory translation only for MLSD commands from * frontend clients. */ if (cmd->cmd_id != PR_CMD_MLSD_ID) { return proxy_data(proxy_sess, cmd); } if (proxy_sess->dirlist_policy == PROXY_SESS_DIRECTORY_LIST_POLICY_DEFAULT) { return proxy_data(proxy_sess, cmd); } if (proxy_ftp_dirlist_init(cmd->tmp_pool, proxy_sess) < 0) { /* TODO: What to do if this fails? */ } pr_event_register(&proxy_module, "mod_proxy.data-read", proxy_dirlist_data_ev, proxy_sess); /* Replace the given MLSD `cmd` here with an appropriately rewritten LIST * cmd, copying paths, adding options, etc. */ facts_opts = proxy_ftp_facts_get_opts(); if (facts_opts & PROXY_FTP_FACTS_OPT_SHOW_UNIX_OWNER_NAME) { list_opts = list_arg = pstrdup(cmd->pool, "-al"); } else { list_opts = list_arg = pstrdup(cmd->pool, "-aln"); } if (cmd->argc == 1) { list_path = pstrdup(cmd->pool, ""); } else { list_path = pstrdup(cmd->pool, cmd->argv[1]); list_arg = pstrcat(cmd->pool, list_arg, " ", list_path, NULL); } list_cmd = pr_cmd_alloc(cmd->pool, cmd->argc + 1, C_LIST, list_opts, list_path); list_cmd->arg = list_arg; mr = proxy_data(proxy_sess, list_cmd); xerrno = errno; pr_event_unregister(&proxy_module, "mod_proxy.data-read", proxy_dirlist_data_ev); if (proxy_ftp_dirlist_finish(proxy_sess) < 0) { /* TODO: What to do if this fails? */ } errno = xerrno; return mr; } MODRET proxy_eprt(cmd_rec *cmd, struct proxy_session *proxy_sess) { int res, xerrno; const pr_netaddr_t *remote_addr = NULL; config_rec *c; unsigned short remote_port; int allow_foreign_address = FALSE; CHECK_CMD_ARGS(cmd, 2); /* We can't just send the frontend's EPRT, as is, to the backend. * We need to connect to the frontend's EPRT; we need to open a listening * socket and send its address to the backend in our EPRT command. */ /* XXX How to handle this if we are chrooted, without root privs, for * e.g. source ports below 1024? */ remote_addr = proxy_ftp_msg_parse_ext_addr(proxy_sess->dataxfer_pool, cmd->argv[1], session.c->remote_addr, cmd->cmd_id, NULL); if (remote_addr == NULL) { xerrno = errno; pr_trace_msg(trace_channel, 2, "error parsing EPRT command '%s': %s", (char *) cmd->argv[1], strerror(xerrno)); if (xerrno == EPROTOTYPE) { #ifdef PR_USE_IPV6 if (pr_netaddr_use_ipv6()) { pr_response_add_err(R_522, _("Network protocol not supported, use (1,2)")); } else { pr_response_add_err(R_522, _("Network protocol not supported, use (1)")); } #else pr_response_add_err(R_522, _("Network protocol not supported, use (1)")); #endif /* PR_USE_IPV6 */ } else { pr_response_add_err(R_501, _("Illegal EPRT command")); } pr_response_flush(&resp_err_list); errno = xerrno; return PR_ERROR(cmd); } remote_port = ntohs(pr_netaddr_get_port(remote_addr)); /* If we are NOT listening on an RFC1918 address, BUT the client HAS * sent us an RFC1918 address in its EPRT command (which we know to not be * routable), then ignore that address, and use the client's remote address. */ if (pr_netaddr_is_rfc1918(session.c->local_addr) != TRUE && pr_netaddr_is_rfc1918(session.c->remote_addr) != TRUE && pr_netaddr_is_rfc1918(remote_addr) == TRUE) { const char *rfc1918_ipstr; rfc1918_ipstr = pr_netaddr_get_ipstr(remote_addr); remote_addr = pr_netaddr_dup(session.pool, session.c->remote_addr); /* Make sure the remote port is set on our duplicated netaddr, too * (Issue #158). */ pr_netaddr_set_port2((pr_netaddr_t *) remote_addr, remote_port); (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "client sent RFC1918 address '%s' in EPRT command, ignoring it and " "using '%s'", rfc1918_ipstr, pr_netaddr_get_ipstr(remote_addr)); } /* Make sure that the address specified matches the address from which * the control connection is coming. */ c = find_config(main_server->conf, CONF_PARAM, "AllowForeignAddress", FALSE); if (c != NULL) { int allowed; allowed = *((int *) c->argv[0]); switch (allowed) { case TRUE: allow_foreign_address = TRUE; break; case FALSE: break; default: { char *class_name; const pr_class_t *cls; class_name = c->argv[1]; cls = pr_class_find(class_name); if (cls != NULL) { if (pr_class_satisfied(cmd->tmp_pool, cls, remote_addr) == TRUE) { allow_foreign_address = TRUE; } else { pr_log_debug(DEBUG8, " '%s' not satisfied by foreign " "address '%s'", class_name, pr_netaddr_get_ipstr(remote_addr)); } } else { pr_log_debug(DEBUG8, " '%s' not found for filtering " "AllowForeignAddress", class_name); } } } } if (allow_foreign_address == FALSE) { if (pr_netaddr_cmp(remote_addr, session.c->remote_addr) != 0 || !remote_port) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "Refused EPRT %s (address mismatch)", cmd->arg); pr_response_add_err(R_500, _("Illegal EPRT command")); pr_response_flush(&resp_err_list); errno = EPERM; return PR_ERROR(cmd); } } /* Additionally, make sure that the port number used is a "high numbered" * port, to avoid bounce attacks. For remote Windows machines, the * port numbers mean little. However, there are also quite a few Unix * machines out there for whom the port number matters... */ if (remote_port < 1024) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "Refused EPRT %s (port %d below 1024, possible bounce attack)", cmd->arg, remote_port); pr_response_add_err(R_500, _("Illegal EPRT command")); pr_response_flush(&resp_err_list); errno = EPERM; return PR_ERROR(cmd); } proxy_sess->frontend_data_addr = remote_addr; switch (proxy_sess->dataxfer_policy) { case PR_CMD_PASV_ID: case PR_CMD_EPSV_ID: { const pr_netaddr_t *addr; pr_response_t *resp; unsigned int resp_nlines = 0; addr = proxy_ftp_xfer_prepare_passive(proxy_sess->dataxfer_policy, cmd, R_500, proxy_sess, 0); if (addr == NULL) { return PR_ERROR(cmd); } resp = palloc(cmd->tmp_pool, sizeof(pr_response_t)); resp->num = R_200; resp->msg = _("EPRT command successful"); resp_nlines = 1; res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool, proxy_sess->frontend_ctrl_conn, resp, resp_nlines); if (res < 0) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error sending '%s %s' response to frontend: %s", resp->num, resp->msg, strerror(xerrno)); errno = xerrno; return PR_ERROR(cmd); } proxy_sess->backend_data_addr = addr; proxy_sess->backend_sess_flags |= SF_PASSIVE; break; } case PR_CMD_PORT_ID: case PR_CMD_EPRT_ID: default: { pr_response_t *resp; unsigned int resp_nlines = 0; res = proxy_ftp_xfer_prepare_active(proxy_sess->dataxfer_policy, cmd, R_425, proxy_sess, 0); if (res < 0) { return PR_ERROR(cmd); } resp = palloc(cmd->tmp_pool, sizeof(pr_response_t)); resp->num = R_200; resp->msg = _("EPRT command successful"); resp_nlines = 1; res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool, proxy_sess->frontend_ctrl_conn, resp, resp_nlines); if (res < 0) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error sending '%s %s' response to frontend: %s", resp->num, resp->msg, strerror(xerrno)); errno = xerrno; return PR_ERROR(cmd); } proxy_sess->backend_sess_flags |= SF_PORT; break; } } /* If the command was successful, mark it in the session state/flags. */ proxy_sess->frontend_sess_flags |= SF_PORT; return PR_HANDLED(cmd); } MODRET proxy_epsv(cmd_rec *cmd, struct proxy_session *proxy_sess) { int res, xerrno; conn_t *data_conn; const char *epsv_msg; char resp_msg[PR_RESPONSE_BUFFER_SIZE]; const pr_netaddr_t *bind_addr, *remote_addr; pr_response_t *resp; unsigned int resp_nlines = 1; /* TODO: Handle any possible EPSV params, e.g. "EPSV ALL", properly. */ switch (proxy_sess->dataxfer_policy) { case PR_CMD_PORT_ID: case PR_CMD_EPRT_ID: res = proxy_ftp_xfer_prepare_active(proxy_sess->dataxfer_policy, cmd, R_425, proxy_sess, 0); if (res < 0) { return PR_ERROR(cmd); } proxy_sess->backend_sess_flags |= SF_PORT; break; case PR_CMD_PASV_ID: case PR_CMD_EPSV_ID: default: remote_addr = proxy_ftp_xfer_prepare_passive(proxy_sess->dataxfer_policy, cmd, R_500, proxy_sess, 0); if (remote_addr == NULL) { return PR_ERROR(cmd); } proxy_sess->backend_data_addr = remote_addr; proxy_sess->backend_sess_flags |= SF_PASSIVE; break; } /* We do NOT want to connect here, but would rather wait until the * ensuing data transfer-initiating command. Otherwise, a client could * spew PASV commands at us, and we would flood the backend server with * data transfer connections needlessly. * * We DO, however, need to create our own listening connection, so that * we can inform the client of the address/port to which IT is to * connect for its part of the data transfer. * * Note that we do NOT use the ProxySourceAddress here, since this listening * connection is for the frontend client. */ if (pr_netaddr_get_family(session.c->local_addr) == pr_netaddr_get_family(session.c->remote_addr)) { bind_addr = session.c->local_addr; } else { /* In this scenario, the server has an IPv6 socket, but the remote client * is an IPv4 (or IPv4-mapped IPv6) peer. */ bind_addr = pr_netaddr_v6tov4(cmd->pool, session.c->local_addr); } /* PassivePorts is handled by proxy_ftp_conn_listen(). */ data_conn = proxy_ftp_conn_listen(cmd->pool, bind_addr, FALSE); if (data_conn == NULL) { xerrno = errno; proxy_inet_close(session.pool, proxy_sess->backend_data_conn); pr_inet_close(session.pool, proxy_sess->backend_data_conn); proxy_sess->backend_data_conn = NULL; pr_response_add_err(R_425, _("Unable to build data connection: Internal error")); pr_response_flush(&resp_err_list); errno = xerrno; return PR_ERROR(cmd); } if (proxy_sess->frontend_data_conn != NULL) { /* Make sure that we only have one frontend data connection. */ pr_inet_close(session.pool, proxy_sess->frontend_data_conn); proxy_sess->frontend_data_conn = session.d = NULL; } proxy_sess->frontend_data_conn = session.d = data_conn; epsv_msg = proxy_ftp_msg_fmt_ext_addr(cmd->tmp_pool, data_conn->local_addr, data_conn->local_port, cmd->cmd_id, TRUE); (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "Entering Extended Passive Mode (%s)", epsv_msg); /* Change the response to send back to the connecting client, telling it * to use OUR address/port. */ resp = palloc(cmd->tmp_pool, sizeof(pr_response_t)); resp->num = R_229; memset(resp_msg, '\0', sizeof(resp_msg)); snprintf(resp_msg, sizeof(resp_msg)-1, "Entering Extended Passive Mode (%s)", epsv_msg); resp->msg = pstrdup(cmd->tmp_pool, resp_msg); res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool, proxy_sess->frontend_ctrl_conn, resp, resp_nlines); if (res < 0) { xerrno = errno; proxy_inet_close(session.pool, proxy_sess->backend_data_conn); pr_inet_close(session.pool, proxy_sess->backend_data_conn); proxy_sess->backend_data_conn = NULL; proxy_inet_close(session.pool, data_conn); pr_inet_close(session.pool, data_conn); pr_response_block(TRUE); pr_response_add_err(R_500, _("%s: %s"), (char *) cmd->argv[0], strerror(xerrno)); pr_response_flush(&resp_err_list); errno = xerrno; return PR_ERROR(cmd); } proxy_sess->frontend_sess_flags |= SF_PASSIVE; return PR_HANDLED(cmd); } MODRET proxy_pasv(cmd_rec *cmd, struct proxy_session *proxy_sess) { int res, xerrno; conn_t *data_conn; const char *pasv_msg; char resp_msg[PR_RESPONSE_BUFFER_SIZE]; const pr_netaddr_t *bind_addr, *remote_addr; pr_response_t *resp; unsigned int resp_nlines = 1; switch (proxy_sess->dataxfer_policy) { case PR_CMD_PORT_ID: case PR_CMD_EPRT_ID: res = proxy_ftp_xfer_prepare_active(proxy_sess->dataxfer_policy, cmd, R_425, proxy_sess, 0); if (res < 0) { return PR_ERROR(cmd); } proxy_sess->backend_sess_flags |= SF_PORT; break; case PR_CMD_PASV_ID: case PR_CMD_EPSV_ID: default: remote_addr = proxy_ftp_xfer_prepare_passive(proxy_sess->dataxfer_policy, cmd, R_500, proxy_sess, 0); if (remote_addr == NULL) { return PR_ERROR(cmd); } proxy_sess->backend_data_addr = remote_addr; proxy_sess->backend_sess_flags |= SF_PASSIVE; break; } /* We do NOT want to connect here, but would rather wait until the * ensuing data transfer-initiating command. Otherwise, a client could * spew PASV commands at us, and we would flood the backend server with * data transfer connections needlessly. * * We DO, however, need to create our own listening connection, so that * we can inform the client of the address/port to which IT is to * connect for its part of the data transfer. * * Note that we do NOT use the ProxySourceAddress here, since this listening * connection is for the frontend client. */ if (pr_netaddr_get_family(session.c->local_addr) == pr_netaddr_get_family(session.c->remote_addr)) { #ifdef PR_USE_IPV6 if (pr_netaddr_use_ipv6()) { /* Make sure that the family is NOT IPv6, even though the family of the * local and remote ends match. The PASV command cannot be used for * IPv6 addresses (Bug#3745). */ if (pr_netaddr_get_family(session.c->local_addr) == AF_INET6) { xerrno = EPERM; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "Unable to handle PASV for IPv6 address '%s', rejecting command", pr_netaddr_get_ipstr(session.c->local_addr)); pr_response_add_err(R_501, "%s: %s", (char *) cmd->argv[0], strerror(xerrno)); pr_cmd_set_errno(cmd, xerrno); errno = xerrno; return PR_ERROR(cmd); } } #endif /* PR_USE_IPV6 */ bind_addr = session.c->local_addr; } else { bind_addr = pr_netaddr_v6tov4(cmd->pool, session.c->local_addr); } /* PassivePorts is handled by proxy_ftp_conn_listen(). */ data_conn = proxy_ftp_conn_listen(cmd->pool, bind_addr, TRUE); if (data_conn == NULL) { xerrno = errno; proxy_inet_close(session.pool, proxy_sess->backend_data_conn); pr_inet_close(session.pool, proxy_sess->backend_data_conn); proxy_sess->backend_data_conn = NULL; pr_response_add_err(R_425, _("Unable to build data connection: Internal error")); pr_response_flush(&resp_err_list); errno = xerrno; return PR_ERROR(cmd); } if (proxy_sess->frontend_data_conn != NULL) { /* Make sure that we only have one frontend data connection. */ pr_inet_close(session.pool, proxy_sess->frontend_data_conn); proxy_sess->frontend_data_conn = session.d = NULL; } proxy_sess->frontend_data_conn = session.d = data_conn; pasv_msg = proxy_ftp_msg_fmt_addr(cmd->tmp_pool, data_conn->local_addr, data_conn->local_port, TRUE); (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "Entering Passive Mode (%s).", pasv_msg); /* Change the response to send back to the connecting client, telling it * to use OUR address/port. */ resp = palloc(cmd->tmp_pool, sizeof(pr_response_t)); resp->num = R_227; memset(resp_msg, '\0', sizeof(resp_msg)); snprintf(resp_msg, sizeof(resp_msg)-1, "Entering Passive Mode (%s).", pasv_msg); resp->msg = pstrdup(cmd->tmp_pool, resp_msg); res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool, proxy_sess->frontend_ctrl_conn, resp, resp_nlines); if (res < 0) { xerrno = errno; proxy_inet_close(session.pool, proxy_sess->backend_data_conn); pr_inet_close(session.pool, proxy_sess->backend_data_conn); proxy_sess->backend_data_conn = NULL; pr_inet_close(session.pool, data_conn); proxy_sess->frontend_data_conn = session.d = NULL; pr_response_block(TRUE); pr_response_add_err(R_500, _("%s: %s"), (char *) cmd->argv[0], strerror(xerrno)); pr_response_flush(&resp_err_list); errno = xerrno; return PR_ERROR(cmd); } proxy_sess->frontend_sess_flags |= SF_PASSIVE; return PR_HANDLED(cmd); } MODRET proxy_port(cmd_rec *cmd, struct proxy_session *proxy_sess) { int res, xerrno; const pr_netaddr_t *remote_addr = NULL; unsigned short remote_port; config_rec *c; int allow_foreign_address = FALSE; CHECK_CMD_ARGS(cmd, 2); /* We can't just send the frontend's PORT, as is, to the backend. * We need to connect to the frontend's PORT; we need to open a listening * socket and send its address to the backend in our PORT command. */ /* XXX How to handle this if we are chrooted, without root privs, for * e.g. source ports below 1024? */ remote_addr = proxy_ftp_msg_parse_addr(proxy_sess->dataxfer_pool, cmd->argv[1], pr_netaddr_get_family(session.c->remote_addr)); if (remote_addr == NULL) { xerrno = errno; pr_trace_msg("proxy", 2, "error parsing PORT command '%s': %s", (char *) cmd->argv[1], strerror(xerrno)); pr_response_add_err(R_501, _("Illegal PORT command")); pr_response_flush(&resp_err_list); errno = xerrno; return PR_ERROR(cmd); } remote_port = ntohs(pr_netaddr_get_port(remote_addr)); /* If we are NOT listening on an RFC1918 address, BUT the client HAS * sent us an RFC1918 address in its PORT command (which we know to not be * routable), then ignore that address, and use the client's remote address. */ if (pr_netaddr_is_rfc1918(session.c->local_addr) != TRUE && pr_netaddr_is_rfc1918(session.c->remote_addr) != TRUE && pr_netaddr_is_rfc1918(remote_addr) == TRUE) { const char *rfc1918_ipstr; rfc1918_ipstr = pr_netaddr_get_ipstr(remote_addr); remote_addr = pr_netaddr_dup(session.pool, session.c->remote_addr); /* Make sure the remote port is set on our duplicated netaddr, too * (Issue #158). */ pr_netaddr_set_port2((pr_netaddr_t *) remote_addr, remote_port); (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "client sent RFC1918 address '%s' in PORT command, ignoring it and " "using '%s'", rfc1918_ipstr, pr_netaddr_get_ipstr(remote_addr)); } /* Make sure that the address specified matches the address from which * the control connection is coming. */ c = find_config(TOPLEVEL_CONF, CONF_PARAM, "AllowForeignAddress", FALSE); if (c != NULL) { int allowed; allowed = *((int *) c->argv[0]); switch (allowed) { case TRUE: allow_foreign_address = TRUE; break; case FALSE: break; default: { char *class_name; const pr_class_t *cls; class_name = c->argv[1]; cls = pr_class_find(class_name); if (cls != NULL) { if (pr_class_satisfied(cmd->tmp_pool, cls, remote_addr) == TRUE) { allow_foreign_address = TRUE; } else { pr_log_debug(DEBUG8, " '%s' not satisfied by foreign " "address '%s'", class_name, pr_netaddr_get_ipstr(remote_addr)); } } else { pr_log_debug(DEBUG8, " '%s' not found for filtering " "AllowForeignAddress", class_name); } } } } if (allow_foreign_address == FALSE) { #ifdef PR_USE_IPV6 if (pr_netaddr_use_ipv6()) { /* We can only compare the PORT-given address against the remote client * address if the remote client address is an IPv4-mapped IPv6 address. */ if (pr_netaddr_get_family(session.c->remote_addr) == AF_INET6 && pr_netaddr_is_v4mappedv6(session.c->remote_addr) != TRUE) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "Refused PORT %s (IPv4/IPv6 address mismatch)", cmd->arg); pr_response_add_err(R_500, _("Illegal PORT command")); pr_response_flush(&resp_err_list); errno = EPERM; return PR_ERROR(cmd); } } #endif /* PR_USE_IPV6 */ if (pr_netaddr_cmp(remote_addr, session.c->remote_addr) != 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "Refused PORT %s (address mismatch)", cmd->arg); pr_response_add_err(R_500, _("Illegal PORT command")); pr_response_flush(&resp_err_list); errno = EPERM; return PR_ERROR(cmd); } } /* Additionally, make sure that the port number used is a "high numbered" * port, to avoid bounce attacks. For remote Windows machines, the * port numbers mean little. However, there are also quite a few Unix * machines out there for whom the port number matters... */ if (remote_port < 1024) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "Refused PORT %s (port %d below 1024, possible bounce attack)", cmd->arg, remote_port); pr_response_add_err(R_500, _("Illegal PORT command")); pr_response_flush(&resp_err_list); errno = EPERM; return PR_ERROR(cmd); } proxy_sess->frontend_data_addr = remote_addr; switch (proxy_sess->dataxfer_policy) { case PR_CMD_PASV_ID: case PR_CMD_EPSV_ID: { const pr_netaddr_t *addr; pr_response_t *resp; unsigned int resp_nlines = 0; addr = proxy_ftp_xfer_prepare_passive(proxy_sess->dataxfer_policy, cmd, R_500, proxy_sess, 0); if (addr == NULL) { return PR_ERROR(cmd); } resp = palloc(cmd->tmp_pool, sizeof(pr_response_t)); resp->num = R_200; resp->msg = _("PORT command successful"); resp_nlines = 1; res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool, proxy_sess->frontend_ctrl_conn, resp, resp_nlines); if (res < 0) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error sending '%s %s' response to frontend: %s", resp->num, resp->msg, strerror(xerrno)); errno = xerrno; return PR_ERROR(cmd); } proxy_sess->backend_data_addr = addr; proxy_sess->backend_sess_flags |= SF_PASSIVE; break; } case PR_CMD_PORT_ID: case PR_CMD_EPRT_ID: default: { pr_response_t *resp; unsigned int resp_nlines = 0; res = proxy_ftp_xfer_prepare_active(proxy_sess->dataxfer_policy, cmd, R_425, proxy_sess, 0); if (res < 0) { return PR_ERROR(cmd); } resp = palloc(cmd->tmp_pool, sizeof(pr_response_t)); resp->num = R_200; resp->msg = _("PORT command successful"); resp_nlines = 1; res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool, proxy_sess->frontend_ctrl_conn, resp, resp_nlines); if (res < 0) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error sending '%s %s' response to frontend: %s", resp->num, resp->msg, strerror(xerrno)); errno = xerrno; return PR_ERROR(cmd); } proxy_sess->backend_sess_flags |= SF_PORT; break; } } /* If the command was successful, mark it in the session state/flags. */ proxy_sess->frontend_sess_flags |= SF_PORT; return PR_HANDLED(cmd); } MODRET proxy_feat(cmd_rec *cmd, struct proxy_session *proxy_sess) { modret_t *mr = NULL; pr_response_t *resp = NULL; mr = proxy_cmd(cmd, proxy_sess, &resp); /* If we don't already have our backend feature table allocated, as * when the backend server won't support the FEAT command until AFTER * authentication occurs, then try to piggyback on the frontend client's * FEAT command, and fill our table now. */ if (proxy_sess->backend_features == NULL) { if (MODRET_ISHANDLED(mr) && resp != NULL) { const char *feat_crlf = "\r\n"; char *feats, *token; size_t token_len = 0; pr_trace_msg(trace_channel, 9, "populating backend features based on FEAT response to frontend " "client"); proxy_sess->backend_features = pr_table_nalloc(proxy_pool, 0, 4); feats = pstrdup(cmd->tmp_pool, resp->msg); token = pr_str_get_token2(&feats, (char *) feat_crlf, &token_len); while (token != NULL) { pr_signals_handle(); if (token_len > 0) { /* The FEAT response lines in which we are interested all start with * a single space, per RFC spec. Ignore any other lines. */ if (token[0] == ' ') { char *key, *val, *ptr; /* Find the next space in the string, to delimit our key/value * pairs. */ ptr = strchr(token + 1, ' '); if (ptr != NULL) { key = pstrndup(proxy_pool, token + 1, ptr - token - 1); val = pstrdup(proxy_pool, ptr + 1); } else { key = pstrdup(proxy_pool, token + 1); val = pstrdup(proxy_pool, ""); } pr_table_add(proxy_sess->backend_features, key, val, 0); } } feats = token + token_len + 1; token = pr_str_get_token2(&feats, (char *) feat_crlf, &token_len); } } } return mr; } static void proxy_login_failed(void) { unsigned int max_logins = PROXY_SESS_MAX_LOGIN_ATTEMPTS; config_rec *c; c = find_config(main_server->conf, CONF_PARAM, "MaxLoginAttempts", FALSE); if (c != NULL) { max_logins = *((unsigned int *) c->argv[0]); } if (max_logins > 0 && ++proxy_login_attempts >= max_logins) { /* Generate an event for the benefit of modules like mod_ban and mod_snmp. */ pr_event_generate("mod_auth.max-login-attempts", session.c); } } MODRET proxy_user(cmd_rec *cmd, struct proxy_session *proxy_sess, int *block_responses) { int successful = FALSE, res = 0, xerrno = 0; /* Remove any exit handlers installed by mod_xfer. We do this here, * rather than in sess_init, since our sess_init is called BEFORE the * sess_init of mod_xfer. */ pr_event_unregister(&xfer_module, "core.exit", NULL); if (proxy_sess_state & PROXY_SESS_STATE_BACKEND_AUTHENTICATED) { /* If we've already authenticated, then let the backend server deal * with this. */ return proxy_cmd(cmd, proxy_sess, NULL); } /* We handle errors differently for the roles. * * In the forward proxy case, the client knows _a priori_ that it is * connecting through a proxy. In this case, we can relay more of the * errors to the client. * * In the reverse proxy case, though, we want to hide such errors from * the oblivious client, and not leak any more information than necessary. */ switch (proxy_role) { case PROXY_ROLE_REVERSE: res = proxy_reverse_handle_user(cmd, proxy_sess, &successful, block_responses); xerrno = errno; pr_response_add_err(R_530, _("Login incorrect.")); break; case PROXY_ROLE_FORWARD: res = proxy_forward_handle_user(cmd, proxy_sess, &successful, block_responses); xerrno = errno; if (xerrno != EINVAL) { pr_response_add_err(R_500, _("%s: %s"), (char *) cmd->argv[0], strerror(xerrno)); } else { pr_response_add_err(R_530, _("Login incorrect.")); } break; } if (res < 0) { pr_response_flush(&resp_err_list); errno = xerrno; return PR_ERROR(cmd); } if (successful == TRUE) { config_rec *c; const char *notes_key = "mod_auth.orig-user"; char *user; /* For 2xx/3xx responses (others?), stash the user name appropriately. */ user = cmd->arg; (void) pr_table_remove(session.notes, notes_key, NULL); if (pr_table_add_dup(session.notes, notes_key, user, 0) < 0) { pr_log_debug(DEBUG3, "error stashing '%s' in session.notes: %s", notes_key, strerror(errno)); } /* Handle DefaultTransferMode here. */ c = find_config(main_server->conf, CONF_PARAM, "DefaultTransferMode", FALSE); if (c != NULL) { if (strncasecmp(c->argv[0], "binary", 7) == 0) { session.sf_flags &= (SF_ALL^SF_ASCII); } else { session.sf_flags |= SF_ASCII; } } else { /* ASCII by default. */ session.sf_flags |= SF_ASCII; } } else { if (res == 1) { /* If we haven't been marked as successful, BUT the return value is 1, * then use this as an indicator that an error response was sent already * to the client. */ errno = xerrno; return PR_ERROR(cmd); } } if (res == 0) { return PR_DECLINED(cmd); } return PR_HANDLED(cmd); } MODRET proxy_pass(cmd_rec *cmd, struct proxy_session *proxy_sess, int *block_responses) { int successful = FALSE, res = 0; /* It's possible that the client only sent a PASS command with no arguments, * effectively a blank/missing password. Some other FTP servers may not * handle this as well as ProFTPD does. */ if (cmd->argc == 1) { cmd_rec *new_cmd; new_cmd = pr_cmd_alloc(cmd->pool, 2, C_PASS, pstrdup(cmd->pool, "")); new_cmd->arg = pstrdup(new_cmd->pool, ""); cmd = new_cmd; } if (proxy_sess_state & PROXY_SESS_STATE_BACKEND_AUTHENTICATED) { /* If we've already authenticated, then let the backend server deal with * this. */ return proxy_cmd(cmd, proxy_sess, NULL); } switch (proxy_role) { case PROXY_ROLE_REVERSE: res = proxy_reverse_handle_pass(cmd, proxy_sess, &successful, block_responses); break; case PROXY_ROLE_FORWARD: res = proxy_forward_handle_pass(cmd, proxy_sess, &successful, block_responses); break; } if (res < 0) { int xerrno = errno; if (xerrno == ECONNRESET || xerrno == ECONNABORTED || xerrno == EPIPE) { /* This indicates that the backend server closed the control * connection on us. Given that, the only thing we can do is to close * the frontend connection in turn. */ (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "lost backend connection (%s) handling %s command", strerror(xerrno), (char *) cmd->argv[0]); pr_response_add_err(R_530, _("Login incorrect.")); pr_response_flush(&resp_err_list); pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION, "Backend control connection lost"); } if (xerrno != EINVAL) { pr_response_add_err(R_500, _("%s: %s"), (char *) cmd->argv[0], strerror(xerrno)); } else { pr_response_add_err(R_530, _("Login incorrect.")); } pr_response_flush(&resp_err_list); proxy_login_failed(); errno = xerrno; return PR_ERROR(cmd); } if (successful == TRUE) { const char *user; int proxy_auth = FALSE; user = pr_table_get(session.notes, "mod_auth.orig-user", NULL); session.user = user; switch (proxy_role) { case PROXY_ROLE_FORWARD: proxy_auth = proxy_forward_use_proxy_auth(); break; case PROXY_ROLE_REVERSE: proxy_auth = proxy_reverse_use_proxy_auth(); break; } /* Unless proxy auth happened, we set the session groups to null, * to avoid unexpected behavior when looking up e.g. sections. */ if (proxy_auth == FALSE) { if (session.group != NULL) { pr_trace_msg(trace_channel, 9, "clearing unauthenticated primary group name '%s' for user '%s'", session.group, session.user); session.group = NULL; } if (session.groups != NULL) { if (session.groups->nelts > 0) { register unsigned int i; pr_trace_msg(trace_channel, 9, "clearing %d unauthenticated additional group %s for user '%s':", session.groups->nelts, session.groups->nelts != 1 ? "names" : "name", session.user); for (i = 0; i < session.groups->nelts; i++) { pr_trace_msg(trace_channel, 9, " clearing additional group name '%s'", ((char **) session.groups->elts)[i]); } } session.groups = NULL; } } /* XXX Do we need to set other login-related fields here? E.g. * session.uid, session.gid, etc? */ fixup_dirs(main_server, CF_DEFER); if (proxy_role == PROXY_ROLE_FORWARD) { proxy_restrict_session(); } } else { proxy_login_failed(); return PR_ERROR(cmd); } return PR_HANDLED(cmd); } MODRET proxy_type(cmd_rec *cmd, struct proxy_session *proxy_sess) { int res, xerrno; pr_response_t *resp; unsigned int resp_nlines = 0; res = proxy_ftp_ctrl_send_cmd(cmd->tmp_pool, proxy_sess->backend_ctrl_conn, cmd); if (res < 0) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error sending %s to backend: %s", (char *) cmd->argv[0], strerror(xerrno)); pr_response_add_err(R_500, _("%s: %s"), (char *) cmd->argv[0], strerror(xerrno)); pr_response_flush(&resp_err_list); errno = xerrno; return PR_ERROR(cmd); } resp = proxy_ftp_ctrl_recv_resp(cmd->tmp_pool, proxy_sess->backend_ctrl_conn, &resp_nlines, 0); if (resp == NULL) { xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error receiving %s response from backend: %s", (char *) cmd->argv[0], strerror(xerrno)); pr_response_add_err(R_500, _("%s: %s"), (char *) cmd->argv[0], strerror(xerrno)); pr_response_flush(&resp_err_list); errno = xerrno; return PR_ERROR(cmd); } if (resp->num[0] == '2') { char *type; /* This code is duplicated from mod_xfer.c#xfer_type(). Would be nice * to factor it out somewhere reusable, i.e. some pr_str_ function. */ type = pstrdup(cmd->tmp_pool, cmd->argv[1]); type[0] = toupper(type[0]); if (strcmp(type, "A") == 0 || (cmd->argc == 3 && strcmp(type, "L") == 0 && strcmp(cmd->argv[2], "7") == 0)) { /* TYPE A(SCII) or TYPE L 7. */ session.sf_flags |= SF_ASCII; } else if (strcmp(type, "I") == 0 || (cmd->argc == 3 && strcmp(type, "L") == 0 && strcmp(cmd->argv[2], "8") == 0)) { /* TYPE I(MAGE) or TYPE L 8. */ session.sf_flags &= (SF_ALL^(SF_ASCII|SF_ASCII_OVERRIDE)); } } res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool, proxy_sess->frontend_ctrl_conn, resp, resp_nlines); if (res < 0) { xerrno = errno; pr_response_block(TRUE); errno = xerrno; return PR_ERROR(cmd); } return PR_HANDLED(cmd); } static int proxy_get_cmd_group(cmd_rec *cmd) { cmdtable *cmdtab; int idx; unsigned int h; idx = cmd->stash_index; h = cmd->stash_hash; cmdtab = pr_stash_get_symbol2(PR_SYM_CMD, cmd->argv[0], NULL, &idx, &h); while (cmdtab != NULL) { pr_signals_handle(); if (cmdtab->group == NULL || cmdtab->cmd_type != CMD) { cmdtab = pr_stash_get_symbol2(PR_SYM_CMD, cmd->argv[0], cmdtab, &idx, &h); continue; } cmd->group = pstrdup(cmd->pool, cmdtab->group); return 0; } /* Note that some commands legitimately have no group (G_NONE is NULL), thus * the absense of a group could simply indicate G_NONE. */ if (cmd->group == NULL) { pr_trace_msg(trace_channel, 15, "found group 'NONE' for command '%s'", (char *) cmd->argv[0]); } return 0; } static int proxy_have_limit(cmd_rec *cmd, const char **resp_code) { int res; /* Some commands get a free pass. */ switch (cmd->cmd_id) { case PR_CMD_ACCT_ID: case PR_CMD_EPRT_ID: case PR_CMD_EPSV_ID: case PR_CMD_FEAT_ID: case PR_CMD_PASS_ID: case PR_CMD_PASV_ID: case PR_CMD_PORT_ID: case PR_CMD_QUIT_ID: case PR_CMD_SYST_ID: case PR_CMD_USER_ID: return 0; default: break; } /* Note: since we use a PRE_CMD ANY handler here, the core code does NOT * actually look up the specific records for this command. This means that * that the command's command group may not be known. But to honor any * group-based sections, we need to look up the command group. */ if (cmd->group == NULL) { if (proxy_get_cmd_group(cmd) < 0) { pr_trace_msg(trace_channel, 5, "error finding group for command '%s': %s", (char *) cmd->argv[0], strerror(errno)); } } res = dir_check(cmd->tmp_pool, cmd, cmd->group, session.cwd, NULL); if (res == 0) { /* The appropriate response code depends on the command, unfortunately. * See RFC 959, Section 5.4 for the gory details. */ switch (cmd->cmd_id) { case PR_CMD_ALLO_ID: case PR_CMD_MODE_ID: case PR_CMD_REST_ID: case PR_CMD_STRU_ID: case PR_CMD_TYPE_ID: *resp_code = R_501; break; default: *resp_code = R_550; break; } errno = EPERM; return -1; } return 0; } MODRET proxy_any(cmd_rec *cmd) { int block_responses = TRUE; struct proxy_session *proxy_sess; modret_t *mr = NULL; const char *resp_code = R_550; if (proxy_engine == FALSE) { return PR_DECLINED(cmd); } /* Honor any sections for this comand. */ if (proxy_have_limit(cmd, &resp_code) < 0) { int xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "%s denied by configuration", (char *) cmd->argv[0]); pr_response_add_err(resp_code, "%s: %s", (char *) cmd->argv[0], strerror(xerrno)); pr_cmd_set_errno(cmd, xerrno); errno = xerrno; return PR_ERROR(cmd); } proxy_sess = (struct proxy_session *) pr_table_get(session.notes, "mod_proxy.proxy-session", NULL); if (proxy_sess == NULL) { /* Unlikely to occur. */ (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "%s", "missing proxy session unexpectedly"); return PR_DECLINED(cmd); } /* Backend servers can send "asynchronous" messages to us; we need to check * for them. */ if (proxy_sess->backend_ctrl_conn != NULL) { if (proxy_ftp_ctrl_handle_async(cmd->tmp_pool, proxy_sess->backend_ctrl_conn, proxy_sess->frontend_ctrl_conn, 0) < 0) { int xerrno = errno; pr_trace_msg(trace_channel, 7, "error checking for async messages from the backend server: %s", strerror(xerrno)); if (xerrno == ECONNRESET || xerrno == ECONNABORTED || xerrno == ENOENT || xerrno == EPIPE) { pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION, "Backend control connection lost"); } } } pr_response_block(FALSE); pr_timer_reset(PR_TIMER_IDLE, ANY_MODULE); /* Commands related to logins and data transfers are handled separately. */ switch (cmd->cmd_id) { case PR_CMD_USER_ID: mr = proxy_user(cmd, proxy_sess, &block_responses); if (block_responses) { pr_response_block(TRUE); } return mr; case PR_CMD_PASS_ID: mr = proxy_pass(cmd, proxy_sess, &block_responses); if (block_responses) { pr_response_block(TRUE); } return mr; case PR_CMD_EPRT_ID: if (proxy_sess_state & PROXY_SESS_STATE_CONNECTED) { if (proxy_opts & PROXY_OPT_USE_DIRECT_DATA_TRANSFERS) { /* For direct data transfers, we proxy EPRT commands directly to the * backend server. */ break; } proxy_session_reset_dataxfer(proxy_sess); mr = proxy_eprt(cmd, proxy_sess); pr_response_block(TRUE); return mr; } else { pr_response_send(R_530, _("Access denied")); return PR_ERROR(cmd); } break; case PR_CMD_EPSV_ID: if (proxy_sess_state & PROXY_SESS_STATE_CONNECTED) { if (proxy_opts & PROXY_OPT_USE_DIRECT_DATA_TRANSFERS) { /* For direct data transfers, we proxy EPSV commands directly to the * backend server. */ break; } proxy_session_reset_dataxfer(proxy_sess); mr = proxy_epsv(cmd, proxy_sess); pr_response_block(TRUE); return mr; } else { pr_response_send(R_530, _("Access denied")); return PR_ERROR(cmd); } break; case PR_CMD_PASV_ID: if (proxy_sess_state & PROXY_SESS_STATE_CONNECTED) { if (proxy_opts & PROXY_OPT_USE_DIRECT_DATA_TRANSFERS) { /* For direct data transfers, we proxy PASV commands directly to the * backend server. */ break; } proxy_session_reset_dataxfer(proxy_sess); mr = proxy_pasv(cmd, proxy_sess); pr_response_block(TRUE); return mr; } else { pr_response_send(R_530, _("Access denied")); return PR_ERROR(cmd); } break; case PR_CMD_PORT_ID: if (proxy_sess_state & PROXY_SESS_STATE_CONNECTED) { if (proxy_opts & PROXY_OPT_USE_DIRECT_DATA_TRANSFERS) { /* For direct data transfers, we proxy PORT commands directly to the * backend server. */ break; } proxy_session_reset_dataxfer(proxy_sess); mr = proxy_port(cmd, proxy_sess); pr_response_block(TRUE); return mr; } else { pr_response_send(R_530, _("Access denied")); return PR_ERROR(cmd); } break; case PR_CMD_TYPE_ID: if (proxy_sess_state & PROXY_SESS_STATE_CONNECTED) { /* Used for setting the ASCII/binary session flag properly, e.g. for * TransferLogs. */ mr = proxy_type(cmd, proxy_sess); pr_response_block(TRUE); return mr; } break; case PR_CMD_LIST_ID: case PR_CMD_MLSD_ID: case PR_CMD_NLST_ID: if (proxy_sess_state & PROXY_SESS_STATE_CONNECTED) { if (session.xfer.p != NULL) { destroy_pool(session.xfer.p); } memset(&session.xfer, 0, sizeof(session.xfer)); session.xfer.p = make_sub_pool(session.pool); pr_pool_tag(session.xfer.p, "proxy session xfer pool"); session.xfer.direction = PR_NETIO_IO_WR; if (proxy_opts & PROXY_OPT_USE_DIRECT_DATA_TRANSFERS) { /* For direct data transfers, we proxy data transfer commands * directly to the backend server. Since data transfer commands * involve two responsese (the initial 1xx, then the closing 2xx), * we need to handle them more carefully. */ mr = proxy_data_cmd(cmd, proxy_sess); } else { mr = proxy_directory_data(proxy_sess, cmd); } pr_response_block(TRUE); return mr; } else { pr_response_send(R_530, _("Access denied")); return PR_ERROR(cmd); } break; case PR_CMD_APPE_ID: case PR_CMD_RETR_ID: case PR_CMD_STOR_ID: case PR_CMD_STOU_ID: if (proxy_sess_state & PROXY_SESS_STATE_CONNECTED) { /* In addition to the same setup as for directory listings, we also * track more things, for supporting e.g. TransferLog. */ if (session.xfer.p != NULL) { destroy_pool(session.xfer.p); } memset(&session.xfer, 0, sizeof(session.xfer)); if (pr_cmd_cmp(cmd, PR_CMD_RETR_ID) == 0) { session.xfer.direction = PR_NETIO_IO_RD; } else { session.xfer.direction = PR_NETIO_IO_WR; } session.xfer.p = make_sub_pool(session.pool); pr_pool_tag(session.xfer.p, "proxy session xfer pool"); gettimeofday(&session.xfer.start_time, NULL); if (proxy_opts & PROXY_OPT_USE_DIRECT_DATA_TRANSFERS) { /* For direct data transfers, we proxy data transfer commands * directly to the backend server. Since data transfer commands * involve two responsese (the initial 1xx, then the closing 2xx), * we need to handle them more carefully. */ mr = proxy_data_cmd(cmd, proxy_sess); } else { mr = proxy_data(proxy_sess, cmd); } pr_response_block(TRUE); return mr; } else { pr_response_send(R_530, _("Access denied")); return PR_ERROR(cmd); } break; case PR_CMD_FEAT_ID: if (proxy_role == PROXY_ROLE_REVERSE) { /* In reverse proxy mode, we do not want to necessarily leak the * capabilities of the selected backend server to the client. Or * do we? */ if (proxy_sess_state & PROXY_SESS_STATE_CONNECTED) { if (proxy_opts & PROXY_OPT_SHOW_FEATURES) { mr = proxy_feat(cmd, proxy_sess); return mr; } } return PR_DECLINED(cmd); } else { if (proxy_sess_state & PROXY_SESS_STATE_CONNECTED) { mr = proxy_feat(cmd, proxy_sess); return mr; } } break; case PR_CMD_OPTS_ID: if (cmd->argc == 3 && strcasecmp(cmd->argv[1], C_MLST) == 0 && proxy_sess->dirlist_policy == PROXY_SESS_DIRECTORY_LIST_POLICY_LIST) { (void) proxy_ftp_facts_parse_opts(pstrdup(cmd->tmp_pool, cmd->argv[2])); } break; case PR_CMD_HOST_ID: /* TODO is there any value in handling the HOST command locally? * Answer: yes! Consider the reverse proxy case + mod_autohost! * Thus we DO want to return DECLINED here, BUT we ALSO need to implement * the event listener for resetting the forward/reverse (but not tls) * APIs. */ return PR_DECLINED(cmd); /* Directory changing commands not allowed locally. */ case PR_CMD_CDUP_ID: case PR_CMD_CWD_ID: case PR_CMD_MKD_ID: case PR_CMD_PWD_ID: case PR_CMD_RMD_ID: case PR_CMD_XCUP_ID: case PR_CMD_XCWD_ID: case PR_CMD_XMKD_ID: case PR_CMD_XPWD_ID: case PR_CMD_XRMD_ID: if ((proxy_sess_state & PROXY_SESS_STATE_PROXY_AUTHENTICATED) && !(proxy_sess_state & PROXY_SESS_STATE_CONNECTED)) { pr_response_send(R_530, _("Access denied")); return PR_ERROR(cmd); } break; /* RFC 2228 commands */ case PR_CMD_ADAT_ID: case PR_CMD_AUTH_ID: case PR_CMD_CCC_ID: case PR_CMD_CONF_ID: case PR_CMD_ENC_ID: case PR_CMD_MIC_ID: case PR_CMD_PBSZ_ID: return PR_DECLINED(cmd); case PR_CMD_PROT_ID: /* For a TLS transfer protection policy of "client" (0), we handle the * PROT command by proxying it to the backend server. For any other * policy, though, we decline to handle the command from the client * (and let it dispatch to mod_tls); mod_proxy will send a different * PROT command to the backend, based on the policy. */ if (proxy_tls_xfer_prot_policy != PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_CLIENT) { return PR_DECLINED(cmd); } break; case PR_CMD_ABOR_ID: mr = proxy_abort(cmd, proxy_sess, NULL); if ((proxy_sess->frontend_sess_flags & SF_XFER) || (proxy_sess->backend_sess_flags & SF_XFER)) { pr_trace_msg(trace_channel, 19, "received ABOR on frontend connection, " "closing frontend data connection"); if (session.d != NULL) { pr_inet_close(session.pool, proxy_sess->frontend_data_conn); proxy_sess->frontend_data_conn = session.d = NULL; } proxy_sess->frontend_sess_flags &= ~SF_XFER; proxy_sess->backend_sess_flags &= ~SF_XFER; } return mr; } /* If we are not connected to a backend server, then don't try to proxy * the command. */ if (!(proxy_sess_state & PROXY_SESS_STATE_CONNECTED)) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "declining to proxy %s command: not connected to backend server", (char *) cmd->argv[0]); return PR_DECLINED(cmd); } /* XXX Should any other commands, like TYPE or SYST, also be allowed through * to the backend server, prior to authentication? */ if (pr_cmd_cmp(cmd, PR_CMD_QUIT_ID) != 0) { /* If we have connected to a backend server, but we have NOT authenticated * to that backend server, then reject all commands as "out of sequence" * errors (i.e. malicious or misinformed clients). */ if (!(proxy_sess_state & PROXY_SESS_STATE_BACKEND_AUTHENTICATED)) { pr_response_add_err(R_530, _("Please login with USER and PASS")); return PR_ERROR(cmd); } } return proxy_cmd(cmd, proxy_sess, NULL); } MODRET proxy_post_prot(cmd_rec *cmd) { if (proxy_engine == FALSE) { return PR_DECLINED(cmd); } switch (proxy_tls_xfer_prot_policy) { case PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_CLEAR: proxy_tls_set_data_prot(FALSE); break; case PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_REQUIRED: proxy_tls_set_data_prot(TRUE); break; case PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_CLIENT: if (strcasecmp(cmd->arg, "P") == 0) { proxy_tls_set_data_prot(TRUE); } else if (strcasecmp(cmd->arg, "C") == 0) { proxy_tls_set_data_prot(FALSE); } break; } return PR_DECLINED(cmd); } /* Event handlers */ static void proxy_ctrl_read_ev(const void *event_data, void *user_data) { switch (proxy_role) { case PROXY_ROLE_REVERSE: if (proxy_sess_state & PROXY_SESS_STATE_CONNECTED) { proxy_restrict_session(); pr_event_unregister(&proxy_module, "mod_proxy.ctrl-read", proxy_ctrl_read_ev); } break; case PROXY_ROLE_FORWARD: /* We don't really need this event listener for forward proxying. */ pr_event_unregister(&proxy_module, "mod_proxy.ctrl-read", proxy_ctrl_read_ev); break; } } static void proxy_exit_ev(const void *event_data, void *user_data) { struct proxy_session *proxy_sess; proxy_sess = (struct proxy_session *) pr_table_get(session.notes, "mod_proxy.proxy-session", NULL); if (proxy_sess != NULL) { /* proxy_sess->frontend_ctrl_conn is session.c; let the core engine * close that connection. If we try to close it here via pr_inet_close(), * we risk segfaults due to double-free of the memory, stale pointers, etc. */ if (proxy_sess->frontend_ctrl_conn != NULL) { proxy_sess->frontend_ctrl_conn = NULL; } if (proxy_sess->frontend_data_conn != NULL) { /* Note: if session.d is NULL, ASSUME that the core API's session * cleanup already closed that connection, and so doing it here * would be redundant (and worse, an attempted double-free); * the frontend_data_conn and session.d are the same connection. */ if (session.d != NULL) { pr_inet_close(proxy_sess->pool, proxy_sess->frontend_data_conn); proxy_sess->frontend_data_conn = session.d = NULL; } } if (proxy_sess->backend_ctrl_conn != NULL) { proxy_inet_close(proxy_sess->pool, proxy_sess->backend_ctrl_conn); pr_inet_close(proxy_sess->pool, proxy_sess->backend_ctrl_conn); proxy_sess->backend_ctrl_conn = NULL; } if (proxy_sess->backend_data_conn != NULL) { proxy_inet_close(proxy_sess->pool, proxy_sess->backend_data_conn); pr_inet_close(proxy_sess->pool, proxy_sess->backend_data_conn); proxy_sess->backend_data_conn = NULL; } pr_table_remove(session.notes, "mod_proxy.proxy-session", NULL); } switch (proxy_role) { case PROXY_ROLE_REVERSE: proxy_reverse_sess_exit(session.pool); break; case PROXY_ROLE_FORWARD: break; } if (proxy_logfd >= 0) { (void) close(proxy_logfd); proxy_logfd = -1; } } #if defined(PR_SHARED_MODULE) static void proxy_mod_unload_ev(const void *event_data, void *user_data) { if (strcmp((const char *) event_data, "mod_proxy.c") != 0) { return; } /* Unregister ourselves from all events. */ pr_event_unregister(&proxy_module, NULL, NULL); destroy_pool(proxy_pool); proxy_pool = NULL; (void) close(proxy_logfd); proxy_logfd = -1; } #endif static void proxy_postparse_ev(const void *event_data, void *user_data) { int engine = FALSE; config_rec *c; c = find_config(main_server->conf, CONF_PARAM, "ProxyEngine", FALSE); if (c != NULL) { engine = *((int *) c->argv[0]); } else { server_rec *s; for (s = (server_rec *) server_list->xas_list; s; s = s->next) { c = find_config(s->conf, CONF_PARAM, "ProxyEngine", FALSE); if (c != NULL) { engine = *((int *) c->argv[0]); } if (engine) { break; } } } if (engine == FALSE) { return; } c = find_config(main_server->conf, CONF_PARAM, "ProxyTables", FALSE); if (c == NULL) { /* No ProxyTables configured, mod_proxy cannot run. */ pr_log_pri(PR_LOG_WARNING, MOD_PROXY_VERSION ": missing required ProxyTables directive, failing to start up"); pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BAD_CONFIG, "Missing required config"); } proxy_tables_dir = c->argv[0]; if (proxy_forward_init(proxy_pool, proxy_tables_dir) < 0) { pr_log_pri(PR_LOG_WARNING, MOD_PROXY_VERSION ": unable to initialize forward proxy, failing to start up: %s", strerror(errno)); pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BAD_CONFIG, "Failed forward proxy initialization"); } if (proxy_reverse_init(proxy_pool, proxy_tables_dir, 0) < 0) { pr_log_pri(PR_LOG_WARNING, MOD_PROXY_VERSION ": unable to initialize reverse proxy, failing to start up: %s", strerror(errno)); pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BAD_CONFIG, "Failed reverse proxy initialization"); } if (proxy_ssh_init(proxy_pool, proxy_tables_dir, 0) < 0) { pr_log_pri(PR_LOG_WARNING, MOD_PROXY_VERSION ": unable to initialize SSH support, failing to start up: %s", strerror(errno)); pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BAD_CONFIG, "Failed SSH initialization"); } if (proxy_tls_init(proxy_pool, proxy_tables_dir, 0) < 0) { pr_log_pri(PR_LOG_WARNING, MOD_PROXY_VERSION ": unable to initialize TLS support, failing to start up: %s", strerror(errno)); pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BAD_CONFIG, "Failed TLS initialization"); } } static void proxy_restart_ev(const void *event_data, void *user_data) { (void) proxy_forward_free(proxy_pool); (void) proxy_reverse_free(proxy_pool); (void) proxy_ssh_free(proxy_pool); (void) proxy_tls_free(proxy_pool); /* Do NOT close the database connection/handle here; we may have session * processes that have their own handles to that same file. */ } static void proxy_sess_reinit_ev(const void *event_data, void *user_data) { struct proxy_session *proxy_sess; int res; /* A HOST command changed the main_server pointer; reinitialize ourselves. */ pr_event_unregister(&proxy_module, "core.exit", proxy_exit_ev); pr_event_unregister(&proxy_module, "core.session-reinit", proxy_sess_reinit_ev); pr_event_unregister(&proxy_module, "mod_proxy.ctrl-read", proxy_ctrl_read_ev); pr_event_unregister(&proxy_module, "core.timeout-idle", proxy_timeoutidle_ev); pr_event_unregister(&proxy_module, "core.timeout-no-transfer", proxy_timeoutnoxfer_ev); pr_event_unregister(&proxy_module, "core.timeout-stalled", proxy_timeoutstalled_ev); /* Reset static variables, other session state. Note that we explicitly * do NOT reset the proxy_tables_dir variable; that is set during postparse, * and affects the entire daemon process. */ proxy_sess = (struct proxy_session *) pr_table_get(session.notes, "mod_proxy.proxy-session", NULL); if (proxy_sess != NULL) { proxy_ssh_sess_free(proxy_pool); proxy_tls_sess_free(proxy_pool); proxy_reverse_sess_free(proxy_pool, proxy_sess); proxy_forward_sess_free(proxy_pool, proxy_sess); (void) pr_table_remove(session.notes, "mod_proxy.proxy-session", NULL); proxy_session_free(proxy_pool, proxy_sess); } (void) close(proxy_logfd); proxy_logfd = -1; (void) proxy_db_close(proxy_pool, NULL); proxy_engine = FALSE; proxy_opts = 0UL; proxy_login_attempts = 0; proxy_role = PROXY_ROLE_REVERSE; proxy_tls_xfer_prot_policy = PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_REQUIRED; res = proxy_sess_init(); if (res < 0) { pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_SESSION_INIT_FAILED, NULL); } } static void proxy_shutdown_ev(const void *event_data, void *user_data) { int res; (void) proxy_forward_free(proxy_pool); (void) proxy_reverse_free(proxy_pool); (void) proxy_ssh_free(proxy_pool); (void) proxy_tls_free(proxy_pool); res = proxy_db_close(proxy_pool, NULL); if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error closing database: %s", strerror(errno)); } (void) proxy_db_free(); #if defined(HAVE_OSSL_PROVIDER_LOAD_OPENSSL) if (legacy_provider != NULL) { OSSL_PROVIDER_unload(legacy_provider); legacy_provider = NULL; } #endif /* HAVE_OSSL_PROVIDER_LOAD_OPENSSL */ destroy_pool(proxy_pool); proxy_pool = NULL; if (proxy_logfd >= 0) { (void) close(proxy_logfd); proxy_logfd = -1; } } static void unblock_responses(void) { const struct proxy_session *proxy_sess; int block_responses = FALSE; proxy_sess = pr_table_get(session.notes, "mod_proxy.proxy-session", NULL); if (proxy_sess != NULL && proxy_sess->use_ssh == TRUE) { /* We do not want mod_core's response flushed to the frontend client * for SSH connections. */ block_responses = TRUE; } pr_response_block(block_responses); } static void proxy_timeoutidle_ev(const void *event_data, void *user_data) { /* Unblock responses here, so that mod_core's response will be flushed * out to the frontend client. */ unblock_responses(); } static void proxy_timeoutnoxfer_ev(const void *event_data, void *user_data) { /* Unblock responses here, so that mod_xfer's response will be flushed * out to the frontend client. */ unblock_responses(); } static void proxy_timeoutstalled_ev(const void *event_data, void *user_data) { /* Unblock responses here, so that mod_xfer's response will be flushed * out to the frontend client. */ unblock_responses(); } /* XXX Do we want to support any Controls/ftpctl actions? */ /* Initialization routines */ static int proxy_init(void) { proxy_pool = make_sub_pool(permanent_pool); pr_pool_tag(proxy_pool, MOD_PROXY_VERSION); #if defined(PR_SHARED_MODULE) pr_event_register(&proxy_module, "core.module-unload", proxy_mod_unload_ev, NULL); #endif pr_event_register(&proxy_module, "core.postparse", proxy_postparse_ev, NULL); pr_event_register(&proxy_module, "core.restart", proxy_restart_ev, NULL); pr_event_register(&proxy_module, "core.shutdown", proxy_shutdown_ev, NULL); #if defined(HAVE_OSSL_PROVIDER_LOAD_OPENSSL) /* Load the "legacy" OpenSSL algorithm provider, for those SSH algorithms * that require support of algorithms that OpenSSL deemed "legacy". */ legacy_provider = OSSL_PROVIDER_load(NULL, "legacy"); #endif /* HAVE_OSSL_PROVIDER_LOAD_OPENSSL */ if (proxy_db_init(proxy_pool) < 0) { return -1; } return 0; } /* Set defaults for directives that mod_proxy should allow (but whose * values are checked e.g. by PRE_CMD handlers): * * AllowOverwrite * AllowStoreRestart * * Unless these directives have already been set, of course. */ static void proxy_set_sess_defaults(void) { config_rec *c; c = find_config(main_server->conf, CONF_PARAM, "AllowOverwrite", FALSE); if (c == NULL) { c = add_config_param_set(&main_server->conf, "AllowOverwrite", 1, NULL); c->argv[0] = palloc(c->pool, sizeof(unsigned char)); *((unsigned char *) c->argv[0]) = TRUE; c->flags |= CF_MERGEDOWN; } c = find_config(main_server->conf, CONF_PARAM, "AllowStoreRestart", FALSE); if (c == NULL) { c = add_config_param_set(&main_server->conf, "AllowStoreRestart", 1, NULL); c->argv[0] = palloc(c->pool, sizeof(unsigned char)); *((unsigned char *) c->argv[0]) = TRUE; c->flags |= CF_MERGEDOWN; } } static int proxy_sess_init(void) { config_rec *c; int res; struct proxy_session *proxy_sess; const char *sess_dir = NULL; /* We have to register our HOST handler here, even if ProxyEngine is off, * as the current vhost may be disabled BUT the requested vhost may be * enabled. */ pr_event_register(&proxy_module, "core.session-reinit", proxy_sess_reinit_ev, NULL); c = find_config(main_server->conf, CONF_PARAM, "ProxyEngine", FALSE); if (c != NULL) { proxy_engine = *((int *) c->argv[0]); } if (proxy_engine == FALSE) { return 0; } pr_event_register(&proxy_module, "core.exit", proxy_exit_ev, NULL); pr_event_register(&proxy_module, "mod_proxy.ctrl-read", proxy_ctrl_read_ev, NULL); /* Install event handlers for timeouts, so that we can properly close * the connections on either side. */ pr_event_register(&proxy_module, "core.timeout-idle", proxy_timeoutidle_ev, NULL); pr_event_register(&proxy_module, "core.timeout-no-transfer", proxy_timeoutnoxfer_ev, NULL); pr_event_register(&proxy_module, "core.timeout-stalled", proxy_timeoutstalled_ev, NULL); c = find_config(main_server->conf, CONF_PARAM, "ProxyLog", FALSE); if (c != NULL) { char *logname; logname = c->argv[0]; if (strncasecmp(logname, "none", 5) != 0) { int xerrno; pr_signals_block(); PRIVS_ROOT res = pr_log_openfile(logname, &proxy_logfd, PR_LOG_SYSTEM_MODE); xerrno = errno; PRIVS_RELINQUISH pr_signals_unblock(); if (res < 0) { if (res == -1) { pr_log_pri(PR_LOG_NOTICE, MOD_PROXY_VERSION ": notice: unable to open ProxyLog '%s': %s", logname, strerror(xerrno)); } else if (res == PR_LOG_WRITABLE_DIR) { pr_log_pri(PR_LOG_NOTICE, MOD_PROXY_VERSION ": notice: unable to open ProxyLog '%s': parent directory is " "world-writable", logname); } else if (res == PR_LOG_SYMLINK) { pr_log_pri(PR_LOG_NOTICE, MOD_PROXY_VERSION ": notice: unable to open ProxyLog '%s': cannot log to a symlink", logname); } } } } if (proxy_pool == NULL) { proxy_pool = make_sub_pool(session.pool); pr_pool_tag(proxy_pool, MOD_PROXY_VERSION " Session Pool"); } c = find_config(main_server->conf, CONF_PARAM, "ProxyOptions", FALSE); while (c != NULL) { unsigned long opts; pr_signals_handle(); opts = *((unsigned long *) c->argv[0]); proxy_opts |= opts; c = find_config_next(c, c->next, CONF_PARAM, "ProxyOptions", FALSE); } c = find_config(main_server->conf, CONF_PARAM, "ProxyRole", FALSE); if (c != NULL) { proxy_role = *((int *) c->argv[0]); } proxy_random_init(); /* Set defaults for directives that mod_proxy should allow. */ proxy_set_sess_defaults(); /* Allocate our own session structure, for tracking proxy-specific * fields. Use the session.notes table for stashing/retrieving it as * needed. */ proxy_sess = (struct proxy_session *) proxy_session_alloc(proxy_pool); res = pr_table_add(session.notes, "mod_proxy.proxy-session", proxy_sess, sizeof(struct proxy_session)); if (res < 0 && errno == ENOSPC) { int nents, nmaxents; nents = pr_table_count(session.notes); nmaxents = nents * 2; /* Attempt to handle the unusual case where the table is full, since * we really need this note. */ pr_trace_msg(trace_channel, 1, "session notes table is full (%u), increasing entry limit to %u", nents, nmaxents); if (pr_table_ctl(session.notes, PR_TABLE_CTL_SET_MAX_ENTS, &nmaxents) == 0) { res = pr_table_add(session.notes, "mod_proxy.proxy-session", proxy_sess, sizeof(struct proxy_session)); } else { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error increasing session notes max entries: %s", strerror(errno)); res = -1; errno = ENOSPC; } } if (res < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error stashing proxy session note: %s", strerror(errno)); /* This is a fatal error; mod_proxy won't function without this note. */ errno = EPERM; return -1; } /* Provide default note values. */ (void) pr_table_add_dup(session.notes, "mod_proxy.backend-port", "0", 0); c = find_config(main_server->conf, CONF_PARAM, "ProxySourceAddress", FALSE); if (c != NULL) { proxy_sess->src_addr = c->argv[0]; } c = find_config(main_server->conf, CONF_PARAM, "ProxyDataTransferPolicy", FALSE); if (c != NULL) { proxy_sess->dataxfer_policy = *((int *) c->argv[0]); } c = find_config(main_server->conf, CONF_PARAM, "ProxyDirectoryListPolicy", FALSE); if (c != NULL) { proxy_sess->dirlist_policy = *((int *) c->argv[0]); proxy_sess->dirlist_opts = *((unsigned long *) c->argv[1]); } c = find_config(main_server->conf, CONF_PARAM, "ProxyTimeoutConnect", FALSE); if (c != NULL) { proxy_sess->connect_timeout = *((int *) c->argv[0]); } else { proxy_sess->connect_timeout = PROXY_CONNECT_DEFAULT_TIMEOUT; } c = find_config(main_server->conf, CONF_PARAM, "ProxyTimeoutLinger", FALSE); if (c != NULL) { proxy_sess->linger_timeout = *((int *) c->argv[0]); } else { proxy_sess->linger_timeout = PROXY_LINGER_DEFAULT_TIMEOUT; } c = find_config(main_server->conf, CONF_PARAM, "ProxyTLSTransferProtectionPolicy", FALSE); if (c != NULL) { proxy_tls_xfer_prot_policy = *((int *) c->argv[0]); switch (proxy_tls_xfer_prot_policy) { case PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_CLIENT: case PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_REQUIRED: proxy_tls_set_data_prot(TRUE); break; case PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_CLEAR: proxy_tls_set_data_prot(FALSE); break; default: /* ignore */ break; } } /* Every proxy session starts off in the ProxyTables/empty/ directory. */ sess_dir = pdircat(proxy_pool, proxy_tables_dir, "empty", NULL); if (pr_fsio_chdir_canon(sess_dir, TRUE) < 0) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error setting session directory to '%s': %s", sess_dir, strerror(errno)); } /* Close any database handle inherited from our parent, and open a new * one, per SQLite3 recommendation. */ (void) proxy_db_close(proxy_pool, NULL); if (proxy_ssh_sess_init(proxy_pool, proxy_sess, 0) < 0) { pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION, "Unable to initialize SSH API"); } if (proxy_tls_sess_init(proxy_pool, proxy_sess, 0) < 0) { pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION, "Unable to initialize TLS API"); } switch (proxy_role) { case PROXY_ROLE_REVERSE: if (proxy_reverse_sess_init(proxy_pool, proxy_tables_dir, proxy_sess, 0) < 0) { pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION, "Unable to initialize reverse proxy API"); } set_auth_check(proxy_reverse_have_authenticated); break; case PROXY_ROLE_FORWARD: if (proxy_forward_sess_init(proxy_pool, proxy_tables_dir, proxy_sess) < 0) { pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION, "Unable to initialize forward proxy API"); } /* XXX TODO: * DisplayConnect */ set_auth_check(proxy_forward_have_authenticated); break; } /* XXX set protocol? What about ssh2 proxying? How to interact * with mod_sftp, which doesn't have the same pipeline of request * handling? (Need to add PRE_REQ handling in mod_sftp, to support proxying.) * * pr_session_set_protocol("proxy"); ? * * If we do this, we should also add separate "proxy" rows to the DelayTable. */ /* We have to use our own command event loop, since we will also need to * watch any data transfer connections with the backend server, in addition * to the client control connection. */ pr_cmd_set_handler(proxy_cmd_loop); proxy_remove_symbols(); return 0; } /* Module API tables */ static conftable proxy_conftab[] = { { "ProxyDataTransferPolicy", set_proxydataxferpolicy, NULL }, { "ProxyDatastore", set_proxydatastore, NULL }, { "ProxyDirectoryListPolicy", set_proxydirlistpolicy, NULL }, { "ProxyEngine", set_proxyengine, NULL }, { "ProxyForwardEnabled", set_proxyforwardenabled, NULL }, { "ProxyForwardMethod", set_proxyforwardmethod, NULL }, { "ProxyForwardTo", set_proxyforwardto, NULL }, { "ProxyLog", set_proxylog, NULL }, { "ProxyOptions", set_proxyoptions, NULL }, { "ProxyRetryCount", set_proxyretrycount, NULL }, { "ProxyReverseConnectPolicy",set_proxyreverseconnectpolicy, NULL }, { "ProxyReverseServers", set_proxyreverseservers, NULL }, { "ProxyRole", set_proxyrole, NULL }, { "ProxySourceAddress", set_proxysourceaddress, NULL }, { "ProxyTables", set_proxytables, NULL }, { "ProxyTimeoutConnect", set_proxytimeoutconnect, NULL }, { "ProxyTimeoutLinger", set_proxytimeoutlinger, NULL }, /* SSH support */ { "ProxySFTPCiphers", set_proxysftpciphers, NULL }, { "ProxySFTPCompression", set_proxysftpcompression, NULL }, { "ProxySFTPDigests", set_proxysftpdigests, NULL }, { "ProxySFTPHostKey", set_proxysftphostkey, NULL }, { "ProxySFTPKeyExchanges", set_proxysftpkeyexchanges, NULL }, { "ProxySFTPOptions", set_proxysftpoptions, NULL }, { "ProxySFTPPassPhraseProvider", set_proxysftppassphraseprovider, NULL }, { "ProxySFTPServerAlive", set_proxysftpserveralive, NULL }, { "ProxySFTPServerMatch", set_proxysftpservermatch, NULL }, { "ProxySFTPVerifyServer", set_proxysftpverifyserver, NULL }, /* TLS support */ { "ProxyTLSCACertificateFile",set_proxytlscacertfile, NULL }, { "ProxyTLSCACertificatePath",set_proxytlscacertpath, NULL }, { "ProxyTLSCARevocationFile", set_proxytlscacrlfile, NULL }, { "ProxyTLSCARevocationPath", set_proxytlscacrlpath, NULL }, { "ProxyTLSCertificateFile", set_proxytlscertfile, NULL }, { "ProxyTLSCertificateKeyFile",set_proxytlscertkeyfile, NULL }, { "ProxyTLSCipherSuite", set_proxytlsciphersuite, NULL }, { "ProxyTLSEngine", set_proxytlsengine, NULL }, { "ProxyTLSOptions", set_proxytlsoptions, NULL }, { "ProxyTLSPreSharedKey", set_proxytlspresharedkey, NULL }, { "ProxyTLSProtocol", set_proxytlsprotocol, NULL }, { "ProxyTLSTimeoutHandshake", set_proxytlstimeouthandshake, NULL }, { "ProxyTLSTransferProtectionPolicy", set_proxytlsxferprotpolicy, NULL }, { "ProxyTLSVerifyServer", set_proxytlsverifyserver, NULL }, /* Support TransferPriority for proxied connections? */ /* Deliberately ignore/disable HiddenStores in mod_proxy configs */ /* Two timeouts, one for frontend and one for backend? */ { NULL } }; static cmdtable proxy_cmdtab[] = { /* XXX Should this be marked with a CL_ value, for logging? */ { CMD, C_ANY, G_NONE, proxy_any, FALSE, FALSE }, { POST_CMD, C_PROT, G_NONE, proxy_post_prot, FALSE, FALSE }, { 0, NULL } }; module proxy_module = { /* Always NULL */ NULL, NULL, /* Module API version */ 0x20, /* Module name */ "proxy", /* Module configuration handler table */ proxy_conftab, /* Module command handler table */ proxy_cmdtab, /* Module authentication handler table */ NULL, /* Module initialization */ proxy_init, /* Session initialization */ proxy_sess_init, /* Module version */ MOD_PROXY_VERSION }; proftpd-mod_proxy-0.9.5/mod_proxy.h.in000066400000000000000000000106141475737016700200420ustar00rootroot00000000000000/* * ProFTPD - mod_proxy * Copyright (c) 2012-2025 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #ifndef MOD_PROXY_H #define MOD_PROXY_H #include "conf.h" #include "privs.h" #include #if HAVE_SYS_MMAN_H # include #endif /* Define if you have the header. */ #undef HAVE_ZLIB_H /* Define if you have OpenSSL with crippled AES support. */ #undef HAVE_AES_CRIPPLED_OPENSSL /* Define if you have OpenSSL with EVP_aes_128_ctr support. */ #undef HAVE_EVP_AES_128_CTR_OPENSSL /* Define if you have OpenSSL with EVP_aes_192_ctr support. */ #undef HAVE_EVP_AES_192_CTR_OPENSSL /* Define if you have OpenSSL with EVP_aes_256_ctr support. */ #undef HAVE_EVP_AES_256_CTR_OPENSSL /* Define if you have OpenSSL with EVP_aes_256_gcm support. */ #undef HAVE_EVP_AES_256_GCM_OPENSSL /* Define if you have OpenSSL with OSSL_PROVIDER_load support. */ #undef HAVE_OSSL_PROVIDER_LOAD_OPENSSL /* Define if you have OpenSSL with SHA256 support. */ #undef HAVE_SHA256_OPENSSL /* Define if you have OpenSSL with SHA512 support. */ #undef HAVE_SHA512_OPENSSL /* Define if you have OpenSSL with X448 support. */ #undef HAVE_X448_OPENSSL /* Define if you have the sqlite3.h header. */ #undef HAVE_SQLITE3_H #if !defined(HAVE_SQLITE3_H) # error "SQLite library/headers required" #endif /* Define if you have the random(3) function. */ #undef HAVE_RANDOM /* Define if you have the sqlite3_stmt_readonly() function. */ #undef HAVE_SQLITE3_STMT_READONLY /* Define if you have the sqlite3_trace() function. */ #undef HAVE_SQLITE3_TRACE /* Define if you have the sqlite3_trace_v2() function. */ #undef HAVE_SQLITE3_TRACE_V2 /* Define if you have the srandom(3) function. */ #undef HAVE_SRANDOM /* Define if you have the strnstr(3) function. */ #undef HAVE_STRNSTR #define MOD_PROXY_VERSION "mod_proxy/0.9.5" /* Make sure the version of proftpd is as necessary. */ #if PROFTPD_VERSION_NUMBER < 0x0001030706 # error "ProFTPD 1.3.7a or later required" #endif /* mod_proxy option flags */ #define PROXY_OPT_USE_PROXY_PROTOCOL_V1 0x0001 #define PROXY_OPT_SHOW_FEATURES 0x0002 #define PROXY_OPT_USE_REVERSE_PROXY_AUTH 0x0004 #define PROXY_OPT_USE_DIRECT_DATA_TRANSFERS 0x0008 #define PROXY_OPT_IGNORE_CONFIG_PERMS 0x0010 #define PROXY_OPT_USE_PROXY_PROTOCOL_V2 0x0020 #define PROXY_OPT_USE_PROXY_PROTOCOL_V2_TLVS 0x0040 #define PROXY_OPT_ALLOW_FOREIGN_ADDRESS 0x0080 #define PROXY_OPT_IGNORE_FOREIGN_ADDRESS 0x0100 /* mod_proxy datastores */ #define PROXY_DATASTORE_SQLITE 1 #define PROXY_DATASTORE_REDIS 2 /* Miscellaneous */ extern int proxy_logfd; extern module proxy_module; extern pool *proxy_pool; extern unsigned long proxy_opts; extern unsigned int proxy_sess_state; extern int proxy_datastore; extern void *proxy_datastore_data; extern size_t proxy_datastore_datasz; /* mod_proxy session state flags */ #define PROXY_SESS_STATE_PROXY_AUTHENTICATED 0x0001 #define PROXY_SESS_STATE_CONNECTED 0x0002 #define PROXY_SESS_STATE_BACKEND_AUTHENTICATED 0x0004 #define PROXY_SESS_STATE_BACKEND_HAS_CTRL_TLS 0x0008 #define PROXY_SESS_STATE_BACKEND_HAS_DATA_TLS 0x0010 #define PROXY_SESS_STATE_SSH_HAVE_KEX 0x0020 #define PROXY_SESS_STATE_SSH_HAVE_SERVICE 0x0040 #define PROXY_SESS_STATE_SSH_HAVE_AUTH 0x0080 #define PROXY_SESS_STATE_SSH_REKEYING 0x0100 #define PROXY_SESS_STATE_SSH_HAVE_EXT_INFO 0x0200 #ifndef PROXY_DEFAULT_RETRY_COUNT # define PROXY_DEFAULT_RETRY_COUNT 5 #endif /* mod_proxy SSH roles */ #define PROXY_SSH_ROLE_SERVER 1 #define PROXY_SSH_ROLE_CLIENT 2 #endif /* MOD_PROXY_H */ proftpd-mod_proxy-0.9.5/mod_proxy.html000066400000000000000000003032661475737016700201620ustar00rootroot00000000000000 ProFTPD module mod_proxy

    ProFTPD module mod_proxy



    The purpose of the mod_proxy module is to provide FTP proxying capabilities in proftpd, both reverse (or "gateway") proxying and forward proxying.

    Installation instructions are discussed here. Note that mod_proxy requires ProFTPD 1.3.6rc2 or later. Detailed notes on best practices for using this module are here.

    This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit (http://www.openssl.org/).

    This product includes cryptographic software written by Eric Young (eay@cryptsoft.com).

    The most current version of mod_proxy can be found at:

      https://github.com/Castaglia/proftpd-mod_proxy.git
    

    Author

    Please contact TJ Saunders <tj at castaglia.org> with any questions, concerns, or suggestions regarding this module.

    Thanks

    2015-08-24: Thanks to Michael Toth <mtoth at queldor.net> for helping test multiple iterations of mod_proxy with IIS servers.

    Directives


    ProxyDataTransferPolicy

    Syntax: ProxyDataTransferPolicy client|active|passive|pasv|epsv|port|eprt
    Default: ProxyDataTransferPolicy client
    Context: server config, <VirtualHost>, <Global>
    Module: mod_proxy
    Compatibility: 1.3.6rc1 and later

    The ProxyDataTransferPolicy directive configures the data transfer policy that mod_proxy uses when performing data transfers (e.g. file uploads/downloads, directory listings) with the backend/destination server.

    The currently supported policies are:

    • client

      This policy indicates that mod_proxy will use whatever the connected client uses. Thus if the client sends PORT, mod_proxy will send PORT to the backend/destination server.

      This is the recommended policy in most cases.

    • active

      Regardless of the commands sent by the client, mod_proxy will use only active data transfers (i.e. using PORT commands) with the backend/destination server.

    • passive

      Regardless of the commands sent by the client, mod_proxy will use only passive data transfers (i.e. using PASV commands) with the backend/destination server.

    • PASV

      Regardless of the commands sent by the client, mod_proxy will use only PASV commands with the backend/destination server.

    • PORT

      Regardless of the commands sent by the client, mod_proxy will use only PORT commands with the backend/destination server.

    • EPSV

      Regardless of the commands sent by the client, mod_proxy will use only EPSV commands with the backend/destination server.

    • EPRT

      Regardless of the commands sent by the client, mod_proxy will use only EPRT commands with the backend/destination server.


    ProxyDatastore

    Syntax: ProxyDatastore type [info]
    Default: ProxyDatastore SQLite
    Context: server config
    Module: mod_proxy
    Compatibility: 1.3.6rc1 and later

    The ProxyDatastore directive configures the type of datastore that mod_proxy uses for persistence. The currently supported datastore types are:

    • Redis
    • SQLite

    Note that the Redis type also requires the info parameter, namely a prefix for all of the Redis keys. This prefix must be different/unique among all of your mod_proxy servers using that Redis server/cluster; the prefix is used to keep all of the data for this server separate from all other servers. For example:

      <IfModule mod_proxy.c>
    
        ...
        <IfModule mod_redis.c>
          # Use our IP address as our prefix
          ProxyDatastore Redis 1.2.3.4.
        </IfModule>
      </IfModule>
    


    ProxyDirectoryListPolicy

    Syntax: ProxyDirectoryListPolicy client|"LIST" [opt1 ...]
    Default: ProxyDirectoryListPolicy client
    Context: server config, <VirtualHost>, <Global>
    Module: mod_proxy
    Compatibility: 1.3.6rc1 and later

    The ProxyDirectoryListPolicy directive configures the directory list policy that mod_proxy uses when performing directory lists with the backend/destination server.

    The currently supported policies are:

    • client

      This policy indicates that mod_proxy will use whatever the connected client uses. Thus if the client sends MLSD, mod_proxy will send MLSD to the backend/destination server.

      This is the recommended policy in most cases.

    • LIST

      This policy instructs mod_proxy to use the LIST command with the backend/destination server, regardless of the command used by the client. The mod_proxy module then handles any reformatting of the LIST response into the response needed by the client. For example, if the client sends MLSD but the backend/destination server does not support this command, mod_proxy will send LIST instead, and translate the backend format into the client-requested format.

      The current implementation of this policy handles LIST-<MLSD translations only.

    You may also provide options; the currently implemented options are:
    • UseSlink

      Use this option to have mod_proxy use the broken "OS.unix=slink" syntax, preferred by FTP clients such as FileZilla, for indicating symlinks, rather than the more correct "OS.unix=symlink" syntax. See Bug#3318 for a more detailed discussion.


    ProxyEngine

    Syntax: ProxyEngine on|off
    Default: None
    Context: server config, <VirtualHost>, <Global>
    Module: mod_proxy
    Compatibility: 1.3.6rc1 and later

    The ProxyEngine directive toggles the support for proxying by mod_proxy. This is usually used inside a <VirtualHost> section to enable proxying of FTP sessions for a particular virtual host. By default mod_proxy is disabled for both the main server and all configured virtual hosts.


    ProxyForwardEnabled

    Syntax: ProxyForwardEnabled on|off
    Default: None
    Context: <Class>
    Module: mod_proxy
    Compatibility: 1.3.6rc2 and later

    The ProxyForwardEnabled directive determines whether a client can use mod_proxy for forward proxying, based on that client's class.

    By default, mod_proxy rejects any forward proxy request from any client, with the exception of clients connecting from RFC 1918 addresses:

      192.168.0.0/16
      172.16.0.0/12
      10.0.0.0/8
    
    This is done as a security measure: open/unrestricted proxy servers are dangerous both to your network and to the Internet at large. Thus to make it possible for clients to use your server for forward proxying, they must be explicitly enabled to do so.

    Example:

      <Class forward-proxy>
        From 1.2.3.4/12
    
        # Allow clients from this class to use FTP forward proxying
        ProxyForwardEnabled on
      </Class>
    

    See also: ProxyForwardTo


    ProxyForwardMethod

    Syntax: ProxyForwardMethod method
    Default: None
    Context: server config, <VirtualHost>, <Global>
    Module: mod_proxy
    Compatibility: 1.3.6rc1 and later

    The ProxyForwardMethod directive configures the method that clients can use for requesting forward proxying. Some methods require that the client authenticate to the proxy first, and then separately authenticate to the destination server; these methods differ on just when the client specifies the destination server. Other methods do not require proxy authentication. There are many variations on a theme with these methods.

    The currently supported methods are:

    • proxyuser,user@host

      This method indicates that proxy authentication is required. The client first authenticates to the proxy via USER/PASS commands:

        USER proxy-user
        PASS proxy-passwd
      
      Then the client authenticates again, this time including the address (and optionally port) of the destination server as part of the second USER command:
        USER real-user@ftp.example.com
        PASS real-passwd
      
      The mod_proxy module will remove the destination address portion of the second USER command before proxying it to the destination server.
    • proxyuser@host,user

      This method indicates that proxy authentication is required. The client first authenticates to the proxy via USER/PASS commands; note that the destination address (and optionally port) is included as part of the first USER command:

        USER proxy-user@ftp.example.com
        PASS proxy-passwd
      
      Then the client authenticates again, this time sending the USER/PASS commands to authenticate to the destination server:
        USER real-user
        PASS real-passwd
      
    • user@host

      This methods indicates that no proxy authentication is used. The client indicates the destination address (and optionally port) of the server as part of the USER command:

        USER real-user@ftp.example.com
        PASS real-passwd
      
      The mod_proxy module will remove the destination address portion of the USER command before proxying it to the destination server.
    • user@sni

      This methods indicates that no proxy authentication is used. The client indicates the destination address (and optionally port) of the server as part of a TLS SNI (Server Name Indication) portion of a TLS session:

        AUTH TLS handshake with SNI
        USER real-user
        PASS real-passwd
      

    Configuring the FTP client's proxy settings to match the above methods varies greatly, depending on the FTP client.


    ProxyForwardTo

    Syntax: ProxyForwardTo [!]pattern [flags]
    Default: None
    Context: server config, <VirtualHost>, <Global>
    Module: mod_proxy
    Compatibility: 1.3.6rc1 and later

    The ProxyForwardTo directive is used to restrict which hosts/domains can be requested for forward proxying. The destination host/port for forward proxying must match the configured pattern regular expression, or the forward proxying request will fail.

    The optional flags parameter, if present, modifies how the given pattern will be evaludated. The supported flags are:

    • nocase|NC (no case)
      This makes the pattern case-insensitive, i.e. there is no difference between 'A-Z' and 'a-z' when pattern is matched against the path

    ProxyForwardTo limits the destination hosts to which clients can request forward proxying; by contrast, ProxyForwardEnabled controls forward proxying based on where clients connect from.

    Example:

      # Limit forward proxying to specific host and port
      ProxyForwardTo ^ftp.example.com:21$
    


    ProxyLog

    Syntax: ProxyLog path|"none"
    Default: None
    Context: server config, <VirtualHost>, <Global>
    Module: mod_proxy
    Compatibility: 1.3.6rc1 and later

    The ProxyLog directive is used to specify a log file for mod_proxy's reporting on a per-server basis. The path parameter given must be the full path to the file to use for logging.

    Note that this path must not be to a world-writable directory and, unless AllowLogSymlinks is explicitly set to on (generally a bad idea), the path must not be a symbolic link.


    ProxyOptions

    Syntax: ProxyOptions opt1 ...
    Default: None
    Context: server config, <VirtualHost>, <Global>
    Module: mod_proxy
    Compatibility: 1.3.6rc1 and later

    The ProxyOptions directive is used to configure various optional behavior of mod_proxy. For example:

      ProxyOptions UseProxyProtocolV1
    

    The currently implemented options are:

    • AllowForeignAddress

      The AllowForeignAddress directive controls the policy for frontend data transfer requests from clients connecting to the proxy server; it does not apply to backend data transfer requests. For those, you will want to use this AllowForeignAddress option:

        # Allow for cases where the backend server tells us to use a different IP
        # address for data transfers than the IP address to which mod_proxy connected.
        ProxyOptions AllowForeignAddress
          

      Note that the IgnoreForeignAddress option takes precedence over this option.

    • IgnoreForeignAddress

      Use this option to tell the mod_proxy module to always use the same IP address for passive data transfers to the backend server as used for the control connection, ignoring any different IP address that the backend server may provide in its PASV response.

      This option may be needed in cases where you see backend data transfers fail with errors logged such as:

            unable to connect to 172.16.1.2#8200: Network unreachable
            unable to connect to 172.16.2.2#8200: Connection refused
          
      Example:
        # When the backend server tells us to use a different IP address for data
        # transfers than the IP address to which mod_proxy connected, ignore that
        # different IP address and use the original initial IP address.
        ProxyOptions IgnoreForeignAddress
          

      Note that this option takes precedence over the AllowForeignAddress option.

    • ShowFeatures

      When reverse proxying, mod_proxy defaults to not responding to the FTP FEAT command, which is used to determine the supported features/capabilities of the FTP server; this is done to prevent leaking of information about internal FTP servers to the outside world. However, some clients rely on the FEAT data. For such clients/use cases, use this option to tell mod_proxy to proxy the FEAT command/response to the backend server.

    • UseDirectDataTransfers

      The mod_proxy module will, by default, proxy all data transfers through the proxy server. Some sites may wish to have the FTP data transfers occur directly between the frontend client and the backend server, to avoid the latency/overhead of the proxying. Use this option to enable these direct data transfers (akin to Direct Server Return, or DSR), for both forward and reverse proxy roles:

        # Enable data transfers directly from frontend client to/from backend server
        ProxyOptions UseDirectDataTransfers
          
    • UseProxyProtocolV1

      When mod_proxy connects to the backend/destination server, use the PROXY V1 protocol, sending the human-readable PROXY command to the destination server. This allows backend servers to implement access controls/logging, based on the IP address of the connecting client. The mod_proxy_protocol ProFTPD module can be used to handle the PROXY command on the receiving side, i.e. when using proftpd as the backend/destination server.

      Note: do not use this option unless the backend server does support the PROXY V1 protocol. Otherwise, the PROXY protocol message will only confuse the backend server, possibly leading to connection/login failures.

    • UseProxyProtocolV2

      When mod_proxy connects to the backend/destination server, use the PROXY V2 protocol, sending a binary-encoded "PROXY" command to the destination server. This allows backend servers to implement access controls/logging, based on the IP address of the connecting client. The mod_proxy_protocol ProFTPD module can be used to handle the "PROXY" command on the receiving side, i.e. when using proftpd as the backend/destination server.

      Note: do not use this option unless the backend server does support the PROXY V2 protocol. Otherwise, the "PROXY" protocol message will only confuse the backend server, possibly leading to connection/login failures.

    • UseProxyProtocolV2TLVs

      When mod_proxy is configured to use the PROXY protocol V2, via the UseProxyProtocolV2 option, this option tells mod_proxy to send additional data as Type/Length/Value (TLV). These TLVs include:

      • ALPN (Application Layer Protocol Negotiation)
      • Authority (Hostname provided by client)
      • SSL/TLS (whether SSL/TLS is in use at the time of the PROXY protocol)
      • SSL/TLS protocol version
      • SSL/TLS cipher
      • Unique ID (when mod_unique_id is present/used)

      Note: use of the UseProxyProtocolV2 option is also required for the TLVs to be sent.

    • UseReverseProxyAuth

      When reverse proxying, mod_proxy delegates user authentication to the selected backend server. However, there are some sites which need to centralize user authentication in the proxy; use this option to require proxy authentication for such cases. Note that this option only pertains to reverse proxy connections; proxy authentication when forward proxying is determined by the ProxyForwardMethod directive.


    ProxyRetryCount

    Syntax: ProxyRetryCount count
    Default: ProxyRetryCount 5
    Context: server config, <VirtualHost>, <Global>
    Module: mod_proxy
    Compatibility: 1.3.6rc1 and later

    The ProxyRetryCount directive configures the number of times mod_proxy will attempt to connect to the backend/destination server. The default is 5 attempts.


    ProxyReverseConnectPolicy

    Syntax: ProxyReverseConnectPolicy policy
    Default: RoundRobin
    Context: server config, <VirtualHost>, <Global>
    Module: mod_proxy
    Compatibility: 1.3.6rc1 and later

    The ProxyReverseConnectPolicy directive configures the policy that mod_proxy will use for selecting the backend server when reverse proxying.

    The currently supported policies are:

    • LeastConns

      Select the backend server with the lowest number of proxied connections.

    • LeastResponseTime

      Select the backend server with the least response time; this is determined based on the connect time to the backend server, and its number of current connections.

    • PerGroup

      Select a backend server based on the primary group of the authenticated USER name used by the connecting client; any future connections using that same USER name will be routed to the same backend server.

      Note: use of this ProxyReverseConnectPolicy also requires use of the UseReverseProxyAuth ProxyOption, as authenticating the given USER name is required for discovering the primary group of that user.

    • PerHost

      Select a backend server based on the IP address of the connecting client; any future connections from that IP address will be routed to the same backend server.

    • PerUser

      Select a backend server based on the USER name used by the connecting client; any future connections using that same USER name will be routed to the same backend server.

    • Random

      Randomly select any of the backend servers.

    • RoundRobin

      Select the next backend server in the list.

    • Shuffle

      Similar to the Random policy, except the selection happens from the not-yet-chosen backend servers. This means that all backend servers will eventually be used evenly, just in a random order.


    ProxyReverseServers

    Syntax: ProxyReverseServers servers
    Default: None
    Context: server config, <VirtualHost>, <Global>
    Module: mod_proxy
    Compatibility: 1.3.6rc1 and later

    The ProxyReverseServers directive configures the list of servers to be used as the backend servers for reverse proxying.

    Each server must be configured as a URL. Only the "ftp" and "ftps" schemes are currently supported. If not specified, the port will be 21. IPv6 addresses must be enclosed within square brackets. Thus, for example, the following are all valid URLs:

      ftp://ftp1.example.com:2121
      ftp://1.2.3.4
      ftp://[::ffff:6.7.8.9]:2121
      ftps://ftp2.example.com
      ftps://ftp3.example.com:990
    
    And using them all in the configuration would look like:
      ProxyReverseServers ftp://ftp1.example.com:2121 ftps://1.2.3.4 ftp://[::ffff:6.7.8.9]:2121
    

    The backend servers can also be discovered via DNS SRV or TXT records, using SRV/TXT URL scheme variants, e.g.:

      # Discover backend addresses via DNS SRV records
      ftp+srv://_ftp._tcp.castaglia.org
    
      # Discover backend addresses via DNS TXT records (which must be an FTP URL)
      ftp+txt://castaglia.org
    
    These SRV/TXT URL scheme variations also apply to FTPS URLs. Note that any explicit port numbers provided in URLs using these SRV/TXT scheme variants will be ignored; the actual port numbers to use will be discovered from the SRV and TXT DNS records.

    The backend servers can also be contained in a list in a JSON file, e.g.:

    [
      "ftp://ftp1.example.com:2121",
      "ftp://[::ffff:6.7.8.9]:2121"
    ]
    
    You then only need to configure the path to that JSON file:
      ProxyReverseServers file:/path/to/backends.json
    

    Note that mod_proxy has strict requirements for the permissions on ProxyReverseServers files. The configured file must not be world-writable, since that would allow any user on the system to modify the file, directing proxied connections anywhere else. If a world-writable ProxyReverseServers file is found, you will see the following error message logged:

      unable to use world-writable ProxyReverseServers '/opt/proxy/reverse.json' (perms 0666): Operation not permitted
    
    In addition, the directory containing the ProxyReverseServers file cannot be world-writable, either. Even if the file itself is not world-writable, being in a world-writable directory means that any user on the system can delete that file, and write a new file. When a world-writable directory is found, the following error is logged:
      unable to use ProxyReverseServers '/opt/proxy/reverse.json' from world-writable directory '/opt/proxy' (perms 0777): Operation not permitted
    

    The backend servers can also be provided from an external SQL database, queried by mod_proxy via SQLNamedQuery. For example, assuming a schema such as this:

      CREATE TABLE proxy_user_servers (
        user_name TEXT,
        url TEXT
      );
    
      CREATE INDEX proxy_user_servers_name_idx ON proxy_user_servers (user_name);
    
    where url contains URLs, e.g. "ftp://1.2.3.4:21". Then you would configure mod_proxy to query for those per-user backend URLs using ProxyReverseServers like this:
      <IfModule mod_sql.c>
        ...
        SQLNamedQuery get-user-servers SELECT "url FROM proxy_user_servers WHERE user_name = %{0}"
      </IfModule>
    
      ProxyRole reverse
      ProxyReverseConnectPolicy PerUser
      ProxyReverseServers sql:/get-user-servers
    

    Given that mod_proxy uses SQLite, does that mean that the table used for storing these per-user URLs must be SQLite? No. The use of SQLNamedQuery means that any database, supported by mod_sql, can be used.


    ProxyRole

    Syntax: ProxyRole role
    Default: None
    Context: server config, <VirtualHost>, <Global>
    Module: mod_proxy
    Compatibility: 1.3.6rc1 and later

    The ProxyRole directive configures whether mod_proxy will perform forward or reverse proxying. The list of supported roles are thus:

    • forward

      Perform forward proxying.

    • reverse

      Perform reverse proxying.

    Note that the ProxyRole directive is required for mod_proxy to function. If this directive is not configured, connections to mod_proxy will fail.


    ProxySFTPCiphers

    Syntax: ProxySFTPCiphers algo1 ...
    Default: None
    Context: server config, <VirtualHost>, <Global>
    Module: mod_proxy
    Compatibility: 1.3.8rc3 and later

    The ProxySFTPCiphers directive is used to specify the list of cipher algorithms that mod_proxy should use when connecting to backend SSH servers. The current list of supported cipher algorithms is, in the default order of preference:

    • aes256-ctr
    • aes192-ctr
    • aes128-ctr
    • aes256-gcm@openssh.com
    • aes128-gcm@openssh.com
    • aes256-cbc
    • aes192-cbc
    • aes128-cbc
    • cast128-cbc
    • 3des-ctr
    • 3des-cbc
    By default, all of the above cipher algorithms are presented to the server, in the above order, during the key exchange.

    The "none" cipher (i.e. no encryption) will not be presented to the server by default; any sites which wish to use "none" will have to explicitly configure it via ProxySFTPCiphers.

    Note that CTR mode ciphers (e.g. the aes128-ctr, aes192-ctr, and aes256-ctr ciphers) are recommended. Any CBC mode cipher allows for the possibility of an attack which causes several bits of plaintext to be leaked; the attack is described here. This attack is on the SSH2 protocol design itself; any SSH2 implementation which conforms to the RFCs will have this weakness.

    In general, there is no need to use this directive unless only one specific cipher must be used.


    ProxySFTPCompression

    Syntax: ProxySFTPCompression on|off|delayed
    Default: None
    Context: server config, <VirtualHost>, <Global>
    Module: mod_proxy
    Compatibility: 1.3.8rc3 and later

    The ProxySFTPCompression directive enables the use of zlib compression of the payload of SSH2 packets. This can make for smaller packets, but require more CPU time to compress/uncompress the data.

    The delayed parameter tells mod_proxy to support a custom extension used by OpenSSH, where compression is not actually enabled until after the client has successfully authenticated.


    ProxySFTPDigests

    Syntax: ProxySFTPDigests algo1 ...
    Default: None
    Context: server config, <VirtualHost>, <Global>
    Module: mod_proxy
    Compatibility: 1.3.8rc3 and later

    The ProxySFTPDigests directive is used to specify the list of MAC digest algorithms that mod_proxy should use when connecting to backend SSH servers. The current list of supported MAC algorithms is:

    • hmac-sha2-256
    • hmac-sha2-256-etm@openssh.com
    • hmac-sha2-512
    • hmac-sha2-512-etm@openssh.com
    • hmac-sha1
    • hmac-sha1-etm@openssh.com
    • hmac-sha1-96
    • hmac-sha1-96-etm@openssh.com
    • umac-64@openssh.com
    • umac-64-etm@openssh.com
    • umac-128@openssh.com
    • umac-128-etm@openssh.com
    By default, all of the above MAC algorithms are presented to the server, in the above order, during the key exchange. Note that some algorithms (e.g. the SHA256 and SHA512 algorithms) may not be supported by the version of OpenSSL used; this will be automatically detected.

    The following list of algorithms are supported, but not presented to servers by default. These algorithms must be explicitly configured via ProxySFTPDigests to be available for use by servers:

    • hmac-md5
    • hmac-md5-etm@openssh.com
    • hmac-md5-96
    • hmac-md5-96-etm@openssh.com
    • hmac-ripemd160

    The "none" MAC (i.e. no MAC) will not be presented to the server by default; any sites which wish to use "none" will have to explicitly configure it via ProxySFTPDigests.

    In general, there is no need to use this directive unless only one specific MAC algorithm must be used.


    ProxySFTPHostKey

    Syntax: ProxySFTPHostKey file|agent:/path
    Default: None
    Context: server config, <VirtualHost>, <Global>
    Module: mod_proxy
    Compatibility: 1.3.8rc3 and later

    The ProxySFTPHostKey directive configures the path to a host key file. The mod_proxy module uses the keys thus configured for "hostbased" user authentication to backend SSH servers.

    You can use either an RSA key, a DSA key, and/or ECDSA keys. These could be the exact same host key files as used by your SSH server, e.g.:

      <IfModule mod_sftp.c>
        ...
        SFTPHostKey /etc/ssh_host_dsa_key
        SFTPHostKey /etc/ssh_host_rsa_key
        SFTPHostKey /etc/ssh_host_ecdsa_key
        SFTPHostKey /etc/ssh_host_ed25519_key
      </IfModule>
    
      <IfModule mod_proxy.c>
        ProxyEngine on
        ..
        ProxySFTPHostKey /etc/ssh_host_dsa_key
        ProxySFTPHostKey /etc/ssh_host_rsa_key
        ProxySFTPHostKey /etc/ssh_host_ecdsa_key
        ProxySFTPHostKey /etc/ssh_host_ed25519_key
      </IfModule>
    

    The ProxySFTPHostKey directive can also be used to load host keys from an SSH agent such as OpenSSH's ssh-agent. For example:

      # Load all of the keys from the ssh-agent configured for proxy use
      ProxySFTPHostKey agent:%{env:SSH_AUTH_SOCK}
    
    Using an SSH agent for storing host keys allows for configurations where the host keys are not stored on files on the server system, e.g. the keys can be loaded into the SSH agent from a PKCS#11 token.


    ProxySFTPKeyExchanges

    Syntax: ProxySFTPKeyExchanges algo1 ...
    Default: None
    Context: server config, <VirtualHost>, <Global>
    Module: mod_proxy
    Compatibility: 1.3.8rc3 and later

    The ProxySFTPKeyExchanges directive is used to specify the list of key exchange algorithms that mod_proxy should use when connecting to backend SSH servers. The current list of supported key exchange algorithms is:

    • curve448-sha512
    • curve25519-sha256
    • ecdh-sha2-nistp521
    • ecdh-sha2-nistp384
    • ecdh-sha2-nistp256
    • diffie-hellman-group18-sha512
    • diffie-hellman-group16-sha512
    • diffie-hellman-group14-sha256
    • diffie-hellman-group-exchange-sha256
    • diffie-hellman-group-exchange-sha1
    • diffie-hellman-group14-sha1
    • diffie-hellman-group1-sha1
    • rsa1024-sha1
    By default, all of the above key exchange algorithms except diffie-hellman-group1-sha1 are presented to the server, in the above order, during the key exchange.

    Note that the diffie-hellman-group1-sha1 key exchange algorithm uses a weak hardcoded Diffie-Hellman group, and thus is not enabled by default. To enable this key exchange algorithm, you must configure the ProxySFTPKeyExchanges list to explicitly include this algorithm, or use the following in your configuration:

      ProxySFTPOptions AllowWeakDH
    

    In general, there is no need to use this directive unless only one specific key exchange algorithm must be used.


    ProxySFTPOptions

    Syntax: ProxySFTPOptions opt1 ...
    Default: None
    Context: server config, <VirtualHost>, <Global>
    Module: mod_proxy
    Compatibility: 1.3.8rc3 and later

    The ProxySFTPOptions directive is used to configure various optional SSH-specific behavior of mod_proxy.

    For example:

      ProxySFTPOptions AllowWeakDH
    

    The currently implemented options are:

    • AllowWeakDH

      The mod_proxy module will not use Diffie-Hellman groups of less than 2048 bits, due to weaknesses that can downgrade the security of an SSH session. If for any reason your SFTP/SCP servers require smaller Diffie-Hellman groups, then use this option.

    • NoExtensionNegotiation

      By default, mod_proxy will offer/support the SSH extension negotiation, defined by RFC 8308. Use this option to disable support for extension negotiations.

    • NoHostkeyRotation

      By default, mod_proxy will offer/support the OpenSSH hostkey rotation extensions, "hostkeys-00@openssh.com" and "hostkeys-prove-00@openssh.com". Use this option to disable support for these custom OpenSSH extensions.

    • NoStrictKex

      By default, mod_proxy will honor/support the OpenSSH "strict KEX" mode extension, "kex-strict-c-v00@openssh.com" and "kex-strict-s-v00@openssh.com". Use this option to disable support for these custom OpenSSH extensions.

    • OldProtocolCompat

      Older servers identity their protocol versions as "1.99", rather than as "2.0". By default, mod_proxy will refuse to handle connections to such servers. To enable compatibility with these servers (which tend to be derived from ssh.com/Tectia code), use this option.

      Note that this option automatically enables the PessimisticKexinit ProxySFTPOption as well.

    • PessimisticKexinit

      As described here, the mod_proxy module tries to reduce the connection latency by optimistically sending the KEXINIT key exchange message. However, some SSH servers cannot handle this behavior. Use this option to disable the optimistic sending of the KEXINIT message.


    ProxySFTPPassPhraseProvider

    Syntax: ProxySFTPPassPhraseProvider path
    Default: None
    Context: server config, <VirtualHost>, <Global>
    Module: mod_proxy
    Compatibility: 1.3.8rc3 and later

    The ProxySFTPPassPhraseProvider directive is used to specify an external program which will be called, when mod_proxy starts up, for each encrypted ProxySFTPHostKey file. The program will be invoked with two command-line arguments, passed on stdin to the program:

      servername:portnumber file
    
    where servername:portnumber indicates the server using that encrypted certificate key, and file indicates the host key file in question. The program then must print the corresponding passphrase for the key to stdout.

    The intent is that this external program can perform any security checks necessary, to make sure that the system is not compromised by an attacker, and only when these checks pass successfully will the passphrase be provided. These security checks, and the way the passphrase is determined, can be as complex as you like.

    Example:

      ProxySFTPPassPhraseProvider /etc/ftpd/proxy/get-ssh-passphrase
    


    ProxySFTPServerAlive

    Syntax: ProxySFTPServerAlive count interval
    Default: None
    Context: server config, <VirtualHost>, <Global>
    Module: mod_proxy
    Compatibility: 1.3.8rc3 and later

    The ProxySFTPServerAlive directive configures mod_proxy to send messages to a server, through the encrypted channel, to request a response from the server. If count server alive messages are sent without receiving any response messages from the server, the client will disconnect. The interval indicates how much time, in seconds, that mod_proxy waits for data from the server before sending a server alive message.

    For example, using:

      ProxySFTPServerAlive 3 15
    
    will cause an unresponsive server to be disconnected after approximately 45 seconds.


    ProxySFTPServerMatch

    Syntax: ProxySFTPServerMatch pattern key1 val1 ...
    Default: None
    Context: server config, <VirtualHost>, <Global>
    Module: mod_proxy
    Compatibility: 1.3.8rc3 and later

    The ProxySFTPServerMatch directive is used to tune some of the SSH2/SFTP values based on the connected server, for better interoperability with those servers. The pattern parameter specifies a POSIX regular expression which will be matched against the connected server's SSH version banner. If the server's banner version matches pattern, then the configured keys/values are used for that session.

    The currently supported SSH2/SFTP keys which can be tuned via ProxySFTPServerMatch are:

    • pessimisticNewkeys


    ProxySFTPVerifyServer

    Syntax: ProxySFTPVerifyServer on|off
    Default: ProxySFTPVerifyServer off
    Context: server config, <VirtualHost>, <Global>
    Module: mod_proxy
    Compatibility: 1.3.8rc3 and later

    The ProxySFTPVerifyServer directive tells mod_proxy whether to verify whether the host keys presented by backend SSH servers have changed.

    When mod_proxy connects to a backend SSH server for the very first time, its hostkey will be recorded. The next time mod_proxy connects, the hostkey presented will be compared to the previously recorded hostkey. When ProxySFTPVerifyServer is on, any hostkey mismatches will be logged.


    ProxySourceAddress

    Syntax: ProxySourceAddress address
    Default: None
    Context: server config, <VirtualHost>, <Global>
    Module: mod_proxy
    Compatibility: 1.3.6rc1 and later

    The ProxySourceAddress directive configures the address (or device name) of a network interface on the host machine, to be used when connecting to the backend/destination server. This directive is most useful on a multi-homed (or DMZ) host; frontend connections can be received on one network interface, and the backend connections can use a different network interface. Imagine e.g. separate WAN/LAN interfaces on a proxying host.


    ProxyTables

    Syntax: ProxyTables table-info
    Default: None
    Context: server config
    Module: mod_proxy
    Compatibility: 1.3.6rc1 and later

    The ProxyTables directive is used to specify a directory that mod_proxy will use for storing its database files; these files are used for tracking the various load balancing/healthcheck statistics used for proxying.

    Note that the ProxyTables directive is required for mod_proxy to function. If this directive is not configured, connections to mod_proxy will fail.


    ProxyTimeoutConnect

    Syntax: ProxyTimeoutConnect timeout
    Default: ProxyTimeoutConnect 5sec
    Context: server config, <VirtualHost>, <Global>
    Module: mod_proxy
    Compatibility: 1.3.6rc1 and later

    The ProxyTimeoutConnect directive configures the amount of time that mod_proxy will wait for the backend/destination server to accept a TCP connection, before giving up.

    Note that if there are many different backend/destination servers to try (due to e.g. ProxyRetryCount), and if those backend servers are slow, the connecting client might itself see connection timeouts to mod_proxy. To guard against such slow backend servers, a more aggressively short timeout can be used:

      ProxyTimeoutConnect 1sec
    


    ProxyTimeoutLinger

    Syntax: ProxyTimeoutLinger timeout
    Default: ProxyTimeoutLinger 3sec
    Context: server config, <VirtualHost>, <Global>
    Module: mod_proxy
    Compatibility: 1.3.6rc1 and later

    The ProxyTimeoutLinger directive configures the amount of time that mod_proxy will wait (lingering), after receiving all of the data on the data transfer connection to the backend server, for the explicit "end of transfer" response from the backend server, before giving up.


    ProxyTLSCACertificateFile

    Syntax: ProxyTLSCACertificateFile path
    Default: None
    Context: server config, <VirtualHost>, <Global>
    Module: mod_proxy
    Compatibility: 1.3.6rc2 and later

    The ProxyTLSCACertificateFile directive configures one file where you can assemble the certificates of Certification Authorities (CA) which will be used to verify the servers' certificates. Such a file is merely the concatenation of the various PEM-encoded CA certificates. This directive can be used in addition to, or as an alternative for, ProxyTLSCACertificatePath.

    Example:

      ProxyTLSCACertificateFile /etc/ftpd/cacerts.pem
    

    Note that the location of CA certificates is required for mod_proxy's TLS support; verification of server certificates is required for secure connections to backend/destination servers. For this reason, mod_proxy ships with its own default ProxyTLSCACertificateFile, which is generated using libcurl's mk-ca-bundle.pl script:

      $ lib/mk-ca-bundle.pl -u cacerts.pem
    


    ProxyTLSCACertificatePath

    Syntax: ProxyTLSCACertificatePath directory
    Default: None
    Context: server config, <VirtualHost>, <Global>
    Module: mod_proxy
    Compatibility: 1.3.6rc2 and later

    The ProxyTLSCACertificatePath directive sets the directory for the certificates of Certification Authorities (CAs); these are used to verify the server certificates presented. This directive may be used in addition to, or as alternative for, ProxyTLSCACertificateFile.

    The files in the configured directory have to be PEM-encoded, and are accessed through hash filenames. This means one cannot simply place the CA certificates there: one also has to create symbolic links named hash-value.N. The c_rehash utility that comes with OpenSSL can be used to create the necessary symlinks.

    Example:

      ProxyTLSCACertificatePath /etc/ftpd/cacerts/
    


    ProxyTLSCARevocationFile

    Syntax: ProxyTLSCACertificateFile path
    Default: None
    Context: server config, <VirtualHost>, <Global>
    Module: mod_proxy
    Compatibility: 1.3.6rc2 and later

    The ProxyTLSCARevocationFile directive configures one file that can contain the Certificate Revocation Lists (CRL) of Certification Authorities (CA); these CRLs are used during the verification of server certificates. Such a file is merely the concatenation of the various PEM-encoded CRL files. This directive can be used in addition to, or as an alternative for, ProxyTLSCARevocationPath.

    Example:

      ProxyTLSCARevocationFile /etc/ftpd/cacrls.pem
    


    ProxyTLSCARevocationPath

    Syntax: ProxyTLSCARevocationPath directory
    Default: None
    Context: server config, <VirtualHost>, <Global>
    Module: mod_proxy
    Compatibility: 1.3.6rc2 and later

    The ProxyTLSCARevocationPath directive sets the directory for the Certificate Revocation Lists (CRL) of Certification Authorities (CAs); these are used during the verification of server certificates. This directive may be used in addition to, or as alternative for, ProxyTLSCARevocationFile.

    The files in the configured directory have to be PEM-encoded, and are accessed through hash filenames. This means one cannot simply place the CRLs there: one also has to create symbolic links named hash-value.N. The c_rehash utility that comes with OpenSSL can be used to create the necessary symlinks.

    Example:

      ProxyTLSCARevocationPath /etc/ftpd/cacrls/
    


    ProxyTLSCertificateFile

    Syntax: ProxyTLSCertificateFile path
    Default: None
    Context: server config, <VirtualHost>, <Global>
    Module: mod_proxy
    Compatibility: 1.3.6rc2 and later

    The ProxyTLSCertificateFile directive points to the PEM-encoded file containing the client certificate file, and optionally also the corresponding private key. Note that this directive is only needed for backend/target FTPS servers which require client authentication.

    Example:

      ProxyTLSCertificateFile /etc/ftpd/client-cert.pem
    


    ProxyTLSCertificateKeyFile

    Syntax: ProxyTLSCertificateKeyFile path
    Default: None
    Context: server config, <VirtualHost>, <Global>
    Module: mod_proxy
    Compatibility: 1.3.6rc2 and later

    The ProxyTLSCertificateKeyFile directive points to the PEM-encoded file containing the client certificate file, and optionally also

    The ProxyTLSCertificateKeyFile directive points to the PEM-encoded private key file for the client certificate indicated by ProxyTLSCertificateFile. If the private key is not combined with the certificate in the ProxyTLSCertificateFile, use this additional directive to point to the file with the standalone private key. When ProxyTLSCertificateFile is used and the file contains both the certificate and the private key, this directive need not be used. However, this practice is strongly discouraged. Instead we recommend you to separate the certificate and the private key.


    ProxyTLSCipherSuite

    Syntax: ProxyTLSCipherSuite [protocol] cipher-list
    Default: ProxyTLSCipherSuite DEFAULT:!ADH:!EXPORT:!DES
    Context: server config, <VirtualHost>, <Global>
    Module: mod_proxy
    Compatibility: 1.3.6rc2 and later

    The ProxyTLSCipherSuite directive configures the list of acceptable SSL/TLS ciphersuites to use for backend SSL/TLS connections. The syntax is that of the OpenSSL ciphers command, e.g.:

      $ openssl ciphers -v <cipher-list>
    
    may be used to list all of the ciphers and the order described by a specific <cipher-list>.

    If the SSL library supports TLSv1.3 (e.g. OpenSSL-1.1.1 and later), the protocol specifier "TLSv1.3" can be used to configure the cipher suites for that protocol:

      # Configure TLSv1.3 ciphersuites
      ProxyTLSCipherSuite TLSv1.3 TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256
    


    ProxyTLSEngine

    Syntax: ProxyTLSEngine on|off|auto|MatchClient
    Default: ProxyTLSEngine auto
    Context: server config, <VirtualHost>, <Global>
    Module: mod_proxy
    Compatibility: 1.3.6rc2 and later

    The ProxyTLSEngine directive configures the use of SSL/TLS for backend FTP connections. Note that SSL/TLS support requires the presence/use of the mod_tls ProFTPD module.

    The supported values are:

    • on

      Use of SSL/TLS is required; backend servers which do not support SSL/TLS or which fail the SSL/TLS handshake will cause the proxied session to be closed.

    • off

      Use of SSL/TLS is disabled; backend servers which do support SSL/TLS will be ignored, and only FTP will be used.

    • auto

      Use of SSL/TLS will be automatically used if the backend server supports SSL/TLS (via AUTH TLS in its FEAT response). If the SSL/TLS handshake fails, the backend connection will proceed using plain FTP.

      Note: this is the default behavior in mod_proxy.

    • Use of SSL/TLS to the backend server will attempt to match the SSL/TLS usage of the frontend client. Thus if the frontend client uses explicit FTPS, then explicit FTPS will be attempted with the backend; similarly for implicit FTPS. And if the frontent client does not use FTPS, then SSL/TLS will not be used for the backend connection.


    ProxyTLSOptions

    Syntax: ProxyTLSOptions options
    Default: None
    Context: server config, <VirtualHost>, <Global>
    Module: mod_proxy
    Compatibility: 1.3.6rc2 and later

    The ProxyTLSOptions directive is used to configure various optional SSL/TLS behavior of mod_proxy.

    Example:

      ProxyTLSOptions EnableDiags
    

    The currently implemented options are:

    • AllowWeakSecurity

      Sets the cryptographic security level to "zero", meaning that any/all ciphers and key sizes are permitted. This option allows for the best interoperability with any FTPS server, but is not recommended. Use only when necessary.

    • EnableDiags

      Sets callbacks in the OpenSSL library such that a lot of SSL/TLS protcol information is logged to the ProxyLog file. This option is very useful when debugging strange interactions with FTPS servers.

    • NoSessionCache

      By default, when using SSL/TLS, mod_proxy will cache the negotiated SSL sessions in its local database, for reuse in enabling SSL session resumption in future connections to those hosts. Use this option to disable use of session caching if/when needed.

    • NoSessionTickets

      By default, when using SSL/TLS, mod_proxy will cache any session tickets offered by the server in its local database, for reuse in enabling SSL session resumption in future connections to those hosts. Use this option to disable use of session tickets if/when needed.


    ProxyTLSPreSharedKey

    Syntax: ProxyTLSPreSharedKey identity key-info
    Default: None
    Context: server config, <VirtualHost>, <Global>
    Module: mod_proxy
    Compatibility: 1.3.6rc2 and later

    The ProxyTLSPreSharedKey directive is used to configure a pre-shared key (PSK), for use in TLS-PSK ciphersuites. Each PSK has an identity (a string/name used by clients to request the use of that PSK), and the actual key data. The key data may be encoded in different ways; the ProxyTLSPreSharedKey directive requires that the data be hex-encoded, as indicated in the key-info parameter.

    The key-info parameter is comprised of the type of encoding used for the key data, and the full path to the key file. Only "hex" encoding is supported right now. Thus an example ProxyTLSPreSharedKey directive would be:

      ProxyTLSPreSharedKey MyPSK hex:/path/to/psk.key
    
    The configured file cannot be world-readable or world-writable; the mod_proxy module will skip/ignore such insecure permissions.

    To generate this shared key (which is just a randomly generated bit of data), you can use:

      $ openssl rand 160 -out /path/to/identity.key -hex
    
    Note that ProxyTLSPreSharedKey requires at least 20 bytes of key data. Having generated the random key data, tell mod_proxy to use it via:
      ProxyTLSPreSharedKey identity hex:/path/to/identity.key
    


    ProxyTLSProtocol

    Syntax: ProxyTLSProtocol protocols
    Default: ProxyTLSProtocol TLSv1 TLSv1.1 TLSv1.2
    Context: server config, <VirtualHost>, <Global>
    Module: mod_proxy
    Compatibility: 1.3.6rc2 and later

    The ProxyTLSProtocol directive is used to configure the SSL/TLS protocol versions that mod_proxy should use when establishing SSL/TLS sessions to backend servers.

    The allowed protocols are:

    SSLv3 Use only SSLv3
    TLSv1 Use only TLSv1
    TLSv1.1 Use only TLSv1.1
    TLSv1.2 Use only TLSv1.2

    To support both SSLv3 and TLSv1, simply list both parameters for the ProxyTLSProtocol directive, e.g.:

      ProxyTLSProtocol SSLv3 TLSv1
    

    The ProxyTLSProtocol directive can also be used in a different manner, to add or subtract protocol support. For example, to enable all protocols except SSLv3, you can use:

      ProxyTLSProtocol ALL -SSLv3
    
    Using the directive in this manner requires that "ALL" be the first parameter, and that all protocols have either a + (add) or - (subtract) prefix. "ALL" will always be expanded to all of the supported SSL/TLS protocols known by mod_proxy and supported by OpenSSL.


    ProxyTLSTimeoutHandshake

    Syntax: ProxyTLSTimeoutHandshake timeout
    Default: ProxyTLSTimeoutHandshake 30sec
    Context: server config, <VirtualHost>, <Global>
    Module: mod_proxy
    Compatibility: 1.3.6rc2 and later

    The ProxyTLSTimeoutHandshake directive configures the maximum number of seconds for mod_proxy to complete an SSL/TLS handshake. If set to zero, mod_proxy will wait forever for a handshake to complete. The default is 30 seconds.


    ProxyTLSTransferProtectionPolicy

    Syntax: ProxyTLSTransferProtectionPolicy client|required|clear
    Default: ProxyTLSTransferProtectionPolicy required
    Context: server config, <VirtualHost>, <Global>
    Module: mod_proxy
    Compatibility: 1.3.6rc1 and later

    The ProxyTLSTransferProtectionPolicy directive configures the data transfer protection policy, when using SSL/TLS, that mod_proxy uses when performing data transfers (e.g. file uploads/downloads, directory listings) with the backend/destination server.

    The currently supported policies are:

    • client

      This policy indicates that mod_proxy will use whatever the connected client uses. Thus if the client sends PROT C, mod_proxy will send PROT C to the backend/destination server.

    • required

      Regardless of the commands sent by the client, mod_proxy will use only protected TLS data transfers (i.e. using PROT P commands) with the backend/destination server.

      This is the recommended policy in most cases.

    • clear

      Regardless of the commands sent by the client, mod_proxy will use only clear (i.e. unprotected) data transfers (i.e. using PROT C commands) with the backend/destination server.


    ProxyTLSVerifyServer

    Syntax: ProxyTLSVerifyServer on|off
    Default: ProxyTLSVerifyServer on
    Context: server config, <VirtualHost>, <Global>
    Module: mod_proxy
    Compatibility: 1.3.6rc2 and later

    The ProxyTLSVerifyServer directive configures how mod_proxy handles certificates presented by servers. If off, the module will accept any server certificate and establish an SSL/TLS session, but will not verify the certificate. If on, the module will verify a server's certificate and, furthermore, will fail all SSL handshake attempts unless the server presents a valid certificate.


    Usage

    Benefits of Proxying
    The benefits of using a module like mod_proxy depend mostly on the type of proxying, forward or reverse, that mod_proxy is configured to perform. There are some benefit, however, that the module can bring, regardless of the type of proxying:

    • Add ProFTPD's FTPS support (via mod_tls) for FTP servers which do not support FTPS
    • Add ProFTPD's FTPS support for FTP clients which do not support FTPS
    • Add ProFTPD's IPv6 support for FTP clients/servers which do not support it
    • Add ProFTPD's logging power (TransferLog, ExtendedLog, etc) for FTP servers which do not provide such versatile logging
    • Use ProFTPD's monitoring capabilities (e.g. mod_snmp) for FTP servers which do not have such features

    When using mod_proxy for proxying, all data transfers (e.g. file uploads/downloads, directory listings, etc) pass through mod_proxy; data transfers do not occur directly between the "frontend" clients and the "backend" servers. This happens so that clients are completely unaware of the network structure for the backend servers; this is especially important when reverse proxying, where it means that the client sees mod_proxy as the real FTP server.

    Forward Proxying
    One of the most common benefits of a forward proxy is having controlled access, by clients within an internal LAN, to outside servers. FTP makes this sort of thing notoriously difficult for firewalls/routers due to its multi-TCP connection nature; this, in turn, makes proxying of FTP more difficult. But mod_proxy makes this possible; it understands FTP, and thus provides the access control needed for such use cases.

    When using mod_proxy as a forward proxy, FTP clients which can only perform active data transfers can use mod_proxy as a way to use passive data transfers with the destination FTP server.

    Similarly, FTP clients which do not support IPv6 can proxy through mod_proxy to reach a destination FTP server with an IPv6 address; mod_proxy handles IPv4 and IPv6 addresses transparently.

    Reverse Proxying
    Just as mod_proxy can aid naive/legacy FTP clients via forward proxying, mod_proxy can similarly front legacy FTP servers. For example, mod_proxy can sit in front of FTP servers which do not handle IPv6 addresses, and provide this functionality transparently to IPv6-capable FTP clients.

    When performing reverse proxying, mod_proxy can also perform load balancing in various ways. The common methods of "round robin" and "least connections" are implemented; mod_proxy also provides "sticky session" load balancing of clients as well.

    Handling of Proxied Sessions
    Once a proxied session has authenticated with the backend/destination server, the mod_proxy module automatically chroots itself to a subdirectory of the ProxyTables directory, after which all root privileges are permanently dropped.

    Forward Proxy Configuration
    Before discussing example forward proxy configurations for mod_proxy, it is very important to understand the consequences of providing forward proxy capabilities.

    Important Security Considerations
    A forward proxy can be used by any client to have the proxy connect to any arbitrary host, while hiding the client's true identity. Malicious behavior, hacking or denial-of-service attempts, etc will appear to be coming from your proxy; this is dangerous for your network, and for the Internet at large. Think of the damage that has been done, and continues to happen, due to open/unrestricted proxies/relays such as open DNS or SMTP/email proxies.

    This is the reason that mod_proxy does not allow just any client to use its forward proxy capabilities by default; instead, only clients connecting from the LAN are allowed by default. Allowing trusted outside clients is done using the ProxyForwardEnabled directive. Even allowing internal clients to use your forward proxy can be troublesome, depending on the destination hosts selected by the clients. To ensure that your clients are using the forward proxy to connect only to the hosts allowed, you can use the ProxyForwardTo directive to configure a regular expression-based whitelist of allowed destination/target hosts; this is strongly recommended.

    With all that said, here's an example mod_proxy configuration for supporting forward proxying:

      <IfModule mod_proxy.c>
        ProxyEngine on
        ProxyLog /path/to/proxy.log
        ProxyTables /var/ftp/proxy
    
        ProxyRole forward
        ProxyForwardMethod user@host
        ProxyForwardTo ^ftp\.example\.com\:21$ [NC]
      </IfModule>
    
    If the configured ProxyForwardTo pattern is not met, the following will be logged in the ProxyLog:
    mod_proxy/0.7[16151]: host/port 'server.example.org:2121' did not match ProxyForwardTo ^ftp\.example\.com\:21$, rejecting
    

    Reverse Proxy Configuration
    Reverse proxies (also known as "gateways") are often used to provide access to FTP resources, located with internal networks, to the outside world. The reverse proxy can perform load balancing, provide functionality that the internal servers may not be able to do, and even perform things like caching.

    Access control for reverse proxies is less critical than for forward proxies because clients can only reach, via the reverse proxy, the backend servers that the reverse proxy has been configured to use; the clients do not get to choose arbitrary hosts for the reverse proxy to use.

    Here's an example mod_proxy configuration for supporting reverse proxying:

      <IfModule mod_proxy.c>
        ProxyEngine on
        ProxyLog /path/to/proxy.log
        ProxyTables /var/ftp/proxy
    
        ProxyRole reverse
        ProxyReverseConnectPolicy RoundRobin
        ProxyReverseServers ftp://ftp-backend1.example.com:2121 ftp://ftp-backend2.example.com:2121 ...
      </IfModule>
    

    Here's an example mod_proxy configuration for reverse proxying, with lookup of per-user backend servers:

      <IfModule mod_proxy.c>
        ProxyEngine on
        ProxyLog /path/to/proxy.log
        ProxyTables /var/ftp/proxy
    
        ProxyRole reverse
        ProxyReverseConnectPolicy PerUser
    
        # We need to provide a pool of backend servers as a fallback
        ProxyReverseServers ftp://ftp-backend1.example.com:2121 ftp://ftp-backend2.example.com:2121 ...
    
        # Look up per-user backend servers from user-specific JSON files
        ProxyReverseServers file:/var/ftp/proxy/backends/%U.json
      </IfModule>
    
    Similarly, you can use a per-group lookup for the backend servers when reverse proxying:
      <IfModule mod_proxy.c>
        ProxyEngine on
        ProxyLog /path/to/proxy.log
        ProxyTables /var/ftp/proxy
    
        ProxyRole reverse
        ProxyReverseConnectPolicy PerGroup
        ProxyOptions UseReverseProxyAuth
    
        # We need to provide a pool of backend servers as a fallback
        ProxyReverseServers ftp://ftp-backend1.example.com:2121 ftp://ftp-backend2.example.com:2121 ...
    
        # Look up per-group backend servers from group-specific JSON files
        ProxyReverseServers file:/var/ftp/proxy/backends/%g.json
      </IfModule>
    

    In order to support FTP over SSL/TLS (FTPS) connections from clients when reverse proxying, simply include your normal mod_tls configuration with the same <VirtualHost> configuration with your mod_proxy configuration.

    For FTPS support to the backend servers, your reverse proxy configuration would look something like this:

      <IfModule mod_proxy.c>
        ProxyEngine on
        ProxyLog /path/to/proxy.log
        ProxyTables /var/ftp/proxy
    
        ProxyRole reverse
        ProxyReverseConnectPolicy RoundRobin
        ProxyReverseServers ftp://ftp-backend1.example.com:2121 ftp://ftp-backend2.example.com:2121 ...
    
        # Use FTPS when supported/available by the backend server
        ProxyTLSEngine auto
    
        # List of trusted root CAs
        ProxyTLSCACertificateFile /etc/ftpd/cacerts.pem
      </IfModule>
    
    In fact, mod_proxy comes with a default ProxyTLSCACertificateFile (comprised of the root CAs that most browsers use/trust), and the default ProxyTLSEngine value is auto. This means that, by default, mod_proxy will try to use FTPS for backend connections automatically (assuming that ProFTPD is built with OpenSSL support using the --enable-openssl configure option).

    Load Balancing versus Session Stickiness
    For reverse proxy configurations, there is a choice between load balancing and sticky session ProxyReverseConnectPolicy parameters; these parameters determine the selection of the backend server that will handle the incoming connection.

    Which should you use, and why?

    All of the balancing policies are able to select the backend server when the FTP client connects to the proxy, before sending any commands. Most of the "sticky" policies, on the other hand, require more knowledge about the user (e.g. USER name, HOST name, SSL session ID) before the backend server can be determined, thus backend server selection is delayed until that information is obtained.

    Balancing is best when all of your backend severs are identical with regard to the content they have, and when it does not matter which server handles a particular client. Maybe all of your backend servers use a shared filesystem via NFS or similar, thus directory listings will be the same for a user no matter which backend server is used, and uploading files to one server means that those files can be downloaded/seen by the other servers. Balancing policies are also best when all of your backend servers have similar processing power (memory, CPU, network, disk), so that all backend servers are equally capable of providing the same service to the connecting client.

    The balancing policies are:

    • LeastConns
    • Random
    • RoundRobin
    • Shuffle

    Stickiness is best when your backend servers are not identical, and some users/clients should only ever go to the same set of backend servers. Thus the user/client needs to be "sticky" to a given backend server.

    The sticky policies are:

    • PerGroup
    • PerHost
    • PerUser

    Implicit FTPS Support
    The mod_proxy module includes support for using implicit FTPS with backend servers, both when forward and reverse proxying. Note that implicit FTPS support requires the presence/use of the mod_tls ProFTPD module.

    In order to use implicit FTPS for a reverse proxy server, the URI syntax must be used in a ProxyReverseServers directive; the scheme must be "ftps" and the port must be explicitly specified as 990, thus: ProxyReverseServers ftps://ftp.example.com:990

    When forward proxying, the client must request the destination server and specify a port of 990, e.g.: USER user@ftp.example.com:990

    Note that there is an additional wrinkle/caveat for implicit FTPS support when your mod_proxy installation uses shared/DSO modules. You must ensure, in a list of LoadModule directives, that mod_proxy appears before mod_tls:

      # Make sure that mod_tls appears after mod_proxy in the list of loaded
      # modules
      LoadModule mod_proxy.c
      LoadModule mod_tls.c
    
    Why does this matter?

    A lot of things happen when a client connects, via TCP, to the server (in this case, to a server configured to be a proxy). ProFTPD publishes this "on connect" event to all of the modules, in "module load order". The last module loaded will be the first module to receive this event; this means "module loader order" is the reverse order of your LoadModule directives.

    What we want is for the mod_tls module to handle the "on connect" event first, so that it handles the implicit TLS handshake. In doing so, the mod_tls module will record some internal session state showing that a TLS handshake occurred. The mod_proxy module looks for the internal session state that mod_tls sets, and will Do The Right Thing if it sees that a TLS session is in effect.

    On the other hand, if mod_proxy appears after mod_tls in the LoadModule list, then it is the mod_proxy module which handles the "on connect" event before mod_tls does. mod_proxy looks for the session state to see if a TLS session is in effect, does not find the session state, and then attempts to proxy things like the backend banner to the client. The frontend client is expecting TLS handshake bytes, not plaintext text bytes from the banner, and thus the frontend client will see a TLS error.

    SFTP/SCP Support
    The mod_proxy module now supports reverse proxying (but not forward proxying) of SFTP/SCP sessions.

    To support a reverse proxy configuration for SFTP/SCP sessions, specify the sftp scheme in your ProxyReverseServers URIs, like so:

      <IfModule mod_proxy.c>
        ProxyEngine on
        ProxyLog /path/to/proxy.log
        ProxyTables /var/ftp/proxy
    
        ProxyRole reverse
        ProxyReverseConnectPolicy RoundRobin
        ProxyReverseServers sftp://ssh-backend1.example.com:2222 sftp://ssh-backend2.example.com ...
      </IfModule>
    

    When proxying SSH connections like this, there will be two separate SSH sessions:

      frontend client --- (SSH session #1) --> proxy --- (SSH session #2) --> backend server
    
    Each SSH session creates a unique session ID, as a hash of client- and server-provided values known as H. This is important for some types of authentication, as we see later.

    Proxying of SSH connections is more complex than proxying of FTP/FTPS connections, especially when it comes to authentication. FTP supports simple password-based authentication, whereas SSH supports multiple different authentication mechanisms. And two authentication mechanisms, in particular, require a slightly more sophisticated mod_proxy configuration: "hostbased" and "publickey".

    When the frontend SSH client requests the "keyboard-interactive" or "password" authentication mechanisms, then mod_proxy can handle the client using the above configuration as-is. For the "hostbased" and "publickey" authentication mechanisms, mod_proxy needs to do a little more work -- and it requires support on the backend servers for "hostbased" authentication.

    Let's consider the common "publickey" use case (the frontend "hostbased" case is quite similar). The frontend client has a public/private key pair; mod_proxy has no knowledge or access to that frontend private key (by design). The frontend client performs the "publickey" authentication by hashing several types of data, then encrypts that hash with its private key; the server (mod_proxy in this case) then hashes the same data, and decrypts the client-sent value with the client's public key. The complication is that one of the types of data included in these hashes is the unique session ID known as H mentioned above. The H values for the SSH connections (from the frontend client to the proxy, and separately from the proxy to the backend server) will be different. And since mod_proxy has no access to the frontend private key, it cannot forge or change H (and this is a good thing). But it does mean that mod_proxy cannot simply proxy the "publickey" authentication request as is, from frontend client to backend server.

    Instead, for these authentication mechanisms, mod_proxy will use a different authentication mechanism, specifically "hostbased" authentication, to the backend server. This makes sense, right? The backend server trusts that the proxy is doing proper handling of all frontend clients, thus backend server can trust the entire proxy host, not just individual clients proxied by that host. In order support these backend "hostbased" authentication requests, mod_proxy will need to know its private key to use for such things, via ProxySFTPHostKey; this can actually the same private key used by the SFTPHostKey directive (or be a different private key, depending on your needs). It also requires that the backend servers be configured to trust the corresponding public key; for mod_sftp, this would be done using its SFTPAuthorizedHostKeys directive.

    The SFTP/SCP support in mod_proxy also properly supports the PerUser, PerGroup ProxyReverseConnectPolicy values, subject the same caveats. This means that mod_proxy can proxy different frontend SSH users to different backend servers (and even rewrite/change the user name via the mod_rewrite module); just like for FTP/FTPS sessions, you can configure the per-user lookup of backend servers using SQL queries, JSON files, etc.

    Logging
    The mod_proxy module supports different forms of logging. The main module logging is done via the ProxyLog directive. For debugging purposes, the module also uses trace logging, via the module-specific channels:

    • proxy
    • proxy.conn
    • proxy.db
    • proxy.dns
    • proxy.forward
    • proxy.ftp.conn
    • proxy.ftp.ctrl
    • proxy.ftp.data
    • proxy.ftp.dirlist
    • proxy.ftp.facts
    • proxy.ftp.msg
    • proxy.ftp.sess
    • proxy.ftp.xfer
    • proxy.inet
    • proxy.netio
    • proxy.random
    • proxy.reverse
    • proxy.reverse.db
    • proxy.reverse.redis
    • proxy.session
    • proxy.ssh.agent
    • proxy.ssh.auth
    • proxy.ssh.bcrypt
    • proxy.ssh.cipher
    • proxy.ssh.compress
    • proxy.ssh.crypto
    • proxy.ssh.db
    • proxy.ssh.disconnect
    • proxy.ssh.interop
    • proxy.ssh.kex
    • proxy.ssh.keys
    • proxy.ssh.msg
    • proxy.ssh.packet
    • proxy.ssh.service
    • proxy.ssh.utf8
    • proxy.tls
    • proxy.tls.db
    • proxy.tls.redis
    • proxy.uri

    Thus for trace logging, to aid in debugging, you would use the following in your proftpd.conf:

      TraceLog /path/to/proxy-trace.log
      Trace proxy:20
    
    This trace logging can generate large files; it is intended for debugging use only, and should be removed from any production configuration.

    Logging Notes
    The following is a list of notes, logging variables that can be used in custom LogFormats and/or custom SQL statements:

    • %{note:mod_proxy.backend-ip}: IP address of the backend/proxied server
    • %{note:mod_proxy.backend-port}: Port of the backend/proxied server
    • %{note:mod_proxy.backend-url}: URL to the backend/proxied server

    SELinux
    If using the mod_proxy module on an SELinux-enabled system, you may need the following to allow for proper operations of proxying. For example, using the following mod_proxy configuration:

      ProxyTables /var/ftp/proxy
    
    may require that you run:
      $ semanage fcontext --add --type public_content_rw_t '/var/ftp/proxy(/.*)?'
      $ setsebool -P ftpd_anon_write=1
      $ setsebool -P nis_enabled=1
    

    Suggested Future Features
    The following lists the features I hope to add to mod_proxy, according to need, demand, inclination, and time:

    • MODE Z support

    See the GitHub issues page for current bugs and feature requests, and to report issues.

    Frequently Asked Questions

    Question: I have heard a lot about both "round robin" and "least conns" for load balancing. Which is better for FTP connections?
    Answer: There is not an easy answer to this, because it really comes down to the type of traffic that your FTP servers will see.

    If your FTP sessions tend to be long-lived (e.g. on the order of minutes to hours), then using ProxyReverseConnectPolicy LeastConns will tend to provide the best distribution of those sessions across your pool of backend servers. The assumption here is that new connections arrive infrequently relative to the number of existing connections.

    On the other hand, if your FTP sessions tend to be shorter (e.g. minutes at most), then using ProxyReverseConnectPolicy RoundRobin might provide a more even distribution of connections across your pool of backend servers.

    Question: I am using:

      ProxyReverseConnectPolicy PerHost
    
    and would like to configure different pools of backend servers for different incoming clients. How do I do this?
    Answer: The best way to achieve this would be to use classes and mod_ifsession's <IfClass> sections. For example:
      <Class proxied-clients>
      </Class>
    
      ProxyRole reverse
      ProxyReverseConnectPolicy PerHost
    
      <IfClass proxied-clients>
        ProxyReverseServers ftp://ftp-special1.example.com:2121 ftp://ftp-special2.example.com:2121 ...
      </IfClass>
    
      # Don't forget to configure the backend server pool for clients coming
      # from other networks!
      <IfClass !proxied-clients>
        ProxyReverseServers ftp://ftp-backend1.example.com:2121 ftp://ftp-backend2.example.com:2121 ...
      </IfClass>
    

    Question: Does mod_proxy support SSL/TLS connections, i.e. FTPS?
    Answer: Short answer: yes.

    The long answer is the mod_proxy supports FTPS connections both on the frontend, from connecting clients, and on the backend, to backend servers. This means that all of the following flows are supported:

      client --- FTPS ---> proxy --- FTP ---> server
      client --- FTP ---> proxy --- FTPS ---> server
      client --- FTPS ---> proxy --- FTPS ---> server
    

    Thus mod_proxy's FTPS support is suited for reverse proxy configurations, where mod_proxy can be used to provide SSL/TLS capabilities to old/legacy FTP servers which do not implement it. The mod_proxy module is also suited for forward proxy configurations, where the FTPS support can be used to provide SSL/TLS capabilities to old/legacy FTP clients which do not implement it.

    Question: I want to use mod_proxy for reverse proxying. I want to centralize all of my user authentication in mod_proxy, and I want to use different user credentials when logging in to the backend servers. Can mod_proxy do all of this?
    Answer: Yes.

    There are a couple of key parts of your mod_proxy configuration to pay attention to, for achieving the above.

      <IfModule mod_proxy.c>
        ProxyEngine on
        ProxyLog /path/to/proxy.log
        ProxyTables /var/ftp/proxy
    
        ProxyRole reverse
    
        # Make sure to authenticate in the proxy itself
        ProxyOptions UseReverseProxyAuth
    
        ProxyReverseConnectPolicy ...
    
        # Include the username/passwords to use for the backend servers
        # in the URLs.
        ProxyReverseServers ftp://user1:password1@ftp-backend1.example.com:2121 ftp://user2:password2@ftp-backend2.example.com:2121 ...
      </IfModule>
    
    When a URL uses the "username:password" syntax for including the credentials to use for that connection, mod_proxy will use those URI credentials when logging in to that backend server.

    Question: I have configured mod_proxy to block the LIST command for one group of users, like so:

      <Limit LIST>
        DenyGroup somegroup
      </Limit>
    
    But this <Limit> is not working. Is this a bug?
    Answer: No, it is not a bug. It is, unfortunately, expected behavior.

    The <Limit> mechanism works on the authenticated user/group names. And in many cases, it is not mod_proxy which authenticates the user, it is the backend server. Thus it is the backend server which knows the groups to which the authenticated user belongs; mod_proxy only knows that the USER name was successfully authenticated by the backend user. Thus group-based limits cannot be honored.

    However, if proxy auth is enabled, then group-based limits will work. This means using either the following when forward proxying:

      ProxyRole forward
    
      # Either of these two methods result in proxy auth
      ProxyForwardMethod proxyuser,user@host
      ProxyForwardMethod proxyuser@host,user
    
    or this, when reverse proxying:
      ProxyRole reverse
      ProxyOptions UseReverseProxyAuth
    

    Question: Can mod_proxy be configured as a reverse proxy, and to select the backend server based on the client certificate used by the frontend FTPS client?
    Answer: Yes, but it requires the use of a custom SQL query.

    The idea here is to define a SQL query which looks up the backend server to use, based on information in the frontend FTPS client certificate. Since the mod_proxy module already uses SQLite, you should be able to use the mod_sql_sqlite module if needed. The SQL table schema might look like:

      CREATE TABLE proxy_backends (
        common_name TEXT PRIMARY KEY,
        url TEXT
      );
    
      <IfModule mod_tls.c>
        ...
        # Tell mod_tls to export environment variables containing various
        # certificate fields, for use by e.g. mod_sql.
        TLSOptions StdEnvVars
        ...
      </IfModule>
    
      <IfModule mod_sql.c>
        ...
        # Use the TLS_CLIENT_S_DN_CN environment variable (for the CommonName of
        # the Subject section of the frontend FTPS client certificate) provided
        # by mod_tls in our selection.
        SQLNamedQuery get-user-servers SELECT "url FROM proxy_backends WHERE common_name = '%{env:TLS_CLIENT_S_DN_CN}'"
        ...
      </IfModule>
    
      <IfModule mod_proxy.c>
        ...
        ProxyRole reverse
        ProxyReverseConnectPolicy PerUser
    
        # Use a named SQL query to lookup the backend server to use.  Note that
        # the name used here is the name of our SQLNamedQuery defined above.
        ProxyReverseServers sql:/get-user-servers
        ...
      </IfModule>
    
    Note that the mod_tls module provides many other environment variables for other fields of the certificate; using a field other than CommonName is also quite doable, depending on your needs.


    Installation

    To install mod_proxy, go to the third-party module area in the proftpd source code and unpack the mod_proxy source tarball:
      $ cd proftpd-dir/contrib/
      $ tar zxvf /path/to/mod_proxy-version.tar.gz
    
    after unpacking the latest proftpd-1.3.x source code. For including mod_proxy as a statically linked module:
      $ ./configure --with-modules=mod_proxy:...
    
    To build mod_proxy as a DSO module:
      $ ./configure --enable-dso --with-shared=mod_proxy:...
    
    Then follow the usual steps:
      $ make
      $ make install
    
    Note: mod_proxy uses the SQLite library; thus the sqlite3 development library/headers must be installed for building mod_proxy.

    It is highly recommended that SQLite 3.8.5 or later be used. Problems have been reported with mod_proxy when SQLite 3.6.20 is used; these problems disappeared once SQLite was upgraded to a newer version. Note: mod_proxy uses the OpenSSL library, so its development library/header files must be installed for building mod_proxy.


    © Copyright 2015-2025 TJ Saunders
    All Rights Reserved

    proftpd-mod_proxy-0.9.5/t/000077500000000000000000000000001475737016700155055ustar00rootroot00000000000000proftpd-mod_proxy-0.9.5/t/Makefile.in000066400000000000000000000057241475737016700175620ustar00rootroot00000000000000CC=@CC@ @SET_MAKE@ top_builddir=../../.. top_srcdir=../../.. module_srcdir=.. srcdir=@srcdir@ VPATH=@srcdir@ include $(top_srcdir)/Make.rules # Necessary redefinitions INCLUDES=-I. -I.. -I$(module_srcdir)/include -I../../.. -I../../../include @INCLUDES@ TEST_CPPFLAGS=$(ADDL_CPPFLAGS) -DHAVE_CONFIG_H $(DEFAULT_PATHS) $(PLATFORM) $(INCLUDES) TEST_LDFLAGS=-L$(top_srcdir)/lib @LIBDIRS@ EXEEXT=@EXEEXT@ TEST_API_DEPS=\ $(top_srcdir)/lib/prbase.a \ $(top_srcdir)/src/pool.o \ $(top_srcdir)/src/privs.o \ $(top_srcdir)/src/str.o \ $(top_srcdir)/src/sets.o \ $(top_srcdir)/src/table.o \ $(top_srcdir)/src/netacl.o \ $(top_srcdir)/src/class.o \ $(top_srcdir)/src/event.o \ $(top_srcdir)/src/timers.o \ $(top_srcdir)/src/stash.o \ $(top_srcdir)/src/modules.o \ $(top_srcdir)/src/cmd.o \ $(top_srcdir)/src/configdb.o \ $(top_srcdir)/src/parser.o \ $(top_srcdir)/src/regexp.o \ $(top_srcdir)/src/fsio.o \ $(top_srcdir)/src/netio.o \ $(top_srcdir)/src/inet.o \ $(top_srcdir)/src/netaddr.o \ $(top_srcdir)/src/response.o \ $(top_srcdir)/src/auth.o \ $(top_srcdir)/src/env.o \ $(top_srcdir)/src/trace.o \ $(top_srcdir)/src/support.o \ $(top_srcdir)/src/json.o \ $(top_srcdir)/src/redis.o \ $(top_srcdir)/src/error.o \ $(module_srcdir)/lib/proxy/random.o \ $(module_srcdir)/lib/proxy/db.o \ $(module_srcdir)/lib/proxy/dns.o \ $(module_srcdir)/lib/proxy/uri.o \ $(module_srcdir)/lib/proxy/conn.o \ $(module_srcdir)/lib/proxy/netio.o \ $(module_srcdir)/lib/proxy/inet.o \ $(module_srcdir)/lib/proxy/str.o \ $(module_srcdir)/lib/proxy/tls.o \ $(module_srcdir)/lib/proxy/tls/db.o \ $(module_srcdir)/lib/proxy/tls/redis.o \ $(module_srcdir)/lib/proxy/session.o \ $(module_srcdir)/lib/proxy/reverse.o \ $(module_srcdir)/lib/proxy/reverse/db.o \ $(module_srcdir)/lib/proxy/reverse/redis.o \ $(module_srcdir)/lib/proxy/forward.o \ $(module_srcdir)/lib/proxy/ftp/conn.o \ $(module_srcdir)/lib/proxy/ftp/ctrl.o \ $(module_srcdir)/lib/proxy/ftp/data.o \ $(module_srcdir)/lib/proxy/ftp/dirlist.o \ $(module_srcdir)/lib/proxy/ftp/facts.o \ $(module_srcdir)/lib/proxy/ftp/msg.o \ $(module_srcdir)/lib/proxy/ftp/sess.o \ $(module_srcdir)/lib/proxy/ftp/xfer.o TEST_API_LIBS="-lcheck -lm @MODULE_LIBS@" TEST_API_OBJS=\ api/random.o \ api/db.o \ api/dns.o \ api/uri.o \ api/conn.o \ api/netio.o \ api/inet.o \ api/str.o \ api/tls.o \ api/reverse.o \ api/forward.o \ api/session.o \ api/ftp/msg.o \ api/ftp/conn.o \ api/ftp/ctrl.o \ api/ftp/data.o \ api/ftp/dirlist.o \ api/ftp/facts.o \ api/ftp/sess.o \ api/ftp/xfer.o \ api/stubs.o \ api/tests.o dummy: api/.c.o: $(CC) $(CPPFLAGS) $(TEST_CPPFLAGS) $(CFLAGS) -c $< api-tests$(EXEEXT): $(TEST_API_OBJS) $(TEST_API_DEPS) $(LIBTOOL) --mode=link --tag=CC $(CC) $(LDFLAGS) $(TEST_LDFLAGS) -o $@ $(TEST_API_DEPS) $(TEST_API_OBJS) $(TEST_API_LIBS) $(LIBS) ./$@ clean: $(LIBTOOL) --mode=clean $(RM) *.o api/*.o api/*/*.o api-tests$(EXEEXT) api-tests.log proftpd-mod_proxy-0.9.5/t/api/000077500000000000000000000000001475737016700162565ustar00rootroot00000000000000proftpd-mod_proxy-0.9.5/t/api/conn.c000066400000000000000000000661261475737016700173720ustar00rootroot00000000000000/* * ProFTPD - mod_proxy testsuite * Copyright (c) 2013-2022 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ /* Conn API tests */ #include "tests.h" static pool *p = NULL; static void set_up(void) { if (p == NULL) { p = permanent_pool = make_sub_pool(NULL); session.c = NULL; session.notes = NULL; } if (getenv("TEST_VERBOSE") != NULL) { pr_trace_set_levels("proxy.conn", 1, 20); } } static void tear_down(void) { if (getenv("TEST_VERBOSE") != NULL) { pr_trace_set_levels("proxy.conn", 0, 0); } if (p) { destroy_pool(p); p = permanent_pool = NULL; session.c = NULL; session.notes = NULL; } } START_TEST (conn_create_test) { const struct proxy_conn *pconn; const char *url; pconn = proxy_conn_create(NULL, NULL, 0); ck_assert_msg(pconn == NULL, "Failed to handle null arguments"); ck_assert_msg(errno == EINVAL, "Failed to set errno to EINVAL"); mark_point(); url = "ftp://127.0.0.1:21"; pconn = proxy_conn_create(NULL, url, 0); ck_assert_msg(pconn == NULL, "Failed to handle null pool argument"); ck_assert_msg(errno == EINVAL, "Failed to set errno to EINVAL"); mark_point(); pconn = proxy_conn_create(p, NULL, 0); ck_assert_msg(pconn == NULL, "Failed to handle null URL argument"); ck_assert_msg(errno == EINVAL, "Failed to set errno to EINVAL"); /* We're already testing URL parsing elsewhere, so we only need to * supply well-formed URLs in these tests. */ mark_point(); url = "http://127.0.0.1:80"; pconn = proxy_conn_create(p, url, 0); ck_assert_msg(pconn == NULL, "Failed to handle unsupported protocol/scheme"); ck_assert_msg(errno == EPERM, "Failed to set errno to EPERM"); mark_point(); url = "ftp://foo.bar.baz"; pconn = proxy_conn_create(p, url, 0); ck_assert_msg(pconn == NULL, "Failed to handle unresolvable host"); ck_assert_msg(errno == EINVAL, "Failed to set errno to EINVAL"); mark_point(); url = "ftp://127.0.0.1:21"; pconn = proxy_conn_create(p, url, 0); ck_assert_msg(pconn != NULL, "Failed to create pconn for URL '%s' as expected", url); proxy_conn_free(pconn); } END_TEST START_TEST (conn_get_addr_test) { const struct proxy_conn *pconn; const char *ipstr, *url; const pr_netaddr_t *pconn_addr; array_header *other_addrs = NULL; pconn_addr = proxy_conn_get_addr(NULL, NULL); ck_assert_msg(pconn_addr == NULL, "Failed to handle null argument"); ck_assert_msg(errno == EINVAL, "Failed to set errno to EINVAL"); url = "ftp://127.0.0.1:21"; pconn = proxy_conn_create(p, url, 0); ck_assert_msg(pconn != NULL, "Failed to create pconn for URL '%s' as expected", url); pconn_addr = proxy_conn_get_addr(pconn, &other_addrs); ck_assert_msg(pconn_addr != NULL, "Failed to get address for pconn"); ipstr = pr_netaddr_get_ipstr(pconn_addr); ck_assert_msg(strcmp(ipstr, "127.0.0.1") == 0, "Expected IP address '127.0.0.1', got '%s'", ipstr); proxy_conn_free(pconn); } END_TEST START_TEST (conn_get_host_test) { const char *host, *url, *expected; const struct proxy_conn *pconn; host = proxy_conn_get_host(NULL); ck_assert_msg(host == NULL, "Got host from null pconn unexpectedly"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); url = "ftp://127.0.0.1:21"; pconn = proxy_conn_create(p, url, 0); ck_assert_msg(pconn != NULL, "Failed to create pconn for URL '%s' as expected", url); host = proxy_conn_get_host(pconn); ck_assert_msg(host != NULL, "Failed to get host from conn: %s", strerror(errno)); expected = "127.0.0.1"; ck_assert_msg(strcmp(host, expected) == 0, "Expected host '%s', got '%s'", expected, host); proxy_conn_free(pconn); } END_TEST START_TEST (conn_get_port_test) { int port; const char *url; const struct proxy_conn *pconn; port = proxy_conn_get_port(NULL); ck_assert_msg(port < 0, "Got port from null pconn unexpectedly"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); url = "ftp://127.0.0.1:21"; pconn = proxy_conn_create(p, url, 0); ck_assert_msg(pconn != NULL, "Failed to create pconn for URL '%s' as expected", url); port = proxy_conn_get_port(pconn); ck_assert_msg(port == 21, "Expected port 21, got %d", port); proxy_conn_free(pconn); } END_TEST START_TEST (conn_get_hostport_test) { const struct proxy_conn *pconn; const char *hostport, *url; hostport = proxy_conn_get_hostport(NULL); ck_assert_msg(hostport == NULL, "Failed to handle null argument"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); url = "ftp://127.0.0.1:21"; pconn = proxy_conn_create(p, url, 0); ck_assert_msg(pconn != NULL, "Failed to create pconn for URL '%s' as expected", url); hostport = proxy_conn_get_hostport(pconn); ck_assert_msg(hostport != NULL, "Failed to get host/port for pconn"); ck_assert_msg(strcmp(hostport, "127.0.0.1:21") == 0, "Expected host/port '127.0.0.1:21', got '%s'", hostport); /* Implicit/assumed ports */ proxy_conn_free(pconn); url = "ftp://127.0.0.1"; pconn = proxy_conn_create(p, url, 0); ck_assert_msg(pconn != NULL, "Failed to create pconn for URL '%s' as expected", url); hostport = proxy_conn_get_hostport(pconn); ck_assert_msg(hostport != NULL, "Failed to get host/port for pconn"); ck_assert_msg(strcmp(hostport, "127.0.0.1:21") == 0, "Expected host/port '127.0.0.1:21', got '%s'", hostport); proxy_conn_free(pconn); } END_TEST START_TEST (conn_get_uri_test) { const struct proxy_conn *pconn; const char *pconn_url, *url; pconn_url = proxy_conn_get_uri(NULL); ck_assert_msg(pconn_url == NULL, "Failed to handle null argument"); ck_assert_msg(errno == EINVAL, "Failed to set errno to EINVAL"); url = "ftp://127.0.0.1:21"; pconn = proxy_conn_create(p, url, 0); ck_assert_msg(pconn != NULL, "Failed to create pconn for URL '%s' as expected", url); pconn_url = proxy_conn_get_uri(pconn); ck_assert_msg(pconn_url != NULL, "Failed to get URL for pconn"); ck_assert_msg(strcmp(pconn_url, url) == 0, "Expected URL '%s', got '%s'", url, pconn_url); proxy_conn_free(pconn); } END_TEST START_TEST (conn_get_username_test) { const char *username, *url, *expected; const struct proxy_conn *pconn; username = proxy_conn_get_username(NULL); ck_assert_msg(username == NULL, "Got username from null pconn unexpectedly"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); url = "ftp://127.0.0.1:21"; pconn = proxy_conn_create(p, url, 0); ck_assert_msg(pconn != NULL, "Failed to create pconn for URL '%s' as expected", url); username = proxy_conn_get_username(pconn); ck_assert_msg(username == NULL, "Got username unexpectedly"); proxy_conn_free(pconn); url = "ftp://user:passwd@127.0.0.1:2121"; pconn = proxy_conn_create(p, url, 0); ck_assert_msg(pconn != NULL, "Failed to create pconn for URL '%s' as expected", url); username = proxy_conn_get_username(pconn); ck_assert_msg(username != NULL, "Expected username from conn"); expected = "user"; ck_assert_msg(strcmp(username, expected) == 0, "Expected username '%s', got '%s'", expected, username); proxy_conn_free(pconn); } END_TEST START_TEST (conn_get_password_test) { const char *passwd, *url, *expected; const struct proxy_conn *pconn; passwd = proxy_conn_get_password(NULL); ck_assert_msg(passwd == NULL, "Got password from null pconn unexpectedly"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); url = "ftp://127.0.0.1:21"; pconn = proxy_conn_create(p, url, 0); ck_assert_msg(pconn != NULL, "Failed to create pconn for URL '%s' as expected", url); passwd = proxy_conn_get_password(pconn); ck_assert_msg(passwd == NULL, "Got password unexpectedly"); proxy_conn_free(pconn); url = "ftp://user:passwd@127.0.0.1:2121"; pconn = proxy_conn_create(p, url, 0); ck_assert_msg(pconn != NULL, "Failed to create pconn for URL '%s' as expected", url); passwd = proxy_conn_get_password(pconn); ck_assert_msg(passwd != NULL, "Expected password from conn"); expected = "passwd"; ck_assert_msg(strcmp(passwd, expected) == 0, "Expected password '%s', got '%s'", expected, passwd); proxy_conn_free(pconn); url = "ftp://user:@127.0.0.1:2121"; pconn = proxy_conn_create(p, url, 0); ck_assert_msg(pconn != NULL, "Failed to create pconn for URL '%s' as expected", url); passwd = proxy_conn_get_password(pconn); ck_assert_msg(passwd != NULL, "Expected password from conn"); expected = ""; ck_assert_msg(strcmp(passwd, expected) == 0, "Expected password '%s', got '%s'", expected, passwd); proxy_conn_free(pconn); } END_TEST START_TEST (conn_get_tls_test) { int tls; const char *url; const struct proxy_conn *pconn; mark_point(); tls = proxy_conn_get_tls(NULL); ck_assert_msg(tls < 0, "Got TLS from null pconn unexpectedly"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); url = "ftp://127.0.0.1:21"; pconn = proxy_conn_create(p, url, 0); ck_assert_msg(pconn != NULL, "Failed to create pconn for URL '%s' as expected", url); mark_point(); tls = proxy_conn_get_tls(pconn); ck_assert_msg(tls == PROXY_TLS_ENGINE_AUTO, "Expected TLS auto, got %d", tls); proxy_conn_free(pconn); mark_point(); url = "ftp+srv://127.0.0.1:21"; pconn = proxy_conn_create(p, url, 0); ck_assert_msg(pconn != NULL, "Failed to create pconn for URL '%s' as expected", url); mark_point(); tls = proxy_conn_get_tls(pconn); ck_assert_msg(tls == PROXY_TLS_ENGINE_AUTO, "Expected TLS auto, got %d", tls); proxy_conn_free(pconn); mark_point(); url = "ftp+txt://127.0.0.1:21"; pconn = proxy_conn_create(p, url, 0); ck_assert_msg(pconn != NULL, "Failed to create pconn for URL '%s' as expected", url); mark_point(); tls = proxy_conn_get_tls(pconn); ck_assert_msg(tls == PROXY_TLS_ENGINE_AUTO, "Expected TLS auto, got %d", tls); proxy_conn_free(pconn); mark_point(); url = "ftps://127.0.0.1:21"; pconn = proxy_conn_create(p, url, 0); ck_assert_msg(pconn != NULL, "Failed to create pconn for URL '%s' as expected", url); tls = proxy_conn_get_tls(pconn); ck_assert_msg(tls == PROXY_TLS_ENGINE_ON, "Expected TLS on, got %d", tls); proxy_conn_free(pconn); mark_point(); url = "ftps+srv://127.0.0.1:21"; pconn = proxy_conn_create(p, url, 0); ck_assert_msg(pconn != NULL, "Failed to create pconn for URL '%s' as expected", url); tls = proxy_conn_get_tls(pconn); ck_assert_msg(tls == PROXY_TLS_ENGINE_ON, "Expected TLS on, got %d", tls); proxy_conn_free(pconn); mark_point(); url = "ftps+txt://127.0.0.1:21"; pconn = proxy_conn_create(p, url, 0); ck_assert_msg(pconn != NULL, "Failed to create pconn for URL '%s' as expected", url); tls = proxy_conn_get_tls(pconn); ck_assert_msg(tls == PROXY_TLS_ENGINE_ON, "Expected TLS on, got %d", tls); proxy_conn_free(pconn); mark_point(); url = "ftps://127.0.0.1:990"; pconn = proxy_conn_create(p, url, 0); ck_assert_msg(pconn != NULL, "Failed to create pconn for URL '%s' as expected", url); tls = proxy_conn_get_tls(pconn); ck_assert_msg(tls == PROXY_TLS_ENGINE_IMPLICIT, "Expected TLS implicit, got %d", tls); proxy_conn_free(pconn); mark_point(); url = "ftps+srv://127.0.0.1:990"; pconn = proxy_conn_create(p, url, 0); ck_assert_msg(pconn != NULL, "Failed to create pconn for URL '%s' as expected", url); tls = proxy_conn_get_tls(pconn); ck_assert_msg(tls == PROXY_TLS_ENGINE_ON, "Expected TLS on, got %d", tls); proxy_conn_free(pconn); mark_point(); url = "ftps+txt://127.0.0.1:990"; pconn = proxy_conn_create(p, url, 0); ck_assert_msg(pconn != NULL, "Failed to create pconn for URL '%s' as expected", url); tls = proxy_conn_get_tls(pconn); ck_assert_msg(tls == PROXY_TLS_ENGINE_ON, "Expected TLS on, got %d", tls); proxy_conn_free(pconn); } END_TEST START_TEST (conn_use_dns_srv_test) { int use_dns_srv; const char *url; const struct proxy_conn *pconn; mark_point(); use_dns_srv = proxy_conn_use_dns_srv(NULL); ck_assert_msg(use_dns_srv < 0, "Got DNS SRV from null pconn unexpectedly"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); url = "ftp://127.0.0.1:21"; pconn = proxy_conn_create(p, url, 0); ck_assert_msg(pconn != NULL, "Failed to create pconn for URL '%s' as expected", url); mark_point(); use_dns_srv = proxy_conn_use_dns_srv(pconn); ck_assert_msg(use_dns_srv == FALSE, "Expected DNS SRV = false, got %d", use_dns_srv); proxy_conn_free(pconn); mark_point(); url = "ftp+srv://127.0.0.1:21"; pconn = proxy_conn_create(p, url, 0); ck_assert_msg(pconn != NULL, "Failed to create pconn for URL '%s' as expected", url); mark_point(); use_dns_srv = proxy_conn_use_dns_srv(pconn); ck_assert_msg(use_dns_srv == TRUE, "Expected DNS SRV = true, got %d", use_dns_srv); proxy_conn_free(pconn); mark_point(); url = "ftps://127.0.0.1:21"; pconn = proxy_conn_create(p, url, 0); ck_assert_msg(pconn != NULL, "Failed to create pconn for URL '%s' as expected", url); use_dns_srv = proxy_conn_use_dns_srv(pconn); ck_assert_msg(use_dns_srv == FALSE, "Expected DNS SRV = false, got %d", use_dns_srv); proxy_conn_free(pconn); mark_point(); url = "ftps+srv://127.0.0.1:21"; pconn = proxy_conn_create(p, url, 0); ck_assert_msg(pconn != NULL, "Failed to create pconn for URL '%s' as expected", url); use_dns_srv = proxy_conn_use_dns_srv(pconn); ck_assert_msg(use_dns_srv == TRUE, "Expected DNS SRV = true, got %d", use_dns_srv); proxy_conn_free(pconn); } END_TEST START_TEST (conn_use_dns_txt_test) { int use_dns_txt; const char *url; const struct proxy_conn *pconn; mark_point(); use_dns_txt = proxy_conn_use_dns_txt(NULL); ck_assert_msg(use_dns_txt < 0, "Got DNS TXT from null pconn unexpectedly"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); url = "ftp://127.0.0.1:21"; pconn = proxy_conn_create(p, url, 0); ck_assert_msg(pconn != NULL, "Failed to create pconn for URL '%s' as expected", url); mark_point(); use_dns_txt = proxy_conn_use_dns_txt(pconn); ck_assert_msg(use_dns_txt == FALSE, "Expected DNS TXT = false, got %d", use_dns_txt); proxy_conn_free(pconn); mark_point(); url = "ftp+txt://127.0.0.1:21"; pconn = proxy_conn_create(p, url, 0); ck_assert_msg(pconn != NULL, "Failed to create pconn for URL '%s' as expected", url); mark_point(); use_dns_txt = proxy_conn_use_dns_txt(pconn); ck_assert_msg(use_dns_txt == TRUE, "Expected DNS TXT = true, got %d", use_dns_txt); proxy_conn_free(pconn); mark_point(); url = "ftps://127.0.0.1:21"; pconn = proxy_conn_create(p, url, 0); ck_assert_msg(pconn != NULL, "Failed to create pconn for URL '%s' as expected", url); use_dns_txt = proxy_conn_use_dns_txt(pconn); ck_assert_msg(use_dns_txt == FALSE, "Expected DNS TXT = false, got %d", use_dns_txt); proxy_conn_free(pconn); mark_point(); url = "ftps+txt://127.0.0.1:21"; pconn = proxy_conn_create(p, url, 0); ck_assert_msg(pconn != NULL, "Failed to create pconn for URL '%s' as expected", url); use_dns_txt = proxy_conn_use_dns_txt(pconn); ck_assert_msg(use_dns_txt == TRUE, "Expected DNS TXT = true, got %d", use_dns_txt); proxy_conn_free(pconn); } END_TEST START_TEST (conn_get_dns_ttl_test) { int res; const char *url; const struct proxy_conn *pconn; mark_point(); res = proxy_conn_get_dns_ttl(NULL); ck_assert_msg(res < 0, "Failed to handle null argument"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); mark_point(); url = "ftp://www.google.com"; pconn = proxy_conn_create(p, url, 0); ck_assert_msg(pconn != NULL, "Failed to create pconn for URL '%s' as expected", url); res = proxy_conn_get_dns_ttl(pconn); ck_assert_msg(res < 0, "Failed to handle non-TTL URL"); ck_assert_msg(errno == EPERM, "Expected EPERM (%d), got %s (%d)", EPERM, strerror(errno), errno); proxy_conn_free(pconn); mark_point(); url = "ftp+srv://127.0.0.1"; pconn = proxy_conn_create(p, url, 0); ck_assert_msg(pconn != NULL, "Failed to create pconn for URL '%s' as expected", url); res = proxy_conn_get_dns_ttl(pconn); ck_assert_msg(res < 0, "Failed to handle SRV URL"); ck_assert_msg(errno == ENOENT, "Expected ENOENT (%d), got %s (%d)", ENOENT, strerror(errno), errno); proxy_conn_free(pconn); url = "ftps+txt://127.0.0.1:21"; pconn = proxy_conn_create(p, url, 0); ck_assert_msg(pconn != NULL, "Failed to create pconn for URL '%s' as expected", url); res = proxy_conn_get_dns_ttl(pconn); ck_assert_msg(res < 0, "Failed to handle TXT URL"); ck_assert_msg(errno == EPERM, "Expected EPERM (%d), got %s (%d)", EPERM, strerror(errno), errno); proxy_conn_free(pconn); } END_TEST START_TEST (conn_get_server_conn_test) { /* XXX TODO */ } END_TEST START_TEST (conn_clear_username_test) { const char *username, *url, *expected; const struct proxy_conn *pconn; mark_point(); proxy_conn_clear_username(NULL); url = "ftp://127.0.0.1:21"; pconn = proxy_conn_create(p, url, 0); ck_assert_msg(pconn != NULL, "Failed to create pconn for URL '%s' as expected", url); username = proxy_conn_get_username(pconn); ck_assert_msg(username == NULL, "Got username unexpectedly"); mark_point(); proxy_conn_clear_username(pconn); proxy_conn_free(pconn); url = "ftp://user:passwd@127.0.0.1:2121"; pconn = proxy_conn_create(p, url, 0); ck_assert_msg(pconn != NULL, "Failed to create pconn for URL '%s' as expected", url); username = proxy_conn_get_username(pconn); ck_assert_msg(username != NULL, "Expected username from conn"); expected = "user"; ck_assert_msg(strcmp(username, expected) == 0, "Expected username '%s', got '%s'", expected, username); mark_point(); proxy_conn_clear_username(pconn); username = proxy_conn_get_username(pconn); ck_assert_msg(username == NULL, "Expected null username, got '%s'", username); proxy_conn_free(pconn); } END_TEST START_TEST (conn_clear_password_test) { const char *passwd, *url, *expected; const struct proxy_conn *pconn; mark_point(); proxy_conn_clear_password(NULL); url = "ftp://127.0.0.1:21"; pconn = proxy_conn_create(p, url, 0); ck_assert_msg(pconn != NULL, "Failed to create pconn for URL '%s' as expected", url); passwd = proxy_conn_get_password(pconn); ck_assert_msg(passwd == NULL, "Got password unexpectedly"); mark_point(); proxy_conn_clear_password(pconn); proxy_conn_free(pconn); url = "ftp://user:passwd@127.0.0.1:2121"; pconn = proxy_conn_create(p, url, 0); ck_assert_msg(pconn != NULL, "Failed to create pconn for URL '%s' as expected", url); passwd = proxy_conn_get_password(pconn); ck_assert_msg(passwd != NULL, "Expected password from conn"); expected = "passwd"; ck_assert_msg(strcmp(passwd, expected) == 0, "Expected password '%s', got '%s'", expected, passwd); mark_point(); proxy_conn_clear_password(pconn); passwd = proxy_conn_get_password(pconn); ck_assert_msg(passwd == NULL, "Expected null password, got '%s'", passwd); proxy_conn_free(pconn); } END_TEST START_TEST (conn_timeout_cb_test) { int res; struct proxy_session *proxy_sess; const pr_netaddr_t *addr; session.notes = pr_table_alloc(p, 0); proxy_sess = (struct proxy_session *) proxy_session_alloc(p); ck_assert_msg(proxy_sess != NULL, "Failed to allocate proxy session: %s", strerror(errno)); pr_table_add(session.notes, "mod_proxy.proxy-session", proxy_sess, sizeof(struct proxy_session)); addr = pr_netaddr_get_addr(p, "1.2.3.4", NULL); ck_assert_msg(addr != NULL, "Failed to resolve '1.2.3.4': %s", strerror(errno)); pr_table_add(session.notes, "mod_proxy.proxy-connect-address", addr, sizeof(pr_netaddr_t)); proxy_sess->connect_timeout = 1; res = proxy_conn_connect_timeout_cb(0, 0, 0, NULL); ck_assert_msg(res == 0, "Failed to handle timeout: %s", strerror(errno)); proxy_sess->connect_timeout = 2; res = proxy_conn_connect_timeout_cb(0, 0, 0, NULL); ck_assert_msg(res == 0, "Failed to handle timeout: %s", strerror(errno)); session.notes = NULL; proxy_session_free(p, proxy_sess); } END_TEST START_TEST (conn_send_proxy_v1_test) { int res; conn_t *conn; res = proxy_conn_send_proxy_v1(NULL, NULL); ck_assert_msg(res < 0, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); res = proxy_conn_send_proxy_v1(p, NULL); ck_assert_msg(res < 0, "Failed to handle null conn"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); conn = pr_inet_create_conn(p, -1, NULL, INPORT_ANY, FALSE); session.c = pr_inet_create_conn(p, -1, NULL, INPORT_ANY, FALSE); session.c->local_addr = session.c->remote_addr = pr_netaddr_get_addr(p, "127.0.0.1", FALSE); mark_point(); res = proxy_conn_send_proxy_v1(p, conn); ck_assert_msg(res < 0, "Failed to handle invalid conn"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); session.c->local_addr = session.c->remote_addr = pr_netaddr_get_addr(p, "::1", FALSE); mark_point(); res = proxy_conn_send_proxy_v1(p, conn); ck_assert_msg(res < 0, "Failed to handle invalid conn"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); session.c->local_addr = pr_netaddr_get_addr(p, "::1", FALSE); session.c->remote_addr = pr_netaddr_get_addr(p, "127.0.0.1", FALSE); mark_point(); res = proxy_conn_send_proxy_v1(p, conn); ck_assert_msg(res < 0, "Failed to handle invalid conn"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); session.c->remote_addr = pr_netaddr_get_addr(p, "::1", FALSE); session.c->local_addr = pr_netaddr_get_addr(p, "127.0.0.1", FALSE); mark_point(); res = proxy_conn_send_proxy_v1(p, conn); ck_assert_msg(res < 0, "Failed to handle invalid conn"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); conn->remote_addr = pr_netaddr_get_addr(p, "127.0.0.1", FALSE); mark_point(); res = proxy_conn_send_proxy_v1(p, conn); ck_assert_msg(res < 0, "Failed to handle invalid conn"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); pr_inet_close(p, conn); pr_inet_close(p, session.c); session.c = NULL; } END_TEST START_TEST (conn_send_proxy_v2_test) { int res; conn_t *conn; res = proxy_conn_send_proxy_v2(NULL, NULL); ck_assert_msg(res < 0, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); res = proxy_conn_send_proxy_v2(p, NULL); ck_assert_msg(res < 0, "Failed to handle null conn"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); conn = pr_inet_create_conn(p, -1, NULL, INPORT_ANY, FALSE); session.c = pr_inet_create_conn(p, -1, NULL, INPORT_ANY, FALSE); session.c->local_addr = session.c->remote_addr = pr_netaddr_get_addr(p, "127.0.0.1", FALSE); mark_point(); res = proxy_conn_send_proxy_v2(p, conn); ck_assert_msg(res < 0, "Failed to handle invalid conn"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); session.c->local_addr = session.c->remote_addr = pr_netaddr_get_addr(p, "::1", FALSE); mark_point(); res = proxy_conn_send_proxy_v2(p, conn); ck_assert_msg(res < 0, "Failed to handle invalid conn"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); session.c->local_addr = pr_netaddr_get_addr(p, "::1", FALSE); session.c->remote_addr = pr_netaddr_get_addr(p, "127.0.0.1", FALSE); mark_point(); res = proxy_conn_send_proxy_v2(p, conn); ck_assert_msg(res < 0, "Failed to handle invalid conn"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); session.c->remote_addr = pr_netaddr_get_addr(p, "::1", FALSE); session.c->local_addr = pr_netaddr_get_addr(p, "127.0.0.1", FALSE); mark_point(); res = proxy_conn_send_proxy_v2(p, conn); ck_assert_msg(res < 0, "Failed to handle invalid conn"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); conn->remote_addr = pr_netaddr_get_addr(p, "127.0.0.1", FALSE); mark_point(); res = proxy_conn_send_proxy_v2(p, conn); ck_assert_msg(res < 0, "Failed to handle invalid conn"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); pr_inet_close(p, conn); pr_inet_close(p, session.c); session.c = NULL; } END_TEST Suite *tests_get_conn_suite(void) { Suite *suite; TCase *testcase; suite = suite_create("conn"); testcase = tcase_create("base"); tcase_add_checked_fixture(testcase, set_up, tear_down); tcase_add_test(testcase, conn_create_test); tcase_add_test(testcase, conn_get_addr_test); tcase_add_test(testcase, conn_get_host_test); tcase_add_test(testcase, conn_get_port_test); tcase_add_test(testcase, conn_get_hostport_test); tcase_add_test(testcase, conn_get_uri_test); tcase_add_test(testcase, conn_get_username_test); tcase_add_test(testcase, conn_get_password_test); tcase_add_test(testcase, conn_get_tls_test); tcase_add_test(testcase, conn_use_dns_srv_test); tcase_add_test(testcase, conn_use_dns_txt_test); tcase_add_test(testcase, conn_get_dns_ttl_test); tcase_add_test(testcase, conn_get_server_conn_test); tcase_add_test(testcase, conn_clear_username_test); tcase_add_test(testcase, conn_clear_password_test); tcase_add_test(testcase, conn_timeout_cb_test); tcase_add_test(testcase, conn_send_proxy_v1_test); tcase_add_test(testcase, conn_send_proxy_v2_test); /* Allow a longer timeout on these tests, especially for the * unpredictable CI environment. */ tcase_set_timeout(testcase, 15); suite_add_tcase(suite, testcase); return suite; } proftpd-mod_proxy-0.9.5/t/api/db.c000066400000000000000000000531211475737016700170110ustar00rootroot00000000000000/* * ProFTPD - mod_proxy testsuite * Copyright (c) 2015-2022 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ /* Database API tests. */ #include "tests.h" static pool *p = NULL; static const char *db_test_table = "/tmp/prt-mod_proxy-db.dat"; static void set_up(void) { (void) unlink(db_test_table); if (p == NULL) { p = permanent_pool = make_sub_pool(NULL); session.c = NULL; session.notes = NULL; } if (getenv("TEST_VERBOSE") != NULL) { pr_trace_set_levels("proxy.db", 1, 20); } mark_point(); proxy_db_init(p); } static void tear_down(void) { proxy_db_free(); if (getenv("TEST_VERBOSE") != NULL) { pr_trace_set_levels("proxy.db", 0, 0); } if (p) { destroy_pool(p); p = permanent_pool = NULL; session.c = NULL; session.notes = NULL; } (void) unlink(db_test_table); } START_TEST (db_close_test) { int res; res = proxy_db_close(NULL, NULL); ck_assert_msg(res < 0, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Failed to set errno to EINVAL, got %s (%d)", strerror(errno), errno); res = proxy_db_close(p, NULL); ck_assert_msg(res < 0, "Failed to handle null dbh"); ck_assert_msg(errno == EINVAL, "Failed to set errno to EINVAL, got %s (%d)", strerror(errno), errno); } END_TEST START_TEST (db_open_test) { int res; const char *table_path, *schema_name; struct proxy_dbh *dbh; dbh = proxy_db_open(NULL, NULL, NULL); ck_assert_msg(dbh == NULL, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Failed to set errno to EINVAL, got %s (%d)", strerror(errno), errno); dbh = proxy_db_open(p, NULL, NULL); ck_assert_msg(dbh == NULL, "Failed to handle null table path"); ck_assert_msg(errno == EINVAL, "Failed to set errno to EINVAL, got %s (%d)", strerror(errno), errno); (void) unlink(db_test_table); table_path = db_test_table; schema_name = "proxy_test"; dbh = proxy_db_open(p, table_path, NULL); ck_assert_msg(dbh == NULL, "Failed to handle null schema name"); ck_assert_msg(errno == EINVAL, "Failed to set errno to EINVAL, got %s (%d)", strerror(errno), errno); dbh = proxy_db_open(p, table_path, schema_name); ck_assert_msg(dbh != NULL, "Failed to open table '%s': %s", table_path, strerror(errno)); res = proxy_db_close(p, dbh); ck_assert_msg(res == 0, "Failed to close table '%s': %s", table_path, strerror(errno)); (void) unlink(db_test_table); } END_TEST START_TEST (db_open_with_version_test) { int res, flags = 0; struct proxy_dbh *dbh; const char *table_path, *schema_name; unsigned int schema_version; dbh = proxy_db_open_with_version(NULL, NULL, NULL, 0, 0); ck_assert_msg(dbh == NULL, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Failed to set errno to EINVAL, got %s (%d)", strerror(errno), errno); (void) unlink(db_test_table); table_path = db_test_table; schema_name = "proxy_test"; schema_version = 0; mark_point(); dbh = proxy_db_open_with_version(p, table_path, schema_name, schema_version, flags); ck_assert_msg(dbh != NULL, "Failed to open table '%s', schema '%s', version %u: %s", table_path, schema_name, schema_version, strerror(errno)); res = proxy_db_close(p, dbh); ck_assert_msg(res == 0, "Failed to close database: %s", strerror(errno)); flags |= PROXY_DB_OPEN_FL_INTEGRITY_CHECK; mark_point(); dbh = proxy_db_open_with_version(p, table_path, schema_name, schema_version, flags); ck_assert_msg(dbh != NULL, "Failed to open table '%s', schema '%s', version %u: %s", table_path, schema_name, schema_version, strerror(errno)); res = proxy_db_close(p, dbh); ck_assert_msg(res == 0, "Failed to close database: %s", strerror(errno)); if (getenv("CI") == NULL && getenv("TRAVIS") == NULL) { /* Enable the vacuuming for these tests. */ flags |= PROXY_DB_OPEN_FL_VACUUM; mark_point(); dbh = proxy_db_open_with_version(p, table_path, schema_name, schema_version, flags); ck_assert_msg(dbh != NULL, "Failed to open table '%s', schema '%s', version %u: %s", table_path, schema_name, schema_version, strerror(errno)); res = proxy_db_close(p, dbh); ck_assert_msg(res == 0, "Failed to close database: %s", strerror(errno)); flags &= ~PROXY_DB_OPEN_FL_VACUUM; } flags &= ~PROXY_DB_OPEN_FL_INTEGRITY_CHECK; mark_point(); schema_version = 76; flags |= PROXY_DB_OPEN_FL_SCHEMA_VERSION_CHECK|PROXY_DB_OPEN_FL_ERROR_ON_SCHEMA_VERSION_SKEW; dbh = proxy_db_open_with_version(p, table_path, schema_name, schema_version, flags); ck_assert_msg(dbh == NULL, "Opened table with version skew unexpectedly"); ck_assert_msg(errno == EPERM, "Expected EPERM (%d), got '%s' (%d)", EPERM, strerror(errno), errno); mark_point(); flags &= ~PROXY_DB_OPEN_FL_ERROR_ON_SCHEMA_VERSION_SKEW; dbh = proxy_db_open_with_version(p, table_path, schema_name, schema_version, flags); ck_assert_msg(dbh != NULL, "Failed to open table '%s', schema '%s', version %u: %s", table_path, schema_name, schema_version, strerror(errno)); res = proxy_db_close(p, dbh); ck_assert_msg(res == 0, "Failed to close database: %s", strerror(errno)); mark_point(); schema_version = 76; dbh = proxy_db_open_with_version(p, table_path, schema_name, schema_version, flags); ck_assert_msg(dbh != NULL, "Failed to open table '%s', schema '%s', version %u: %s", table_path, schema_name, schema_version, strerror(errno)); res = proxy_db_close(p, dbh); ck_assert_msg(res == 0, "Failed to close databas: %s", strerror(errno)); mark_point(); schema_version = 99; dbh = proxy_db_open_with_version(p, table_path, schema_name, schema_version, flags); ck_assert_msg(dbh != NULL, "Failed to open table '%s', schema '%s', version %u: %s", table_path, schema_name, schema_version, strerror(errno)); res = proxy_db_close(p, dbh); ck_assert_msg(res == 0, "Failed to close database: %s", strerror(errno)); (void) unlink(db_test_table); } END_TEST START_TEST (db_exec_stmt_test) { int res; const char *table_path, *schema_name, *stmt, *errstr; struct proxy_dbh *dbh; res = proxy_db_exec_stmt(NULL, NULL, NULL, NULL); ck_assert_msg(res < 0, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_db_exec_stmt(p, NULL, NULL, NULL); ck_assert_msg(res < 0, "Failed to handle null dbh"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); (void) unlink(db_test_table); table_path = db_test_table; schema_name = "proxy_test"; dbh = proxy_db_open(p, table_path, schema_name); ck_assert_msg(dbh != NULL, "Failed to open table '%s': %s", table_path, strerror(errno)); res = proxy_db_exec_stmt(p, dbh, NULL, NULL); ck_assert_msg(res < 0, "Failed to handle null statement"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); stmt = "SELECT COUNT(*) FROM foo;"; errstr = NULL; res = proxy_db_exec_stmt(p, dbh, stmt, &errstr); ck_assert_msg(res < 0, "Failed to execute statement '%s'", stmt); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_db_close(p, dbh); ck_assert_msg(res == 0, "Failed to close database: %s", strerror(errno)); (void) unlink(db_test_table); } END_TEST static int create_table(pool *stmt_pool, struct proxy_dbh *dbh, const char *table_name) { int res; const char *stmt, *errstr = NULL; stmt = pstrcat(stmt_pool, "CREATE TABLE ", table_name, " (id INTEGER, name TEXT);", NULL); res = proxy_db_exec_stmt(stmt_pool, dbh, stmt, &errstr); return res; } START_TEST (db_prepare_stmt_test) { int res; const char *table_path, *schema_name, *stmt; struct proxy_dbh *dbh; res = proxy_db_prepare_stmt(NULL, NULL, NULL); ck_assert_msg(res < 0, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_db_prepare_stmt(p, NULL, NULL); ck_assert_msg(res < 0, "Failed to handle null dbh"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); (void) unlink(db_test_table); table_path = db_test_table; schema_name = "proxy_test"; dbh = proxy_db_open(p, table_path, schema_name); ck_assert_msg(dbh != NULL, "Failed to open table '%s': %s", table_path, strerror(errno)); res = proxy_db_prepare_stmt(p, dbh, NULL); ck_assert_msg(res < 0, "Failed to handle null statement"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); stmt = "foo bar baz?"; res = proxy_db_prepare_stmt(p, dbh, stmt); ck_assert_msg(res < 0, "Prepared invalid statement '%s' unexpectedly", stmt); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = create_table(p, dbh, "foo"); ck_assert_msg(res == 0, "Failed to create table 'foo': %s", strerror(errno)); stmt = "SELECT COUNT(*) FROM foo;"; res = proxy_db_prepare_stmt(p, dbh, stmt); ck_assert_msg(res == 0, "Failed to prepare statement '%s': %s", stmt, strerror(errno)); res = proxy_db_prepare_stmt(p, dbh, stmt); ck_assert_msg(res == 0, "Failed to prepare statement '%s': %s", stmt, strerror(errno)); res = create_table(p, dbh, "bar"); ck_assert_msg(res == 0, "Failed to create table 'bar': %s", strerror(errno)); stmt = "SELECT COUNT(*) FROM bar;"; res = proxy_db_prepare_stmt(p, dbh, stmt); ck_assert_msg(res == 0, "Failed to prepare statement '%s': %s", stmt, strerror(errno)); res = proxy_db_close(p, dbh); ck_assert_msg(res == 0, "Failed to close database: %s", strerror(errno)); (void) unlink(db_test_table); } END_TEST START_TEST (db_finish_stmt_test) { int res; const char *table_path, *schema_name, *stmt; struct proxy_dbh *dbh; res = proxy_db_finish_stmt(NULL, NULL, NULL); ck_assert_msg(res < 0, "Failed to handle null arguments"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_db_finish_stmt(p, NULL, NULL); ck_assert_msg(res < 0, "Failed to handle null dbh"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); (void) unlink(db_test_table); table_path = db_test_table; schema_name = "proxy_test"; dbh = proxy_db_open(p, table_path, schema_name); ck_assert_msg(dbh != NULL, "Failed to open table '%s': %s", table_path, strerror(errno)); res = proxy_db_finish_stmt(p, dbh, NULL); ck_assert_msg(res < 0, "Failed to handle null statement"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); stmt = "SELECT COUNT(*) FROM foo"; res = proxy_db_finish_stmt(p, dbh, stmt); ck_assert_msg(res < 0, "Failed to handle unprepared statement"); ck_assert_msg(errno == ENOENT, "Expected ENOENT (%d), got '%s' (%d)", ENOENT, strerror(errno), errno); res = create_table(p, dbh, "foo"); ck_assert_msg(res == 0, "Failed to create table 'foo': %s", strerror(errno)); res = proxy_db_prepare_stmt(p, dbh, stmt); ck_assert_msg(res == 0, "Failed to prepare statement '%s': %s", stmt, strerror(errno)); res = proxy_db_finish_stmt(p, dbh, stmt); ck_assert_msg(res == 0, "Failed to finish statement '%s': %s", stmt, strerror(errno)); res = proxy_db_finish_stmt(p, dbh, stmt); ck_assert_msg(res < 0, "Failed to handle unprepared statement"); ck_assert_msg(errno == ENOENT, "Expected ENOENT (%d), got '%s' (%d)", ENOENT, strerror(errno), errno); res = proxy_db_close(p, dbh); ck_assert_msg(res == 0, "Failed to close database: %s", strerror(errno)); (void) unlink(db_test_table); } END_TEST START_TEST (db_bind_stmt_test) { int res; const char *table_path, *schema_name, *stmt; struct proxy_dbh *dbh; int idx, int_val; long long_val; char *text_val; void *blob_val; res = proxy_db_bind_stmt(NULL, NULL, NULL, -1, -1, NULL, -1); ck_assert_msg(res < 0, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_db_bind_stmt(p, NULL, NULL, -1, -1, NULL, -1); ck_assert_msg(res < 0, "Failed to handle null dbh"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); (void) unlink(db_test_table); table_path = db_test_table; schema_name = "proxy_test"; dbh = proxy_db_open(p, table_path, schema_name); ck_assert_msg(dbh != NULL, "Failed to open table '%s': %s", table_path, strerror(errno)); res = proxy_db_bind_stmt(p, dbh, NULL, -1, -1, NULL, -1); ck_assert_msg(res < 0, "Failed to handle null statement"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); stmt = "SELECT COUNT(*) FROM table"; idx = -1; res = proxy_db_bind_stmt(p, dbh, stmt, idx, PROXY_DB_BIND_TYPE_INT, NULL, -1); ck_assert_msg(res < 0, "Failed to handle invalid index %d", idx); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); idx = 1; res = proxy_db_bind_stmt(p, dbh, stmt, idx, PROXY_DB_BIND_TYPE_INT, NULL, -1); ck_assert_msg(res < 0, "Failed to handle unprepared statement"); ck_assert_msg(errno == ENOENT, "Expected ENOENT (%d), got '%s' (%d)", ENOENT, strerror(errno), errno); res = create_table(p, dbh, "foo"); ck_assert_msg(res == 0, "Failed to create table 'foo': %s", strerror(errno)); stmt = "SELECT COUNT(*) FROM foo;"; res = proxy_db_prepare_stmt(p, dbh, stmt); ck_assert_msg(res == 0, "Failed to prepare statement '%s': %s", stmt, strerror(errno)); res = proxy_db_bind_stmt(p, dbh, stmt, idx, PROXY_DB_BIND_TYPE_INT, NULL, -1); ck_assert_msg(res < 0, "Failed to handle missing INT value"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); int_val = 7; res = proxy_db_bind_stmt(p, dbh, stmt, idx, PROXY_DB_BIND_TYPE_INT, &int_val, -1); ck_assert_msg(res < 0, "Failed to handle invalid index value"); ck_assert_msg(errno == EPERM, "Expected EPERM (%d), got '%s' (%d)", EPERM, strerror(errno), errno); res = proxy_db_bind_stmt(p, dbh, stmt, idx, PROXY_DB_BIND_TYPE_LONG, NULL, -1); ck_assert_msg(res < 0, "Failed to handle missing LONG value"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); long_val = 7; res = proxy_db_bind_stmt(p, dbh, stmt, idx, PROXY_DB_BIND_TYPE_LONG, &long_val, -1); ck_assert_msg(res < 0, "Failed to handle invalid index value"); ck_assert_msg(errno == EPERM, "Expected EPERM (%d), got '%s' (%d)", EPERM, strerror(errno), errno); res = proxy_db_bind_stmt(p, dbh, stmt, idx, PROXY_DB_BIND_TYPE_TEXT, NULL, 0); ck_assert_msg(res < 0, "Failed to handle missing TEXT value"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); text_val = "testing"; res = proxy_db_bind_stmt(p, dbh, stmt, idx, PROXY_DB_BIND_TYPE_TEXT, text_val, 0); ck_assert_msg(res < 0, "Failed to handle invalid index value"); ck_assert_msg(errno == EPERM, "Expected EPERM (%d), got '%s' (%d)", EPERM, strerror(errno), errno); res = proxy_db_bind_stmt(p, dbh, stmt, idx, PROXY_DB_BIND_TYPE_BLOB, NULL, -1); ck_assert_msg(res < 0, "Failed to handle missing BLOB value"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); blob_val = "testing"; res = proxy_db_bind_stmt(p, dbh, stmt, idx, PROXY_DB_BIND_TYPE_BLOB, blob_val, strlen(blob_val)); ck_assert_msg(res < 0, "Failed to handle invalid index value"); ck_assert_msg(errno == EPERM, "Expected EPERM (%d), got '%s' (%d)", EPERM, strerror(errno), errno); res = proxy_db_bind_stmt(p, dbh, stmt, idx, PROXY_DB_BIND_TYPE_NULL, NULL, 0); ck_assert_msg(res < 0, "Failed to handle invalid NULL value"); ck_assert_msg(errno == EPERM, "Expected EPERM (%d), got '%s' (%d)", EPERM, strerror(errno), errno); stmt = "SELECT COUNT(*) FROM foo WHERE id = ?;"; res = proxy_db_prepare_stmt(p, dbh, stmt); ck_assert_msg(res == 0, "Failed to prepare statement '%s': %s", stmt, strerror(errno)); int_val = 7; res = proxy_db_bind_stmt(p, dbh, stmt, idx, PROXY_DB_BIND_TYPE_INT, &int_val, -1); ck_assert_msg(res == 0, "Failed to bind INT value: %s", strerror(errno)); res = proxy_db_close(p, dbh); ck_assert_msg(res == 0, "Failed to close database: %s", strerror(errno)); (void) unlink(db_test_table); } END_TEST START_TEST (db_exec_prepared_stmt_test) { int res; array_header *results; const char *table_path, *schema_name, *stmt, *errstr = NULL; struct proxy_dbh *dbh; results = proxy_db_exec_prepared_stmt(NULL, NULL, NULL, NULL); ck_assert_msg(results == NULL, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); results = proxy_db_exec_prepared_stmt(p, NULL, NULL, NULL); ck_assert_msg(results == NULL, "Failed to handle null dbh"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); (void) unlink(db_test_table); table_path = db_test_table; schema_name = "proxy_test"; dbh = proxy_db_open(p, table_path, schema_name); ck_assert_msg(dbh != NULL, "Failed to open table '%s': %s", table_path, strerror(errno)); results = proxy_db_exec_prepared_stmt(p, dbh, NULL, NULL); ck_assert_msg(results == NULL, "Failed to handle null statement"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); stmt = "SELECT COUNT(*) FROM foo;"; results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr); ck_assert_msg(results == NULL, "Failed to handle unprepared statement"); ck_assert_msg(errno == ENOENT, "Expected ENOENT (%d), got '%s' (%d)", ENOENT, strerror(errno), errno); res = create_table(p, dbh, "foo"); ck_assert_msg(res == 0, "Failed to create table 'foo': %s", strerror(errno)); res = proxy_db_prepare_stmt(p, dbh, stmt); ck_assert_msg(res == 0, "Failed to prepare statement '%s': %s", stmt, strerror(errno)); results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr); ck_assert_msg(results != NULL, "Failed to execute prepared statement '%s': %s (%s)", stmt, errstr, strerror(errno)); res = proxy_db_close(p, dbh); ck_assert_msg(res == 0, "Failed to close database: %s", strerror(errno)); (void) unlink(db_test_table); } END_TEST START_TEST (db_reindex_test) { int res; const char *table_path, *schema_name, *index_name, *errstr = NULL; struct proxy_dbh *dbh; res = proxy_db_reindex(NULL, NULL, NULL, NULL); ck_assert_msg(res < 0, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_db_reindex(p, NULL, NULL, NULL); ck_assert_msg(res < 0, "Failed to handle null dbh"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); (void) unlink(db_test_table); table_path = db_test_table; schema_name = "proxy_test"; dbh = proxy_db_open(p, table_path, schema_name); ck_assert_msg(dbh != NULL, "Failed to open table '%s': %s", table_path, strerror(errno)); res = proxy_db_reindex(p, dbh, NULL, NULL); ck_assert_msg(res < 0, "Failed to handle null index name"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); index_name = "test_idx"; res = proxy_db_reindex(p, dbh, index_name, &errstr); ck_assert_msg(res < 0, "Failed to handle invalid index"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); ck_assert_msg(errstr != NULL, "Failed to provide error string"); res = proxy_db_close(p, dbh); ck_assert_msg(res == 0, "Failed to close database: %s", strerror(errno)); (void) unlink(db_test_table); } END_TEST Suite *tests_get_db_suite(void) { Suite *suite; TCase *testcase; suite = suite_create("db"); testcase = tcase_create("base"); tcase_add_checked_fixture(testcase, set_up, tear_down); tcase_add_test(testcase, db_close_test); tcase_add_test(testcase, db_open_test); tcase_add_test(testcase, db_open_with_version_test); tcase_add_test(testcase, db_exec_stmt_test); tcase_add_test(testcase, db_prepare_stmt_test); tcase_add_test(testcase, db_finish_stmt_test); tcase_add_test(testcase, db_bind_stmt_test); tcase_add_test(testcase, db_exec_prepared_stmt_test); tcase_add_test(testcase, db_reindex_test); suite_add_tcase(suite, testcase); return suite; } proftpd-mod_proxy-0.9.5/t/api/dns.c000066400000000000000000000201741475737016700172120ustar00rootroot00000000000000/* * ProFTPD - mod_proxy testsuite * Copyright (c) 2020-2022 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ /* DNS API tests */ #include "tests.h" static pool *p = NULL; static void set_up(void) { if (p == NULL) { p = permanent_pool = make_sub_pool(NULL); } if (getenv("TEST_VERBOSE") != NULL) { pr_trace_set_levels("proxy.dns", 1, 20); } } static void tear_down(void) { if (getenv("TEST_VERBOSE") != NULL) { pr_trace_set_levels("proxy.dns", 0, 0); } if (p != NULL) { destroy_pool(p); p = permanent_pool = NULL; } } START_TEST (dns_resolve_einval_test) { int res; const char *name = NULL; proxy_dns_type_e dns_type = PROXY_DNS_UNKNOWN; array_header *resp = NULL; mark_point(); res = proxy_dns_resolve(NULL, NULL, dns_type, NULL, NULL); ck_assert_msg(res < 0, "Failed to handle null pool argument"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_dns_resolve(p, NULL, dns_type, NULL, NULL); ck_assert_msg(res < 0, "Failed to handle null name argument"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); mark_point(); name = "www.google.com"; res = proxy_dns_resolve(p, name, dns_type, NULL, NULL); ck_assert_msg(res < 0, "Failed to handle null resp argument"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_dns_resolve(p, name, dns_type, &resp, NULL); ck_assert_msg(res < 0, "Failed to handle UNKNOWN type argument"); ck_assert_msg(errno == EPERM, "Expected EPERM (%d), got %s (%d)", EPERM, strerror(errno), errno); } END_TEST START_TEST (dns_resolve_bad_response_test) { int res; const char *name; proxy_dns_type_e dns_type; array_header *resp = NULL; /* SRV */ dns_type = PROXY_DNS_SRV; mark_point(); name = "foobarbaz"; res = proxy_dns_resolve(p, name, dns_type, &resp, NULL); ck_assert_msg(res < 0, "Failed to handle no SRV records for '%s'", name); ck_assert_msg(errno == ENOENT, "Expected ENOENT (%d), got %s (%d)", ENOENT, strerror(errno), errno); ck_assert_msg(resp == NULL, "Expected null responses"); mark_point(); name = " "; res = proxy_dns_resolve(p, name, dns_type, &resp, NULL); ck_assert_msg(res < 0, "Failed to handle no SRV records for '%s'", name); ck_assert_msg(errno == ENOENT, "Expected ENOENT (%d), got %s (%d)", ENOENT, strerror(errno), errno); ck_assert_msg(resp == NULL, "Expected null responses"); mark_point(); name = "."; res = proxy_dns_resolve(p, name, dns_type, &resp, NULL); ck_assert_msg(res < 0, "Failed to handle no SRV records for '%s'", name); ck_assert_msg(errno == ENOENT || errno == EPERM, "Expected EPERM (%d) or ENOENT (%d), got %s (%d)", EPERM, ENOENT, strerror(errno), errno); ck_assert_msg(resp == NULL, "Expected null responses"); /* TXT */ dns_type = PROXY_DNS_TXT; mark_point(); name = "foobarbaz"; res = proxy_dns_resolve(p, name, dns_type, &resp, NULL); ck_assert_msg(res < 0, "Failed to handle no TXT records for '%s'", name); ck_assert_msg(errno == ENOENT, "Expected ENOENT (%d), got %s (%d)", ENOENT, strerror(errno), errno); ck_assert_msg(resp == NULL, "Expected null responses"); mark_point(); name = " "; res = proxy_dns_resolve(p, name, dns_type, &resp, NULL); ck_assert_msg(res < 0, "Failed to handle no TXT records for '%s'", name); ck_assert_msg(errno == ENOENT, "Expected ENOENT (%d), got %s (%d)", ENOENT, strerror(errno), errno); ck_assert_msg(resp == NULL, "Expected null responses"); mark_point(); name = "."; res = proxy_dns_resolve(p, name, dns_type, &resp, NULL); ck_assert_msg(res < 0, "Failed to handle no TXT records for '%s'", name); ck_assert_msg(errno == ENOENT, "Expected ENOENT (%d), got %s (%d)", ENOENT, strerror(errno), errno); ck_assert_msg(resp == NULL, "Expected null responses"); mark_point(); name = "+"; res = proxy_dns_resolve(p, name, dns_type, &resp, NULL); ck_assert_msg(res < 0, "Failed to handle no TXT records for '%s'", name); ck_assert_msg(errno == ENOENT, "Expected ENOENT (%d), got %s (%d)", ENOENT, strerror(errno), errno); ck_assert_msg(resp == NULL, "Expected null responses"); } END_TEST START_TEST (dns_resolve_type_srv_test) { int res; const char *name = NULL; proxy_dns_type_e dns_type = PROXY_DNS_SRV; array_header *resp = NULL; uint32_t ttl = 0; mark_point(); name = "_ftps._tcp.castaglia.org"; res = proxy_dns_resolve(p, name, dns_type, &resp, &ttl); ck_assert_msg(res < 0, "Failed to handle no SRV records for '%s'", name); ck_assert_msg(errno == ENOENT, "Expected ENOENT (%d), got %s (%d)", ENOENT, strerror(errno), errno); ck_assert_msg(resp == NULL, "Expected null responses"); mark_point(); name = "_imap._tcp.gmail.com"; res = proxy_dns_resolve(p, name, dns_type, &resp, &ttl); ck_assert_msg(res < 0, "Failed to handle explicit 'no service' for '%s'", name); ck_assert_msg(errno == ENOENT, "Expected ENOENT (%d), got %s (%d)", ENOENT, strerror(errno), errno); ck_assert_msg(resp == NULL, "Expected null responses"); mark_point(); name = "_imaps._tcp.gmail.com"; res = proxy_dns_resolve(p, name, dns_type, &resp, &ttl); ck_assert_msg(res > 0, "Failed to resolve SRV records for '%s': %s", name, strerror(errno)); ck_assert_msg(resp != NULL, "Expected non-null responses"); mark_point(); name = "_ldap._tcp.ru.ac.za"; res = proxy_dns_resolve(p, name, dns_type, &resp, &ttl); /* This particular DNS record may not always be there... */ if (res < 0 && errno != ENOENT) { ck_assert_msg(res > 0, "Failed to resolve SRV records for '%s': %s", name, strerror(errno)); ck_assert_msg(resp != NULL, "Expected non-null responses"); } } END_TEST START_TEST (dns_resolve_type_txt_test) { int res; const char *name = NULL; proxy_dns_type_e dns_type = PROXY_DNS_TXT; array_header *resp = NULL; uint32_t ttl = 0; /* These sometimes fail unexpected for CI builds. */ mark_point(); name = "google.com"; res = proxy_dns_resolve(p, name, dns_type, &resp, &ttl); if (getenv("CI") == NULL) { ck_assert_msg(res > 0, "Failed to resolve TXT records for '%s': %s", name, strerror(errno)); ck_assert_msg(resp != NULL, "Expected non-null responses"); } mark_point(); name = "amazon.com"; res = proxy_dns_resolve(p, name, dns_type, &resp, &ttl); if (getenv("CI") == NULL) { ck_assert_msg(res > 0, "Failed to resolve TXT records for '%s': %s", name, strerror(errno)); ck_assert_msg(resp != NULL, "Expected non-null responses"); } } END_TEST Suite *tests_get_dns_suite(void) { Suite *suite; TCase *testcase; suite = suite_create("dns"); testcase = tcase_create("base"); tcase_add_checked_fixture(testcase, set_up, tear_down); tcase_add_test(testcase, dns_resolve_einval_test); tcase_add_test(testcase, dns_resolve_bad_response_test); tcase_add_test(testcase, dns_resolve_type_srv_test); tcase_add_test(testcase, dns_resolve_type_txt_test); /* Allow a longer timeout on these tests, as they depend on external DNS * latency. */ tcase_set_timeout(testcase, 30); suite_add_tcase(suite, testcase); return suite; } proftpd-mod_proxy-0.9.5/t/api/forward.c000066400000000000000000000521711475737016700200740ustar00rootroot00000000000000/* * ProFTPD - mod_proxy testsuite * Copyright (c) 2016-2022 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ /* Forward-proxy API tests */ #include "tests.h" extern xaset_t *server_list; static pool *p = NULL; static const char *test_dir = "/tmp/mod_proxy-test-forward"; static void create_main_server(void) { server_rec *s; s = pr_parser_server_ctxt_open("127.0.0.1"); s->ServerName = "Test Server"; main_server = s; } static int create_test_dir(void) { int res; mode_t perms; perms = 0770; res = mkdir(test_dir, perms); ck_assert_msg(res == 0, "Failed to create tmp directory '%s': %s", test_dir, strerror(errno)); res = chmod(test_dir, perms); ck_assert_msg(res == 0, "Failed to set perms %04o on directory '%s': %s", perms, test_dir, strerror(errno)); return 0; } static void test_cleanup(pool *cleanup_pool) { (void) tests_rmpath(cleanup_pool, test_dir); } static void set_up(void) { if (p == NULL) { p = permanent_pool = proxy_pool = session.pool = make_sub_pool(NULL); server_list = NULL; session.c = NULL; session.notes = NULL; } test_cleanup(p); init_config(); init_fs(); init_netaddr(); init_netio(); init_inet(); server_list = xaset_create(p, NULL); pr_parser_prepare(p, &server_list); create_main_server(); (void) create_test_dir(); proxy_db_init(p); if (getenv("TEST_VERBOSE") != NULL) { pr_trace_set_levels("netio", 1, 20); pr_trace_set_levels("proxy.db", 1, 20); pr_trace_set_levels("proxy.forward", 1, 20); pr_trace_set_levels("proxy.tls", 1, 20); pr_trace_set_levels("proxy.uri", 1, 20); pr_trace_set_levels("proxy.ftp.ctrl", 1, 20); pr_trace_set_levels("proxy.ftp.sess", 1, 20); } } static void tear_down(void) { if (getenv("TEST_VERBOSE") != NULL) { pr_trace_set_levels("netio", 0, 0); pr_trace_set_levels("proxy.db", 0, 0); pr_trace_set_levels("proxy.forward", 0, 0); pr_trace_set_levels("proxy.tls", 0, 0); pr_trace_set_levels("proxy.uri", 0, 0); pr_trace_set_levels("proxy.ftp.ctrl", 0, 0); pr_trace_set_levels("proxy.ftp.sess", 0, 0); } proxy_db_free(); pr_parser_cleanup(); pr_inet_clear(); test_cleanup(p); if (p) { destroy_pool(p); p = permanent_pool = proxy_pool = session.pool = NULL; main_server = NULL; server_list = NULL; session.c = NULL; session.notes = NULL; } } START_TEST (forward_free_test) { int res; res = proxy_forward_free(NULL); ck_assert_msg(res == 0, "Failed to free Forward API resources: %s", strerror(errno)); } END_TEST START_TEST (forward_init_test) { int res; res = proxy_forward_init(NULL, NULL); ck_assert_msg(res == 0, "Failed to init Forward API resources: %s", strerror(errno)); } END_TEST START_TEST (forward_sess_free_test) { int res; res = proxy_forward_sess_free(NULL, NULL); ck_assert_msg(res == 0, "Failed to free Forward API session resources: %s", strerror(errno)); } END_TEST START_TEST (forward_sess_init_test) { int res; session.c = pr_inet_create_conn(p, -1, NULL, INPORT_ANY, FALSE); ck_assert_msg(session.c != NULL, "Failed to open session control conn: %s", strerror(errno)); session.c->local_addr = session.c->remote_addr = pr_netaddr_get_addr(p, "127.0.0.1", NULL); ck_assert_msg(session.c->remote_addr != NULL, "Failed to get address: %s", strerror(errno)); mark_point(); res = proxy_forward_sess_init(p, test_dir, NULL); ck_assert_msg(res < 0, "Initialized Forward API session resources unexpectedly"); ck_assert_msg(errno == EPERM, "Expected EPERM (%d), got '%s' (%d)", EPERM, strerror(errno), errno); /* Make the connections look like they're from an RFC1918 address. */ session.c->local_addr = session.c->remote_addr = pr_netaddr_get_addr(p, "192.168.0.1", NULL); ck_assert_msg(session.c->remote_addr != NULL, "Failed to get address: %s", strerror(errno)); mark_point(); res = proxy_forward_sess_init(p, test_dir, NULL); ck_assert_msg(res == 0, "Failed to init Forward API session resources: %s", strerror(errno)); res = proxy_forward_sess_free(p, NULL); ck_assert_msg(res == 0, "Failed to free Forward API session resources: %s", strerror(errno)); } END_TEST START_TEST (forward_get_method_test) { int res; const char *method; res = proxy_forward_get_method(NULL); ck_assert_msg(res < 0, "Failed to handle null argument"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); method = "foo"; res = proxy_forward_get_method(method); ck_assert_msg(res < 0, "Failed to handle unsupported method '%s'", method); ck_assert_msg(errno == ENOENT, "Expected ENOENT (%d), got '%s' (%d)", ENOENT, strerror(errno), errno); method = "proxyuser,user@host"; res = proxy_forward_get_method(method); ck_assert_msg(res == PROXY_FORWARD_METHOD_USER_WITH_PROXY_AUTH, "Failed to handle method '%s'", method); method = "user@host"; res = proxy_forward_get_method(method); ck_assert_msg(res == PROXY_FORWARD_METHOD_USER_NO_PROXY_AUTH, "Failed to handle method '%s'", method); method = "proxyuser@host,user"; res = proxy_forward_get_method(method); ck_assert_msg(res == PROXY_FORWARD_METHOD_PROXY_USER_WITH_PROXY_AUTH, "Failed to handle method '%s'", method); method = "user@sni"; res = proxy_forward_get_method(method); ck_assert_msg(res == PROXY_FORWARD_METHOD_USER_SNI_NO_PROXY_AUTH, "Failed to handle method '%s'", method); } END_TEST START_TEST (forward_use_proxy_auth_test) { int res; res = proxy_forward_use_proxy_auth(); ck_assert_msg(res == TRUE, "Expected true, got %d", res); } END_TEST START_TEST (forward_have_authenticated_test) { int res; cmd_rec *cmd = NULL; res = proxy_forward_have_authenticated(cmd); ck_assert_msg(res == FALSE, "Expected false, got %d", res); } END_TEST static int forward_sess_init(int method_id) { session.c = pr_inet_create_conn(p, -1, NULL, INPORT_ANY, FALSE); if (session.c == NULL) { return -1; } /* Make the connections look like they're from an RFC1918 address. */ session.c->local_addr = session.c->remote_addr = pr_netaddr_get_addr(p, "192.168.0.1", NULL); if (session.c->remote_addr == NULL) { return -1; } pr_netaddr_set_port((pr_netaddr_t *) session.c->local_addr, htons(7777)); if (method_id > 0) { config_rec *c; c = add_config_param("ProxyForwardMethod", 1, NULL); c->argv[0] = palloc(c->pool, sizeof(int)); *((int *) c->argv[0]) = method_id; } return proxy_forward_sess_init(p, test_dir, NULL); } static struct proxy_session *forward_get_proxy_sess(void) { struct proxy_session *proxy_sess; proxy_sess = (struct proxy_session *) proxy_session_alloc(p); proxy_sess->src_addr = pr_netaddr_get_addr(p, "127.0.0.1", NULL); return proxy_sess; } START_TEST (forward_handle_user_noproxyauth_test) { int res, successful = FALSE, block_responses = FALSE; cmd_rec *cmd; struct proxy_session *proxy_sess; res = forward_sess_init(PROXY_FORWARD_METHOD_USER_NO_PROXY_AUTH); ck_assert_msg(res == 0, "Failed to init Forward API session resources: %s", strerror(errno)); proxy_sess = forward_get_proxy_sess(); /* No destination host in USER command. */ cmd = pr_cmd_alloc(p, 2, "USER", "test"); cmd->arg = pstrdup(p, "test"); mark_point(); res = proxy_forward_handle_user(cmd, proxy_sess, &successful, &block_responses); ck_assert_msg(res < 0, "Handled USER command unexpectedly"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); /* Invalid host (no port) in USER command. */ cmd = pr_cmd_alloc(p, 2, "USER", "test@host"); cmd->arg = pstrdup(p, "test@host"); mark_point(); res = proxy_forward_handle_user(cmd, proxy_sess, &successful, &block_responses); ck_assert_msg(res < 0, "Handled USER command unexpectedly"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); /* Valid host (no port) in USER command. */ cmd = pr_cmd_alloc(p, 2, "USER", "test@127.0.0.1"); cmd->arg = pstrdup(p, "test@127.0.0.1"); mark_point(); res = proxy_forward_handle_user(cmd, proxy_sess, &successful, &block_responses); ck_assert_msg(res == 1, "Failed to handle USER command: %s", strerror(errno)); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); cmd = pr_cmd_alloc(p, 2, "USER", "test@192.168.0.1"); cmd->arg = pstrdup(p, "test@192.168.0.1:7777"); mark_point(); res = proxy_forward_handle_user(cmd, proxy_sess, &successful, &block_responses); ck_assert_msg(res == 1, "Failed to handle USER command: %s", strerror(errno)); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); /* Destination host (WITH bad port syntax) in USER command. */ cmd = pr_cmd_alloc(p, 2, "USER", "test@host:foo"); cmd->arg = pstrdup(p, "test@host:foo"); mark_point(); res = proxy_forward_handle_user(cmd, proxy_sess, &successful, &block_responses); ck_assert_msg(res < 0, "Handled USER command unexpectedly"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); /* Destination host (WITH invalid port) in USER command. */ cmd = pr_cmd_alloc(p, 2, "USER", "test@host:70000"); cmd->arg = pstrdup(p, "test@host:70000"); mark_point(); res = proxy_forward_handle_user(cmd, proxy_sess, &successful, &block_responses); ck_assert_msg(res < 0, "Handled USER command unexpectedly"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); /* Destination host (WITH port) in USER command. */ cmd = pr_cmd_alloc(p, 2, "USER", "test@127.0.0.1:2121"); cmd->arg = pstrdup(p, "test@127.0.0.1:2121"); mark_point(); res = proxy_forward_handle_user(cmd, proxy_sess, &successful, &block_responses); ck_assert_msg(res == 1, "Failed to handle USER command: %s", strerror(errno)); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); res = proxy_forward_sess_free(p, NULL); ck_assert_msg(res == 0, "Failed to free Forward API session resources: %s", strerror(errno)); proxy_session_free(p, proxy_sess); } END_TEST START_TEST (forward_handle_user_userwithproxyauth_test) { int res, successful = FALSE, block_responses = FALSE; cmd_rec *cmd; struct proxy_session *proxy_sess; res = forward_sess_init(PROXY_FORWARD_METHOD_USER_WITH_PROXY_AUTH); ck_assert_msg(res == 0, "Failed to init Forward API session resources: %s", strerror(errno)); proxy_sess = forward_get_proxy_sess(); cmd = pr_cmd_alloc(p, 2, "USER", "test@127.0.0.1"); cmd->arg = pstrdup(p, "test@127.0.0.1"); mark_point(); res = proxy_forward_handle_user(cmd, proxy_sess, &successful, &block_responses); ck_assert_msg(res == 0, "Failed to handle USER command: %s", strerror(errno)); ck_assert_msg(block_responses == FALSE, "Expected false, got %d", block_responses); proxy_sess_state |= PROXY_SESS_STATE_PROXY_AUTHENTICATED; mark_point(); res = proxy_forward_handle_user(cmd, proxy_sess, &successful, &block_responses); ck_assert_msg(res == 1, "Failed to handle USER command: %s", strerror(errno)); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); proxy_sess_state &= ~PROXY_SESS_STATE_PROXY_AUTHENTICATED; res = proxy_forward_sess_free(p, NULL); ck_assert_msg(res == 0, "Failed to free Forward API session resources: %s", strerror(errno)); proxy_session_free(p, proxy_sess); } END_TEST START_TEST (forward_handle_user_proxyuserwithproxyauth_test) { int res, successful = FALSE, block_responses = FALSE; cmd_rec *cmd; struct proxy_session *proxy_sess; res = forward_sess_init(PROXY_FORWARD_METHOD_PROXY_USER_WITH_PROXY_AUTH); ck_assert_msg(res == 0, "Failed to init Forward API session resources: %s", strerror(errno)); proxy_sess = forward_get_proxy_sess(); cmd = pr_cmd_alloc(p, 2, "USER", "test@127.0.0.1"); cmd->arg = pstrdup(p, "test@127.0.0.1"); mark_point(); res = proxy_forward_handle_user(cmd, proxy_sess, &successful, &block_responses); ck_assert_msg(res == 0, "Failed to handle USER command: %s", strerror(errno)); ck_assert_msg(block_responses == FALSE, "Expected false, got %d", block_responses); proxy_sess_state |= PROXY_SESS_STATE_PROXY_AUTHENTICATED; mark_point(); res = proxy_forward_handle_user(cmd, proxy_sess, &successful, &block_responses); ck_assert_msg(res == 1, "Failed to handle USER command: %s", strerror(errno)); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); proxy_sess_state &= ~PROXY_SESS_STATE_PROXY_AUTHENTICATED; res = proxy_forward_sess_free(p, NULL); ck_assert_msg(res == 0, "Failed to free Forward API session resources: %s", strerror(errno)); proxy_session_free(p, proxy_sess); } END_TEST START_TEST (forward_handle_pass_noproxyauth_test) { int res, successful = FALSE, block_responses = FALSE; cmd_rec *cmd; #ifdef PR_USE_OPENSSL config_rec *c; #endif /* PR_USE_OPENSSL */ struct proxy_session *proxy_sess; /* Skip this test for CI builds, for now. It fails unexpectedly. */ if (getenv("CI") != NULL || getenv("TRAVIS") != NULL) { return; } res = forward_sess_init(PROXY_FORWARD_METHOD_USER_NO_PROXY_AUTH); ck_assert_msg(res == 0, "Failed to init Forward API session resources: %s", strerror(errno)); proxy_sess = forward_get_proxy_sess(); /* No destination host in PASS command. */ cmd = pr_cmd_alloc(p, 2, "PASS", "test"); cmd->arg = pstrdup(p, "test"); mark_point(); res = proxy_forward_handle_pass(cmd, proxy_sess, &successful, &block_responses); ck_assert_msg(res < 0, "Handled PASS command unexpectedly"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); /* XXX TODO: Use a file fd for the "backend control conn" fd (/dev/null?) */ /* Valid external host (with port) in USER command. */ cmd = pr_cmd_alloc(p, 2, "USER", "anonymous@ftp.cisco.com:21"); cmd->arg = pstrdup(p, "anonymous@ftp.cisco.com:21"); mark_point(); res = proxy_forward_handle_user(cmd, proxy_sess, &successful, &block_responses); ck_assert_msg(res == 1, "Failed to handle USER command: %s", strerror(errno)); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); if (getenv("CI") == NULL && getenv("TRAVIS") == NULL) { cmd = pr_cmd_alloc(p, 2, "PASS", "ftp@nospam.org"); cmd->arg = pstrdup(p, "ftp@nospam.org"); mark_point(); res = proxy_forward_handle_pass(cmd, proxy_sess, &successful, &block_responses); ck_assert_msg(res == 1, "Failed to handle PASS command: %s", strerror(errno)); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); } #ifdef PR_USE_OPENSSL /* This time, try an FTPS-capable site. */ session.notes = pr_table_alloc(p, 0); pr_table_add(session.notes, "mod_proxy.proxy-session", proxy_sess, sizeof(struct proxy_session)); res = proxy_tls_init(p, test_dir, PROXY_DB_OPEN_FL_SKIP_VACUUM); ck_assert_msg(res == 0, "Failed to init TLS API resources: %s", strerror(errno)); c = add_config_param("ProxyTLSEngine", 1, NULL); c->argv[0] = palloc(c->pool, sizeof(int)); *((int *) c->argv[0]) = PROXY_TLS_ENGINE_AUTO; c = add_config_param("ProxyTLSVerifyServer", 1, NULL); c->argv[0] = palloc(c->pool, sizeof(int)); *((int *) c->argv[0]) = FALSE; c = add_config_param("ProxyTLSOptions", 1, NULL); c->argv[0] = palloc(c->pool, sizeof(unsigned long)); *((unsigned long *) c->argv[0]) = PROXY_TLS_OPT_ENABLE_DIAGS; /* XXX if we want to successfully verify the Cisco cert, we'd need to include * its CA certs as a test resource, and configure it here. */ res = proxy_tls_sess_init(p, proxy_sess, PROXY_DB_OPEN_FL_SKIP_VACUUM); ck_assert_msg(res == 0, "Failed to init TLS API session resources: %s", strerror(errno)); /* Valid external host (with port) in USER command. */ cmd = pr_cmd_alloc(p, 2, "USER", "anonymous@ftp.cisco.com:990"); cmd->arg = pstrdup(p, "anonymous@ftp.cisco.com:990"); if (getenv("CI") == NULL && getenv("TRAVIS") == NULL) { mark_point(); res = proxy_forward_handle_user(cmd, proxy_sess, &successful, &block_responses); /* Once you've performed a TLS handshake with ftp.cisco.com, it does not * accept anonymous logins. Fine. */ ck_assert_msg(res != 1, "Handled USER command unexpectedly"); ck_assert_msg(errno == EPERM, "Expected EPERM (%d), got %s (%d)", EPERM, strerror(errno), errno); } mark_point(); res = proxy_tls_sess_free(p); ck_assert_msg(res == 0, "Failed to free TLS API session resources: %s", strerror(errno)); mark_point(); res = proxy_tls_free(p); ck_assert_msg(res == 0, "Failed to free TLS API resources: %s", strerror(errno)); mark_point(); (void) proxy_db_close(p, NULL); #endif /* PR_USE_OPENSSL */ mark_point(); res = proxy_forward_sess_free(p, NULL); ck_assert_msg(res == 0, "Failed to free Forward API session resources: %s", strerror(errno)); proxy_session_free(p, proxy_sess); } END_TEST START_TEST (forward_handle_pass_userwithproxyauth_test) { int res, successful = FALSE, block_responses = FALSE; cmd_rec *cmd; struct proxy_session *proxy_sess; res = forward_sess_init(PROXY_FORWARD_METHOD_USER_WITH_PROXY_AUTH); ck_assert_msg(res == 0, "Failed to init Forward API session resources: %s", strerror(errno)); proxy_sess = forward_get_proxy_sess(); /* No destination host in PASS command. */ cmd = pr_cmd_alloc(p, 2, "PASS", "test"); cmd->arg = pstrdup(p, "test"); mark_point(); res = proxy_forward_handle_pass(cmd, proxy_sess, &successful, &block_responses); ck_assert_msg(res < 0, "Handled PASS command unexpectedly"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); /* XXX TODO: Use a file fd for the "backend control conn" fd (/dev/null?) */ res = proxy_forward_sess_free(p, NULL); ck_assert_msg(res == 0, "Failed to free Forward API session resources: %s", strerror(errno)); proxy_session_free(p, proxy_sess); } END_TEST START_TEST (forward_handle_pass_proxyuserwithproxyauth_test) { int res, successful = FALSE, block_responses = FALSE; cmd_rec *cmd; struct proxy_session *proxy_sess; res = forward_sess_init(PROXY_FORWARD_METHOD_PROXY_USER_WITH_PROXY_AUTH); ck_assert_msg(res == 0, "Failed to init Forward API session resources: %s", strerror(errno)); proxy_sess = forward_get_proxy_sess(); /* No destination host in PASS command. */ cmd = pr_cmd_alloc(p, 2, "PASS", "test"); cmd->arg = pstrdup(p, "test"); mark_point(); res = proxy_forward_handle_pass(cmd, proxy_sess, &successful, &block_responses); ck_assert_msg(res < 0, "Handled PASS command unexpectedly"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); /* XXX TODO: Use a file fd for the "backend control conn" fd (/dev/null?) */ res = proxy_forward_sess_free(p, NULL); ck_assert_msg(res == 0, "Failed to free Forward API session resources: %s", strerror(errno)); proxy_session_free(p, proxy_sess); } END_TEST Suite *tests_get_forward_suite(void) { Suite *suite; TCase *testcase; suite = suite_create("forward"); testcase = tcase_create("base"); tcase_add_checked_fixture(testcase, set_up, tear_down); tcase_add_test(testcase, forward_free_test); tcase_add_test(testcase, forward_init_test); tcase_add_test(testcase, forward_sess_free_test); tcase_add_test(testcase, forward_sess_init_test); tcase_add_test(testcase, forward_get_method_test); tcase_add_test(testcase, forward_use_proxy_auth_test); tcase_add_test(testcase, forward_have_authenticated_test); tcase_add_test(testcase, forward_handle_user_noproxyauth_test); tcase_add_test(testcase, forward_handle_user_userwithproxyauth_test); tcase_add_test(testcase, forward_handle_user_proxyuserwithproxyauth_test); tcase_add_test(testcase, forward_handle_pass_noproxyauth_test); tcase_add_test(testcase, forward_handle_pass_userwithproxyauth_test); tcase_add_test(testcase, forward_handle_pass_proxyuserwithproxyauth_test); suite_add_tcase(suite, testcase); return suite; } proftpd-mod_proxy-0.9.5/t/api/ftp/000077500000000000000000000000001475737016700170475ustar00rootroot00000000000000proftpd-mod_proxy-0.9.5/t/api/ftp/conn.c000066400000000000000000000221511475737016700201510ustar00rootroot00000000000000/* * ProFTPD - mod_proxy testsuite * Copyright (c) 2016-2022 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ /* FTP Connection API tests. */ #include "../tests.h" static pool *p = NULL; static void create_main_server(void) { pool *main_pool; xaset_t *servers; main_pool = make_sub_pool(permanent_pool); pr_pool_tag(main_pool, "testsuite#main_server pool"); servers = xaset_create(main_pool, NULL); main_server = (server_rec *) pcalloc(main_pool, sizeof(server_rec)); xaset_insert(servers, (xasetmember_t *) main_server); main_server->pool = main_pool; main_server->set = servers; main_server->sid = 1; main_server->notes = pr_table_nalloc(main_pool, 0, 8); main_server->conf = xaset_create(main_pool, NULL); /* TCP KeepAlive is enabled by default, with the system defaults. */ main_server->tcp_keepalive = palloc(main_server->pool, sizeof(struct tcp_keepalive)); main_server->tcp_keepalive->keepalive_enabled = TRUE; main_server->tcp_keepalive->keepalive_idle = -1; main_server->tcp_keepalive->keepalive_count = -1; main_server->tcp_keepalive->keepalive_intvl = -1; main_server->ServerName = "Test Server"; main_server->ServerPort = 21; } static void set_up(void) { if (p == NULL) { p = permanent_pool = session.pool = make_sub_pool(NULL); session.c = NULL; session.notes = NULL; } init_netaddr(); init_netio(); init_inet(); create_main_server(); if (getenv("TEST_VERBOSE") != NULL) { pr_trace_set_levels("netio", 1, 20); pr_trace_set_levels("inet", 1, 20); pr_trace_set_levels("proxy.ftp.conn", 1, 20); } pr_inet_set_default_family(p, AF_INET); } static void tear_down(void) { if (getenv("TEST_VERBOSE") != NULL) { pr_trace_set_levels("netio", 0, 0); pr_trace_set_levels("inet", 0, 0); pr_trace_set_levels("proxy.ftp.conn", 0, 0); } pr_inet_set_default_family(p, 0); pr_inet_clear(); if (p) { destroy_pool(p); p = permanent_pool = session.pool = NULL; main_server = NULL; session.c = NULL; session.notes = NULL; } } START_TEST (accept_test) { conn_t *res, *ctrl_conn = NULL, *data_conn = NULL; res = proxy_ftp_conn_accept(NULL, NULL, NULL, FALSE); ck_assert_msg(res == NULL, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_ftp_conn_accept(p, NULL, NULL, FALSE); ck_assert_msg(res == NULL, "Failed to handle null data conn"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); data_conn = pr_inet_create_conn(p, -2, NULL, INPORT_ANY, FALSE); ck_assert_msg(data_conn != NULL, "Failed to create conn: %s", strerror(errno)); mark_point(); res = proxy_ftp_conn_accept(p, data_conn, NULL, FALSE); ck_assert_msg(res == NULL, "Failed to handle null ctrl conn"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); ctrl_conn = pr_inet_create_conn(p, -2, NULL, INPORT_ANY, FALSE); ck_assert_msg(ctrl_conn != NULL, "Failed to create conn: %s", strerror(errno)); session.xfer.direction = PR_NETIO_IO_RD; mark_point(); res = proxy_ftp_conn_accept(p, data_conn, ctrl_conn, FALSE); ck_assert_msg(res == NULL, "Failed to handle null ctrl conn"); ck_assert_msg(errno == EBADF, "Expected EBADF (%d), got '%s' (%d)", EBADF, strerror(errno), errno); mark_point(); res = proxy_ftp_conn_accept(p, data_conn, ctrl_conn, TRUE); ck_assert_msg(res == NULL, "Failed to handle null ctrl conn"); ck_assert_msg(errno == EBADF, "Expected EBADF (%d), got '%s' (%d)", EBADF, strerror(errno), errno); session.xfer.direction = PR_NETIO_IO_WR; mark_point(); res = proxy_ftp_conn_accept(p, data_conn, ctrl_conn, FALSE); ck_assert_msg(res == NULL, "Failed to handle null ctrl conn"); ck_assert_msg(errno == EBADF, "Expected EBADF (%d), got '%s' (%d)", EBADF, strerror(errno), errno); mark_point(); res = proxy_ftp_conn_accept(p, data_conn, ctrl_conn, TRUE); ck_assert_msg(res == NULL, "Failed to handle null ctrl conn"); ck_assert_msg(errno == EBADF, "Expected EBADF (%d), got '%s' (%d)", EBADF, strerror(errno), errno); pr_inet_close(p, ctrl_conn); pr_inet_close(p, data_conn); } END_TEST START_TEST (connect_test) { conn_t *res; const pr_netaddr_t *remote_addr = NULL; res = proxy_ftp_conn_connect(NULL, NULL, NULL, FALSE); ck_assert_msg(res == NULL, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_ftp_conn_connect(p, NULL, NULL, FALSE); ck_assert_msg(res == NULL, "Failed to handle null remote addr"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); remote_addr = pr_netaddr_get_addr(p, "127.0.0.1", NULL); ck_assert_msg(remote_addr != NULL, "Failed to address for 127.0.0.1: %s", strerror(errno)); pr_netaddr_set_port((pr_netaddr_t *) remote_addr, htons(6555)); session.xfer.direction = PR_NETIO_IO_RD; mark_point(); res = proxy_ftp_conn_connect(p, NULL, remote_addr, FALSE); ck_assert_msg(res == NULL, "Failed to handle bad address family"); ck_assert_msg(errno == ECONNREFUSED, "Expected ECONNREFUSED (%d), got %s (%d)", ECONNREFUSED, strerror(errno), errno); mark_point(); res = proxy_ftp_conn_connect(p, NULL, remote_addr, TRUE); ck_assert_msg(res == NULL, "Failed to handle bad address family"); ck_assert_msg(errno == ECONNREFUSED, "Expected ECONNREFUSED (%d), got %s (%d)", ECONNREFUSED, strerror(errno), errno); session.xfer.direction = PR_NETIO_IO_WR; mark_point(); res = proxy_ftp_conn_connect(p, NULL, remote_addr, FALSE); ck_assert_msg(res == NULL, "Failed to handle bad address family"); ck_assert_msg(errno == ECONNREFUSED, "Expected ECONNREFUSED (%d), got %s (%d)", ECONNREFUSED, strerror(errno), errno); mark_point(); res = proxy_ftp_conn_connect(p, NULL, remote_addr, TRUE); ck_assert_msg(res == NULL, "Failed to handle bad address family"); ck_assert_msg(errno == ECONNREFUSED, "Expected ECONNREFUSED (%d), got %s (%d)", ECONNREFUSED, strerror(errno), errno); /* Try connecting to Google's DNS server. */ remote_addr = pr_netaddr_get_addr(p, "8.8.8.8", NULL); ck_assert_msg(remote_addr != NULL, "Failed to resolve '8.8.8.8': %s", strerror(errno)); pr_netaddr_set_port((pr_netaddr_t *) remote_addr, htons(53)); mark_point(); res = proxy_ftp_conn_connect(p, NULL, remote_addr, FALSE); ck_assert_msg(res != NULL, "Failed to connect: %s", strerror(errno)); pr_inet_close(p, res); mark_point(); res = proxy_ftp_conn_connect(p, NULL, remote_addr, TRUE); ck_assert_msg(res != NULL, "Failed to connect: %s", strerror(errno)); pr_inet_close(p, res); } END_TEST START_TEST (listen_test) { conn_t *res; const pr_netaddr_t *bind_addr = NULL; res = proxy_ftp_conn_listen(NULL, NULL, FALSE); ck_assert_msg(res == NULL, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_ftp_conn_listen(p, NULL, FALSE); ck_assert_msg(res == NULL, "Failed to handle null bind address"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); bind_addr = pr_netaddr_get_addr(p, "127.0.0.1", NULL); ck_assert_msg(bind_addr != NULL, "Failed to address for 127.0.0.1: %s", strerror(errno)); pr_netaddr_set_port((pr_netaddr_t *) bind_addr, htons(0)); mark_point(); res = proxy_ftp_conn_listen(p, bind_addr, FALSE); ck_assert_msg(res != NULL, "Failed to listen: %s", strerror(errno)); pr_inet_close(p, res); mark_point(); res = proxy_ftp_conn_listen(p, bind_addr, TRUE); ck_assert_msg(res != NULL, "Failed to listen: %s", strerror(errno)); pr_inet_close(p, res); } END_TEST Suite *tests_get_ftp_conn_suite(void) { Suite *suite; TCase *testcase; suite = suite_create("ftp.conn"); testcase = tcase_create("base"); tcase_add_checked_fixture(testcase, set_up, tear_down); tcase_add_test(testcase, accept_test); tcase_add_test(testcase, connect_test); tcase_add_test(testcase, listen_test); suite_add_tcase(suite, testcase); return suite; } proftpd-mod_proxy-0.9.5/t/api/ftp/ctrl.c000066400000000000000000000304341475737016700201630ustar00rootroot00000000000000/* * ProFTPD - mod_proxy testsuite * Copyright (c) 2016-2022 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ /* FTP Control API tests. */ #include "../tests.h" static pool *p = NULL; static void set_up(void) { if (p == NULL) { p = permanent_pool = session.pool = make_sub_pool(NULL); session.c = NULL; session.notes = NULL; } init_netaddr(); init_netio(); init_inet(); if (getenv("TEST_VERBOSE") != NULL) { pr_trace_set_levels("netio", 1, 20); pr_trace_set_levels("proxy.ftp.ctrl", 1, 20); } pr_inet_set_default_family(p, AF_INET); pr_response_set_pool(NULL); } static void tear_down(void) { if (getenv("TEST_VERBOSE") != NULL) { pr_trace_set_levels("netio", 0, 0); pr_trace_set_levels("proxy.ftp.ctrl", 0, 0); } pr_inet_set_default_family(p, 0); pr_inet_clear(); pr_response_set_pool(NULL); if (p) { destroy_pool(p); p = permanent_pool = session.pool = NULL; session.c = NULL; session.notes = NULL; } } START_TEST (handle_async_test) { int res, flags = PROXY_FTP_CTRL_FL_IGNORE_EOF; conn_t *frontend_conn, *backend_conn; pr_netio_stream_t *nstrm; mark_point(); res = proxy_ftp_ctrl_handle_async(NULL, NULL, NULL, flags); ck_assert_msg(res < 0, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_ftp_ctrl_handle_async(p, NULL, NULL, flags); ck_assert_msg(res < 0, "Failed to handle null backend conn"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); backend_conn = pr_inet_create_conn(p, -2, NULL, INPORT_ANY, FALSE); ck_assert_msg(backend_conn != NULL, "Failed to create conn: %s", strerror(errno)); mark_point(); res = proxy_ftp_ctrl_handle_async(p, backend_conn, NULL, flags); ck_assert_msg(res < 0, "Failed to handle null frontend conn"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); frontend_conn = pr_inet_create_conn(p, -2, NULL, INPORT_ANY, FALSE); ck_assert_msg(frontend_conn != NULL, "Failed to create conn: %s", strerror(errno)); mark_point(); res = proxy_ftp_ctrl_handle_async(p, backend_conn, frontend_conn, flags); ck_assert_msg(res < 0, "Failed to handle null backend conn stream"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); nstrm = pr_netio_open(p, PR_NETIO_STRM_CTRL, 8, PR_NETIO_IO_RD); backend_conn->instrm = nstrm; mark_point(); res = proxy_ftp_ctrl_handle_async(p, backend_conn, frontend_conn, flags); ck_assert_msg(res == 0, "Failed to handle async IO: %s", strerror(errno)); proxy_sess_state |= PROXY_SESS_STATE_CONNECTED; mark_point(); res = proxy_ftp_ctrl_handle_async(p, backend_conn, frontend_conn, flags); ck_assert_msg(res == 0, "Failed to handle async IO: %s", strerror(errno)); proxy_sess_state &= ~PROXY_SESS_STATE_CONNECTED; pr_inet_close(p, frontend_conn); pr_inet_close(p, backend_conn); } END_TEST START_TEST (recv_resp_test) { int flags = PROXY_FTP_CTRL_FL_IGNORE_EOF, len; pr_response_t *resp; unsigned int nlines = 0; conn_t *ctrl_conn = NULL; size_t buflen; pr_buffer_t *pbuf; pr_netio_stream_t *nstrm; mark_point(); resp = proxy_ftp_ctrl_recv_resp(NULL, NULL, NULL, flags); ck_assert_msg(resp == NULL, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); mark_point(); resp = proxy_ftp_ctrl_recv_resp(p, NULL, NULL, flags); ck_assert_msg(resp == NULL, "Failed to handle null ctrl conn"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); ctrl_conn = pr_inet_create_conn(p, -2, NULL, INPORT_ANY, FALSE); ck_assert_msg(ctrl_conn != NULL, "Failed to create conn: %s", strerror(errno)); mark_point(); resp = proxy_ftp_ctrl_recv_resp(p, ctrl_conn, NULL, flags); ck_assert_msg(resp == NULL, "Failed to handle null response nlines"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); mark_point(); resp = proxy_ftp_ctrl_recv_resp(p, ctrl_conn, &nlines, flags); ck_assert_msg(resp == NULL, "Failed to handle EOF"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); nstrm = pr_netio_open(p, PR_NETIO_STRM_CTRL, -1, PR_NETIO_IO_RD); ck_assert_msg(nstrm != NULL, "Failed to open ctrl stream: %s", strerror(errno)); pbuf = pr_netio_buffer_alloc(nstrm); ck_assert_msg(pbuf != NULL, "Failed to allocate stream buffer: %s", strerror(errno)); buflen = pbuf->buflen; len = snprintf(pbuf->buf, pbuf->buflen-1, "%s", "Foo"); pbuf->remaining = len; pbuf->current = pbuf->buf; ctrl_conn->instrm = nstrm; mark_point(); resp = proxy_ftp_ctrl_recv_resp(p, ctrl_conn, &nlines, flags); ck_assert_msg(resp == NULL, "Failed to handle invalid response"); ck_assert_msg(errno == E2BIG, "Expected E2BIG (%d), got %s (%d)", E2BIG, strerror(errno), errno); len = snprintf(pbuf->buf, pbuf->buflen-1, "%s", "Foo\r\n"); pbuf->remaining = len; pbuf->current = pbuf->buf; mark_point(); resp = proxy_ftp_ctrl_recv_resp(p, ctrl_conn, &nlines, flags); ck_assert_msg(resp == NULL, "Failed to handle invalid response"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); len = snprintf(pbuf->buf, pbuf->buflen-1, "%s", "Foo\r\n"); pbuf->remaining = pbuf->buflen = len; pbuf->current = pbuf->buf; ctrl_conn->instrm = nstrm; mark_point(); resp = proxy_ftp_ctrl_recv_resp(p, ctrl_conn, &nlines, flags); ck_assert_msg(resp == NULL, "Failed to handle invalid response"); ck_assert_msg(errno == EBADF, "Expected EBADF (%d), got %s (%d)", EBADF, strerror(errno), errno); len = snprintf(pbuf->buf, pbuf->buflen-1, "%s", "Food\r\n"); pbuf->buflen = buflen; pbuf->remaining = len; pbuf->current = pbuf->buf; mark_point(); resp = proxy_ftp_ctrl_recv_resp(p, ctrl_conn, &nlines, flags); ck_assert_msg(resp == NULL, "Failed to handle invalid response"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); len = snprintf(pbuf->buf, pbuf->buflen-1, "%s", "1ood\r\n"); pbuf->remaining = len; pbuf->current = pbuf->buf; mark_point(); resp = proxy_ftp_ctrl_recv_resp(p, ctrl_conn, &nlines, flags); ck_assert_msg(resp == NULL, "Failed to handle invalid response"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); len = snprintf(pbuf->buf, pbuf->buflen-1, "%s", "12od\r\n"); pbuf->remaining = len; pbuf->current = pbuf->buf; mark_point(); resp = proxy_ftp_ctrl_recv_resp(p, ctrl_conn, &nlines, flags); ck_assert_msg(resp == NULL, "Failed to handle invalid response"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); len = snprintf(pbuf->buf, pbuf->buflen-1, "%s", "123d\r\n"); pbuf->remaining = len; pbuf->current = pbuf->buf; mark_point(); resp = proxy_ftp_ctrl_recv_resp(p, ctrl_conn, &nlines, flags); ck_assert_msg(resp == NULL, "Failed to handle invalid response"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); len = snprintf(pbuf->buf, pbuf->buflen-1, "%s", "001 Foo\r\n"); pbuf->remaining = len; pbuf->current = pbuf->buf; mark_point(); resp = proxy_ftp_ctrl_recv_resp(p, ctrl_conn, &nlines, flags); ck_assert_msg(resp == NULL, "Failed to handle invalid response"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); len = snprintf(pbuf->buf, pbuf->buflen-1, "%s", "999 Foo\r\n"); pbuf->remaining = len; pbuf->current = pbuf->buf; mark_point(); resp = proxy_ftp_ctrl_recv_resp(p, ctrl_conn, &nlines, flags); ck_assert_msg(resp == NULL, "Failed to handle invalid response"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); len = snprintf(pbuf->buf, pbuf->buflen-1, "%s", "200 Foo\r\n"); pbuf->remaining = len; pbuf->current = pbuf->buf; mark_point(); resp = proxy_ftp_ctrl_recv_resp(p, ctrl_conn, &nlines, flags); ck_assert_msg(resp != NULL, "Failed to receive response: %s", strerror(errno)); ck_assert_msg(strcmp(resp->num, R_200) == 0, "Expected '%s', got '%s'", R_200, resp->num); ck_assert_msg(nlines == 1, "Expected 1, got %u", nlines); /* XXX TODO: multiline responses! */ pr_inet_close(p, ctrl_conn); } END_TEST START_TEST (send_cmd_test) { int res; conn_t *ctrl_conn; cmd_rec *cmd; res = proxy_ftp_ctrl_send_cmd(NULL, NULL, NULL); ck_assert_msg(res < 0, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_ftp_ctrl_send_cmd(p, NULL, NULL); ck_assert_msg(res < 0, "Failed to handle null conn"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); ctrl_conn = pr_inet_create_conn(p, -2, NULL, INPORT_ANY, FALSE); ck_assert_msg(ctrl_conn != NULL, "Failed to create conn: %s", strerror(errno)); res = proxy_ftp_ctrl_send_cmd(p, ctrl_conn, NULL); ck_assert_msg(res < 0, "Failed to handle null command"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); cmd = pr_cmd_alloc(p, 2, "FOO", "bar"); mark_point(); res = proxy_ftp_ctrl_send_cmd(p, ctrl_conn, cmd); ck_assert_msg(res < 0, "Failed to handle command without stream"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); cmd = pr_cmd_alloc(p, 1, "FOO"); mark_point(); res = proxy_ftp_ctrl_send_cmd(p, ctrl_conn, cmd); ck_assert_msg(res < 0, "Failed to handle command without stream"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); pr_inet_close(p, ctrl_conn); } END_TEST START_TEST (send_resp_test) { int res; pr_response_t *resp; res = proxy_ftp_ctrl_send_resp(NULL, NULL, NULL, 0); ck_assert_msg(res < 0, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_ftp_ctrl_send_resp(p, NULL, NULL, 0); ck_assert_msg(res < 0, "Failed to handle null response"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); resp = pcalloc(p, sizeof(pr_response_t)); resp->num = "123"; resp->msg = pstrdup(p, "foo bar?"); res = proxy_ftp_ctrl_send_resp(p, NULL, resp, 0); ck_assert_msg(res == 0, "Failed to handle response: %s", strerror(errno)); res = proxy_ftp_ctrl_send_resp(p, NULL, resp, 3); ck_assert_msg(res == 0, "Failed to handle response: %s", strerror(errno)); } END_TEST Suite *tests_get_ftp_ctrl_suite(void) { Suite *suite; TCase *testcase; suite = suite_create("ftp.ctrl"); testcase = tcase_create("base"); tcase_add_checked_fixture(testcase, set_up, tear_down); tcase_add_test(testcase, handle_async_test); tcase_add_test(testcase, recv_resp_test); tcase_add_test(testcase, send_cmd_test); tcase_add_test(testcase, send_resp_test); suite_add_tcase(suite, testcase); return suite; } proftpd-mod_proxy-0.9.5/t/api/ftp/data.c000066400000000000000000000131011475737016700201200ustar00rootroot00000000000000/* * ProFTPD - mod_proxy testsuite * Copyright (c) 2016-2022 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ /* FTP Data API tests. */ #include "../tests.h" static pool *p = NULL; static void set_up(void) { if (p == NULL) { p = permanent_pool = session.pool = make_sub_pool(NULL); session.c = NULL; session.notes = NULL; } init_netaddr(); init_netio(); init_inet(); if (getenv("TEST_VERBOSE") != NULL) { pr_trace_set_levels("proxy.ftp.data", 1, 20); } } static void tear_down(void) { if (getenv("TEST_VERBOSE") != NULL) { pr_trace_set_levels("proxy.ftp.data", 0, 0); } pr_inet_clear(); if (p) { destroy_pool(p); p = permanent_pool = session.pool = NULL; session.c = NULL; session.notes = NULL; } } START_TEST (recv_test) { pr_buffer_t *pbuf; conn_t *conn; mark_point(); pbuf = proxy_ftp_data_recv(NULL, NULL, FALSE); ck_assert_msg(pbuf == NULL, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); mark_point(); pbuf = proxy_ftp_data_recv(p, NULL, FALSE); ck_assert_msg(pbuf == NULL, "Failed to handle null conn"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); conn = pr_inet_create_conn(p, -2, NULL, INPORT_ANY, FALSE); ck_assert_msg(conn != NULL, "Failed to create conn: %s", strerror(errno)); mark_point(); pbuf = proxy_ftp_data_recv(p, conn, FALSE); ck_assert_msg(pbuf == NULL, "Failed to handle missing instream"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); conn->instrm = pr_netio_open(p, PR_NETIO_STRM_DATA, -1, PR_NETIO_IO_RD); ck_assert_msg(conn->instrm != NULL, "Failed open data stream: %s", strerror(errno)); mark_point(); pbuf = proxy_ftp_data_recv(p, conn, FALSE); ck_assert_msg(pbuf == NULL, "Failed to handle bad instream fd"); ck_assert_msg(errno == EBADF, "Expected EBADF (%d), got %s (%d)", EBADF, strerror(errno), errno); mark_point(); pbuf = proxy_ftp_data_recv(p, conn, TRUE); ck_assert_msg(pbuf == NULL, "Failed to handle bad instream fd"); ck_assert_msg(errno == EBADF, "Expected EBADF (%d), got %s (%d)", EBADF, strerror(errno), errno); /* Fill in the instrm fd with an fd to an empty file */ /* Fill in the instrm fd with an fd to /dev/null */ pr_inet_close(p, conn); } END_TEST START_TEST (send_test) { int res; pr_buffer_t *pbuf; conn_t *conn; mark_point(); res = proxy_ftp_data_send(NULL, NULL, NULL, FALSE); ck_assert_msg(res < 0, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_ftp_data_send(p, NULL, NULL, FALSE); ck_assert_msg(res < 0, "Failed to handle null conn"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); conn = pr_inet_create_conn(p, -2, NULL, INPORT_ANY, FALSE); ck_assert_msg(conn != NULL, "Failed to create conn: %s", strerror(errno)); mark_point(); res = proxy_ftp_data_send(p, conn, NULL, FALSE); ck_assert_msg(res < 0, "Failed to handle null buffer"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); conn->outstrm = pr_netio_open(p, PR_NETIO_STRM_DATA, -1, PR_NETIO_IO_WR); ck_assert_msg(conn->outstrm != NULL, "Failed open data stream: %s", strerror(errno)); mark_point(); res = proxy_ftp_data_send(p, conn, NULL, FALSE); ck_assert_msg(res < 0, "Failed to handle null buffer"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); pbuf = pcalloc(p, sizeof(pr_buffer_t)); pbuf->buflen = 1024; pbuf->buf = palloc(p, pbuf->buflen); pbuf->current = pbuf->buf + 2; mark_point(); res = proxy_ftp_data_send(p, conn, pbuf, FALSE); ck_assert_msg(res < 0, "Sent data unexpectedly"); ck_assert_msg(errno == EBADF, "Expected EBADF (%d), got %s (%d)", EBADF, strerror(errno), errno); mark_point(); res = proxy_ftp_data_send(p, conn, pbuf, TRUE); ck_assert_msg(res < 0, "Sent data unexpectedly"); ck_assert_msg(errno == EBADF, "Expected EBADF (%d), got %s (%d)", EBADF, strerror(errno), errno); pr_inet_close(p, conn); } END_TEST Suite *tests_get_ftp_data_suite(void) { Suite *suite; TCase *testcase; suite = suite_create("ftp.data"); testcase = tcase_create("base"); tcase_add_checked_fixture(testcase, set_up, tear_down); tcase_add_test(testcase, recv_test); tcase_add_test(testcase, send_test); suite_add_tcase(suite, testcase); return suite; } proftpd-mod_proxy-0.9.5/t/api/ftp/dirlist.c000066400000000000000000000261151475737016700206720ustar00rootroot00000000000000/* * ProFTPD - mod_proxy testsuite * Copyright (c) 2020-2022 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ /* FTP Dirlist API tests. */ #include "../tests.h" static pool *p = NULL; static void set_up(void) { if (p == NULL) { p = permanent_pool = session.pool = make_sub_pool(NULL); session.c = NULL; session.notes = NULL; } if (getenv("TEST_VERBOSE") != NULL) { pr_trace_set_levels("proxy.ftp.dirlist", 1, 20); } } static void tear_down(void) { if (getenv("TEST_VERBOSE") != NULL) { pr_trace_set_levels("proxy.ftp.dirlist", 0, 0); } if (p != NULL) { destroy_pool(p); p = permanent_pool = session.pool = NULL; session.c = NULL; session.notes = NULL; } } START_TEST (init_test) { int res; mark_point(); res = proxy_ftp_dirlist_init(NULL, NULL); ck_assert_msg(res < 0, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_ftp_dirlist_init(p, NULL); ck_assert_msg(res < 0, "Failed to handle null proxy_sess"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); } END_TEST START_TEST (finish_test) { int res; mark_point(); res = proxy_ftp_dirlist_finish(NULL); ck_assert_msg(res < 0, "Failed to handle null proxy_sess"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); } END_TEST START_TEST (from_dos_test) { struct proxy_dirlist_fileinfo *res = NULL; const char *text = NULL; size_t textlen = 0; mark_point(); res = proxy_ftp_dirlist_fileinfo_from_dos(NULL, NULL, 0, 0); ck_assert_msg(res == NULL, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_ftp_dirlist_fileinfo_from_dos(p, NULL, 0, 0); ck_assert_msg(res == NULL, "Failed to handle null text"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); text = "foo bar baz"; mark_point(); res = proxy_ftp_dirlist_fileinfo_from_dos(p, text, 0, 0); ck_assert_msg(res == NULL, "Failed to handle zero text len"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); textlen = strlen(text); mark_point(); res = proxy_ftp_dirlist_fileinfo_from_dos(p, text, textlen, 0); ck_assert_msg(res == NULL, "Failed to handle bad text"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); } END_TEST START_TEST (from_unix_test) { struct proxy_dirlist_fileinfo *res = NULL; const char *text = NULL; size_t textlen = 0; time_t now; struct tm *tm; mark_point(); res = proxy_ftp_dirlist_fileinfo_from_unix(NULL, NULL, 0, NULL, 0); ck_assert_msg(res == NULL, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_ftp_dirlist_fileinfo_from_unix(p, NULL, 0, NULL, 0); ck_assert_msg(res == NULL, "Failed to handle null text"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); text = "foo bar baz"; mark_point(); res = proxy_ftp_dirlist_fileinfo_from_unix(p, text, 0, NULL, 0); ck_assert_msg(res == NULL, "Failed to handle zero text len"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); textlen = strlen(text); mark_point(); res = proxy_ftp_dirlist_fileinfo_from_unix(p, text, textlen, NULL, 0); ck_assert_msg(res == NULL, "Failed to handle null tm"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); time(&now); tm = pr_gmtime(p, &now); mark_point(); res = proxy_ftp_dirlist_fileinfo_from_unix(p, text, textlen, tm, 0); ck_assert_msg(res == NULL, "Failed to handle bad text"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); } END_TEST START_TEST (from_text_test) { struct proxy_session *proxy_sess = NULL; struct proxy_dirlist_fileinfo *res = NULL; const char *text = NULL; size_t textlen = 0; time_t now; struct tm *tm; mark_point(); res = proxy_ftp_dirlist_fileinfo_from_text(NULL, NULL, 0, NULL, NULL, 0); ck_assert_msg(res == NULL, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_ftp_dirlist_fileinfo_from_text(p, NULL, 0, NULL, NULL, 0); ck_assert_msg(res == NULL, "Failed to handle null text"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); text = "foo bar baz"; mark_point(); res = proxy_ftp_dirlist_fileinfo_from_text(p, text, 0, NULL, NULL, 0); ck_assert_msg(res == NULL, "Failed to handle zero text len"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); textlen = strlen(text); mark_point(); res = proxy_ftp_dirlist_fileinfo_from_text(p, text, textlen, NULL, NULL, 0); ck_assert_msg(res == NULL, "Failed to handle null tm"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); time(&now); tm = pr_gmtime(p, &now); mark_point(); res = proxy_ftp_dirlist_fileinfo_from_text(p, text, textlen, tm, NULL, 0); ck_assert_msg(res == NULL, "Failed to handle null userdata"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); proxy_sess = (struct proxy_session *) proxy_session_alloc(p); ck_assert_msg(proxy_sess != NULL, "Failed to allocate proxy session: %s", strerror(errno)); mark_point(); res = proxy_ftp_dirlist_fileinfo_from_text(p, text, textlen, tm, proxy_sess, 0); ck_assert_msg(res == NULL, "Failed to handle null proxy_sess->dirlist_ctx"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); mark_point(); proxy_session_free(p, proxy_sess); } END_TEST START_TEST (to_facts_test) { const char *text; struct proxy_dirlist_fileinfo *pdf; mark_point(); text = proxy_ftp_dirlist_fileinfo_to_facts(NULL, NULL, NULL); ck_assert_msg(text == NULL, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); mark_point(); text = proxy_ftp_dirlist_fileinfo_to_facts(p, NULL, NULL); ck_assert_msg(text == NULL, "Failed to handle null fileinfo"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); pdf = pcalloc(p, sizeof(struct proxy_dirlist_fileinfo)); mark_point(); text = proxy_ftp_dirlist_fileinfo_to_facts(p, pdf, NULL); ck_assert_msg(text == NULL, "Failed to handle null textlen"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); } END_TEST START_TEST (to_text_test) { int res; struct proxy_session *proxy_sess = NULL; char *buf, *output_text = NULL; size_t buflen, maxlen, output_textlen = 0; mark_point(); res = proxy_ftp_dirlist_to_text(NULL, NULL, 0, 0, NULL, NULL, NULL); ck_assert_msg(res < 0, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_ftp_dirlist_to_text(p, NULL, 0, 0, NULL, NULL, NULL); ck_assert_msg(res < 0, "Failed to handle null buf"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); buf = "foo bar baz"; mark_point(); res = proxy_ftp_dirlist_to_text(p, buf, 0, 0, NULL, NULL, NULL); ck_assert_msg(res < 0, "Failed to handle zero buflen"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); buflen = strlen(buf); mark_point(); res = proxy_ftp_dirlist_to_text(p, buf, buflen, 0, NULL, NULL, NULL); ck_assert_msg(res < 0, "Failed to handle zero max textsz"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); maxlen = 1024; mark_point(); res = proxy_ftp_dirlist_to_text(p, buf, buflen, maxlen, NULL, NULL, NULL); ck_assert_msg(res < 0, "Failed to handle null output text"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_ftp_dirlist_to_text(p, buf, buflen, maxlen, &output_text, NULL, NULL); ck_assert_msg(res < 0, "Failed to handle null output textlen"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_ftp_dirlist_to_text(p, buf, buflen, maxlen, &output_text, &output_textlen, NULL); ck_assert_msg(res < 0, "Failed to handle null userdata"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); proxy_sess = (struct proxy_session *) proxy_session_alloc(p); ck_assert_msg(proxy_sess != NULL, "Failed to allocate proxy session: %s", strerror(errno)); mark_point(); res = proxy_ftp_dirlist_to_text(p, buf, buflen, maxlen, &output_text, &output_textlen, proxy_sess); ck_assert_msg(res < 0, "Failed to handle null proxy_sess->dirlist_ctx"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); mark_point(); proxy_session_free(p, proxy_sess); } END_TEST Suite *tests_get_ftp_dirlist_suite(void) { Suite *suite; TCase *testcase; suite = suite_create("ftp.dirlist"); testcase = tcase_create("base"); tcase_add_checked_fixture(testcase, set_up, tear_down); tcase_add_test(testcase, init_test); tcase_add_test(testcase, finish_test); tcase_add_test(testcase, from_dos_test); tcase_add_test(testcase, from_unix_test); tcase_add_test(testcase, from_text_test); tcase_add_test(testcase, to_facts_test); tcase_add_test(testcase, to_text_test); suite_add_tcase(suite, testcase); return suite; } proftpd-mod_proxy-0.9.5/t/api/ftp/facts.c000066400000000000000000000037341475737016700203220ustar00rootroot00000000000000/* * ProFTPD - mod_proxy testsuite * Copyright (c) 2020 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ /* FTP Facts API tests. */ #include "../tests.h" static pool *p = NULL; static void set_up(void) { if (p == NULL) { p = permanent_pool = session.pool = make_sub_pool(NULL); } if (getenv("TEST_VERBOSE") != NULL) { pr_trace_set_levels("proxy.ftp.facts", 1, 20); } } static void tear_down(void) { if (getenv("TEST_VERBOSE") != NULL) { pr_trace_set_levels("proxy.ftp.facts", 0, 0); } if (p != NULL) { destroy_pool(p); p = permanent_pool = session.pool = NULL; } } START_TEST (get_facts_test) { } END_TEST START_TEST (parse_facts_test) { } END_TEST Suite *tests_get_ftp_facts_suite(void) { Suite *suite; TCase *testcase; suite = suite_create("ftp.facts"); testcase = tcase_create("base"); tcase_add_checked_fixture(testcase, set_up, tear_down); tcase_add_test(testcase, get_facts_test); tcase_add_test(testcase, parse_facts_test); suite_add_tcase(suite, testcase); return suite; } proftpd-mod_proxy-0.9.5/t/api/ftp/msg.c000066400000000000000000000454041475737016700200100ustar00rootroot00000000000000/* * ProFTPD - mod_proxy testsuite * Copyright (c) 2016-2022 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ /* FTP Message API tests. */ #include "../tests.h" static pool *p = NULL; static unsigned char use_ipv6 = FALSE; static void set_up(void) { if (p == NULL) { p = permanent_pool = session.pool = proxy_pool = make_sub_pool(NULL); session.c = NULL; session.notes = NULL; } init_netaddr(); use_ipv6 = pr_netaddr_use_ipv6(); if (getenv("TEST_VERBOSE") != NULL) { pr_trace_set_levels("proxy.ftp.msg", 1, 20); } } static void tear_down(void) { if (getenv("TEST_VERBOSE") != NULL) { pr_trace_set_levels("proxy.ftp.msg", 0, 0); } if (use_ipv6 == FALSE) { pr_netaddr_disable_ipv6(); } if (p) { destroy_pool(p); p = permanent_pool = session.pool = proxy_pool = NULL; session.c = NULL; session.notes = NULL; } } START_TEST (fmt_addr_test) { const char *res, *expected; const pr_netaddr_t *addr; unsigned short port = 2121; mark_point(); res = proxy_ftp_msg_fmt_addr(NULL, NULL, 0, FALSE); ck_assert_msg(res == NULL, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_ftp_msg_fmt_addr(p, NULL, 0, FALSE); ck_assert_msg(res == NULL, "Failed to handle null addr"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); addr = pr_netaddr_get_addr(p, "127.0.0.1", NULL); ck_assert_msg(addr != NULL, "Failed to get addr for 127.0.0.1: %s", strerror(errno)); mark_point(); res = proxy_ftp_msg_fmt_addr(p, addr, port, FALSE); ck_assert_msg(res != NULL, "Failed to format addr: %s", strerror(errno)); expected = "127,0,0,1,8,73"; ck_assert_msg(strcmp(res, expected) == 0, "Expected '%s', got '%s'", expected, res); addr = pr_netaddr_get_addr(p, "169.254.254.254", NULL); ck_assert_msg(addr != NULL, "Failed to get addr for 169.254.254.254: %s", strerror(errno)); res = proxy_ftp_msg_fmt_addr(p, addr, 38550, FALSE); ck_assert_msg(res != NULL, "Failed to format addr: %s", strerror(errno)); expected = "169,254,254,254,150,150"; ck_assert_msg(strcmp(res, expected) == 0, "Expected '%s', got '%s'", expected, res); } END_TEST START_TEST (fmt_ext_addr_test) { const char *res, *expected; const pr_netaddr_t *addr; unsigned short port = 2121; mark_point(); res = proxy_ftp_msg_fmt_ext_addr(NULL, NULL, 0, 0, FALSE); ck_assert_msg(res == NULL, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_ftp_msg_fmt_ext_addr(p, NULL, 0, 0, FALSE); ck_assert_msg(res == NULL, "Failed to handle null addr"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); addr = pr_netaddr_get_addr(p, "127.0.0.1", NULL); ck_assert_msg(addr != NULL, "Failed to get addr for 127.0.0.1: %s", strerror(errno)); mark_point(); res = proxy_ftp_msg_fmt_ext_addr(p, addr, port, 0, FALSE); ck_assert_msg(res == NULL, "Failed to handle null addr"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_ftp_msg_fmt_ext_addr(p, addr, port, PR_CMD_EPRT_ID, FALSE); ck_assert_msg(res != NULL, "Failed to format addr: %s", strerror(errno)); expected = "|1|127.0.0.1|2121|"; ck_assert_msg(strcmp(res, expected) == 0, "Expected '%s', got '%s'", expected, res); mark_point(); res = proxy_ftp_msg_fmt_ext_addr(p, addr, port, PR_CMD_EPSV_ID, FALSE); ck_assert_msg(res != NULL, "Failed to format addr: %s", strerror(errno)); expected = "|||2121|"; ck_assert_msg(strcmp(res, expected) == 0, "Expected '%s', got '%s'", expected, res); #if PR_USE_IPV6 addr = pr_netaddr_get_addr(p, "::1", NULL); ck_assert_msg(addr != NULL, "Failed to get addr for ::1: %s", strerror(errno)); mark_point(); res = proxy_ftp_msg_fmt_ext_addr(p, addr, port, PR_CMD_EPRT_ID, FALSE); ck_assert_msg(res != NULL, "Failed to format addr: %s", strerror(errno)); expected = "|2|::1|2121|"; ck_assert_msg(strcmp(res, expected) == 0, "Expected '%s', got '%s'", expected, res); mark_point(); res = proxy_ftp_msg_fmt_ext_addr(p, addr, port, PR_CMD_EPSV_ID, FALSE); ck_assert_msg(res != NULL, "Failed to format addr: %s", strerror(errno)); expected = "|||2121|"; ck_assert_msg(strcmp(res, expected) == 0, "Expected '%s', got '%s'", expected, res); #endif /* PR_USE_IPV6 */ } END_TEST START_TEST (parse_addr_test) { const pr_netaddr_t *res; const char *msg, *expected, *ip_str; res = proxy_ftp_msg_parse_addr(NULL, NULL, 0); ck_assert_msg(res == NULL, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_ftp_msg_parse_addr(p, NULL, 0); ck_assert_msg(res == NULL, "Failed to handle null msg"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); msg = "foo"; res = proxy_ftp_msg_parse_addr(p, msg, 0); ck_assert_msg(res == NULL, "Failed to handle invalid format"); ck_assert_msg(errno == EPERM, "Expected EPERM (%d), got '%s' (%d)", EPERM, strerror(errno), errno); msg = "(a,b,c,d,e,f)"; res = proxy_ftp_msg_parse_addr(p, msg, 0); ck_assert_msg(res == NULL, "Failed to handle invalid format"); ck_assert_msg(errno == EPERM, "Expected EPERM (%d), got '%s' (%d)", EPERM, strerror(errno), errno); msg = "(1000,2000,3000,4000,5000,6000)"; res = proxy_ftp_msg_parse_addr(p, msg, 0); ck_assert_msg(res == NULL, "Failed to handle invalid format"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); msg = "(1,2,3,4,5000,6000)"; res = proxy_ftp_msg_parse_addr(p, msg, 0); ck_assert_msg(res == NULL, "Failed to handle invalid format"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); msg = "(0,0,0,0,1,2)"; res = proxy_ftp_msg_parse_addr(p, msg, 0); ck_assert_msg(res == NULL, "Failed to handle invalid format"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); msg = "(1,2,3,4,0,0)"; res = proxy_ftp_msg_parse_addr(p, msg, 0); ck_assert_msg(res == NULL, "Failed to handle invalid format"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); msg = "(127,0,0,1,8,73)"; res = proxy_ftp_msg_parse_addr(p, msg, 0); ck_assert_msg(res != NULL, "Failed to parse message '%s': %s", msg, strerror(errno)); ip_str = pr_netaddr_get_ipstr(res); expected = "127.0.0.1"; ck_assert_msg(strcmp(ip_str, expected) == 0, "Expected '%s', got '%s'", expected, ip_str); ck_assert_msg(ntohs(pr_netaddr_get_port(res)) == 2121, "Expected 2121, got %u", ntohs(pr_netaddr_get_port(res))); msg = "(195,144,107,198,8,73)"; res = proxy_ftp_msg_parse_addr(p, msg, 0); ck_assert_msg(res != NULL, "Failed to parse message '%s': %s", msg, strerror(errno)); ip_str = pr_netaddr_get_ipstr(res); expected = "195.144.107.198"; ck_assert_msg(strcmp(ip_str, expected) == 0, "Expected '%s', got '%s'", expected, ip_str); ck_assert_msg(ntohs(pr_netaddr_get_port(res)) == 2121, "Expected 2121, got %u", ntohs(pr_netaddr_get_port(res))); #ifdef PR_USE_IPV6 msg = "(127,0,0,1,8,73)"; res = proxy_ftp_msg_parse_addr(p, msg, AF_INET); ck_assert_msg(res != NULL, "Failed to parse message '%s': %s", msg, strerror(errno)); ip_str = pr_netaddr_get_ipstr(res); expected = "127.0.0.1"; ck_assert_msg(strcmp(ip_str, expected) == 0, "Expected '%s', got '%s'", expected, ip_str); msg = "(127,0,0,1,8,73)"; res = proxy_ftp_msg_parse_addr(p, msg, AF_INET6); ck_assert_msg(res != NULL, "Failed to parse message '%s': %s", msg, strerror(errno)); ip_str = pr_netaddr_get_ipstr(res); expected = "::ffff:127.0.0.1"; ck_assert_msg(strcmp(ip_str, expected) == 0, "Expected '%s', got '%s'", expected, ip_str); msg = "(195,144,107,198,8,73)"; res = proxy_ftp_msg_parse_addr(p, msg, AF_INET6); ck_assert_msg(res != NULL, "Failed to parse message '%s': %s", msg, strerror(errno)); ip_str = pr_netaddr_get_ipstr(res); expected = "::ffff:195.144.107.198"; ck_assert_msg(strcmp(ip_str, expected) == 0, "Expected '%s', got '%s'", expected, ip_str); ck_assert_msg(ntohs(pr_netaddr_get_port(res)) == 2121, "Expected 2121, got %u", ntohs(pr_netaddr_get_port(res))); pr_netaddr_disable_ipv6(); msg = "(127,0,0,1,8,73)"; res = proxy_ftp_msg_parse_addr(p, msg, AF_INET6); ck_assert_msg(res != NULL, "Failed to parse message '%s': %s", msg, strerror(errno)); ip_str = pr_netaddr_get_ipstr(res); expected = "127.0.0.1"; ck_assert_msg(strcmp(ip_str, expected) == 0, "Expected '%s', got '%s'", expected, ip_str); pr_netaddr_enable_ipv6(); #endif /* PR_USE_IPV6 */ } END_TEST START_TEST (parse_ext_addr_test) { const pr_netaddr_t *addr, *res; const char *msg; res = proxy_ftp_msg_parse_ext_addr(NULL, NULL, NULL, 0, NULL); ck_assert_msg(res == NULL, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_ftp_msg_parse_ext_addr(p, NULL, NULL, 0, NULL); ck_assert_msg(res == NULL, "Failed to handle null msg"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); msg = "foo"; res = proxy_ftp_msg_parse_ext_addr(p, msg, NULL, 0, NULL); ck_assert_msg(res == NULL, "Failed to handle null addr"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); addr = pr_netaddr_get_addr(p, "127.0.0.1", NULL); ck_assert_msg(addr != NULL, "Failed to get address for 127.0.0.1: %s", strerror(errno)); res = proxy_ftp_msg_parse_ext_addr(p, msg, addr, 0, NULL); ck_assert_msg(res == NULL, "Failed to handle bad network protocol"); ck_assert_msg(errno == EPROTOTYPE, "Expected EPROTOTYPE (%d), got '%s' (%d)", EPROTOTYPE, strerror(errno), errno); /* EPSV response formats */ res = proxy_ftp_msg_parse_ext_addr(p, msg, addr, PR_CMD_EPSV_ID, NULL); ck_assert_msg(res == NULL, "Failed to handle bad EPSV response"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); msg = "(foo"; res = proxy_ftp_msg_parse_ext_addr(p, msg, addr, PR_CMD_EPSV_ID, NULL); ck_assert_msg(res == NULL, "Failed to handle bad EPSV response"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); msg = "(foo)"; res = proxy_ftp_msg_parse_ext_addr(p, msg, addr, PR_CMD_EPSV_ID, NULL); ck_assert_msg(res == NULL, "Failed to handle bad network protocol"); ck_assert_msg(errno == EPROTOTYPE, "Expected EPROTOTYPE (%d), got '%s' (%d)", EPROTOTYPE, strerror(errno), errno); msg = "(1)"; res = proxy_ftp_msg_parse_ext_addr(p, msg, addr, PR_CMD_EPSV_ID, NULL); ck_assert_msg(res == NULL, "Failed to handle bad network protocol"); ck_assert_msg(errno == EPROTOTYPE, "Expected EPROTOTYPE (%d), got '%s' (%d)", EPROTOTYPE, strerror(errno), errno); msg = "(|4)"; res = proxy_ftp_msg_parse_ext_addr(p, msg, addr, PR_CMD_EPSV_ID, NULL); ck_assert_msg(res == NULL, "Failed to handle bad network protocol"); ck_assert_msg(errno == EPROTOTYPE, "Expected EPROTOTYPE (%d), got '%s' (%d)", EPROTOTYPE, strerror(errno), errno); msg = "(|0)"; res = proxy_ftp_msg_parse_ext_addr(p, msg, addr, PR_CMD_EPSV_ID, NULL); ck_assert_msg(res == NULL, "Failed to handle bad network protocol"); ck_assert_msg(errno == EPROTOTYPE, "Expected EPROTOTYPE (%d), got '%s' (%d)", EPROTOTYPE, strerror(errno), errno); msg = "(|1)"; res = proxy_ftp_msg_parse_ext_addr(p, msg, addr, PR_CMD_EPSV_ID, NULL); ck_assert_msg(res == NULL, "Failed to handle bad network protocol"); ck_assert_msg(errno == EPERM, "Expected EPERM (%d), got %s (%d)", EPERM, strerror(errno), errno); msg = "(|2)"; res = proxy_ftp_msg_parse_ext_addr(p, msg, addr, PR_CMD_EPSV_ID, NULL); ck_assert_msg(res == NULL, "Failed to handle bad network protocol"); ck_assert_msg(errno == EPERM, "Expected EPERM (%d), got %s (%d)", EPERM, strerror(errno), errno); /* Where the network protocol matches that of the address... */ msg = "(|1|)"; res = proxy_ftp_msg_parse_ext_addr(p, msg, addr, PR_CMD_EPSV_ID, NULL); ck_assert_msg(res == NULL, "Failed to handle badly formatted message"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); msg = "(|1|1.2.3.4)"; res = proxy_ftp_msg_parse_ext_addr(p, msg, addr, PR_CMD_EPSV_ID, NULL); ck_assert_msg(res == NULL, "Failed to handle badly formatted message"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); msg = "(|1|1.2.3.4|5)"; res = proxy_ftp_msg_parse_ext_addr(p, msg, addr, PR_CMD_EPSV_ID, NULL); ck_assert_msg(res == NULL, "Failed to handle badly formatted message"); ck_assert_msg(errno == EPERM, "Expected EPERM (%d), got %s (%d)", EPERM, strerror(errno), errno); msg = "(|1|1.2.3.4|5|)"; res = proxy_ftp_msg_parse_ext_addr(p, msg, addr, PR_CMD_EPSV_ID, NULL); ck_assert_msg(res != NULL, "Failed to handle formatted message '%s': %s", msg, strerror(errno)); msg = "(||1.2.3.4|5|)"; res = proxy_ftp_msg_parse_ext_addr(p, msg, addr, PR_CMD_EPSV_ID, NULL); ck_assert_msg(res != NULL, "Failed to handle formatted message '%s': %s", msg, strerror(errno)); msg = "(||1.2.3.4|5|)"; res = proxy_ftp_msg_parse_ext_addr(p, msg, addr, PR_CMD_EPSV_ID, "all"); ck_assert_msg(res != NULL, "Failed to handle formatted message '%s': %s", msg, strerror(errno)); msg = "(||1.2.3.4|5|)"; res = proxy_ftp_msg_parse_ext_addr(p, msg, addr, PR_CMD_EPSV_ID, "1"); ck_assert_msg(res != NULL, "Failed to handle formatted message '%s': %s", msg, strerror(errno)); msg = "(|||5|)"; res = proxy_ftp_msg_parse_ext_addr(p, msg, addr, PR_CMD_EPSV_ID, NULL); ck_assert_msg(res != NULL, "Failed to handle formatted message '%s': %s", msg, strerror(errno)); ck_assert_msg( strcmp(pr_netaddr_get_ipstr(addr), pr_netaddr_get_ipstr(res)) == 0, "Expected '%s', got '%s'", pr_netaddr_get_ipstr(addr), pr_netaddr_get_ipstr(res)); /* ...and where the network protocol does not match that of the address. */ msg = "(||::1|5|)"; res = proxy_ftp_msg_parse_ext_addr(p, msg, addr, PR_CMD_EPSV_ID, NULL); ck_assert_msg(res == NULL, "Failed to handle network protocol mismatch"); ck_assert_msg(errno == EPERM, "Expected EPERM (%d), got %s (%d)", EPERM, strerror(errno), errno); msg = "(|2|1.2.3.4|5)"; res = proxy_ftp_msg_parse_ext_addr(p, msg, addr, PR_CMD_EPSV_ID, NULL); ck_assert_msg(res == NULL, "Failed to handle network protocol mismatch"); ck_assert_msg(errno == EPERM, "Expected EPERM (%d), got %s (%d)", EPERM, strerror(errno), errno); #ifdef PR_USE_IPV6 addr = pr_netaddr_get_addr(p, "::1", NULL); ck_assert_msg(addr != NULL, "Failed to get address for ::1: %s", strerror(errno)); msg = "(|2|1.2.3.4|5)"; res = proxy_ftp_msg_parse_ext_addr(p, msg, addr, PR_CMD_EPSV_ID, NULL); ck_assert_msg(res == NULL, "Failed to handle network protocol mismatch"); ck_assert_msg(errno == EPERM, "Expected EPERM (%d), got %s (%d)", EPERM, strerror(errno), errno); msg = "(|1|::1|5)"; res = proxy_ftp_msg_parse_ext_addr(p, msg, addr, PR_CMD_EPSV_ID, NULL); ck_assert_msg(res == NULL, "Failed to handle network protocol mismatch"); ck_assert_msg(errno == EPERM, "Expected EPERM (%d), got %s (%d)", EPERM, strerror(errno), errno); msg = "(|2|::1|5)"; res = proxy_ftp_msg_parse_ext_addr(p, msg, addr, PR_CMD_EPSV_ID, NULL); ck_assert_msg(res == NULL, "Failed to handle network protocol mismatch"); ck_assert_msg(errno == EPERM, "Expected EPERM (%d), got %s (%d)", EPERM, strerror(errno), errno); msg = "(|2|::1|5|)"; res = proxy_ftp_msg_parse_ext_addr(p, msg, addr, PR_CMD_EPSV_ID, NULL); ck_assert_msg(res != NULL, "Failed to handle formatted message '%s': %s", msg, strerror(errno)); msg = "(|2|::1|5|)"; res = proxy_ftp_msg_parse_ext_addr(p, msg, addr, PR_CMD_EPSV_ID, "ALL"); ck_assert_msg(res != NULL, "Failed to handle formatted message '%s': %s", msg, strerror(errno)); msg = "(||::1|5|)"; res = proxy_ftp_msg_parse_ext_addr(p, msg, addr, PR_CMD_EPSV_ID, "2"); ck_assert_msg(res != NULL, "Failed to handle formatted message '%s': %s", msg, strerror(errno)); msg = "(||::1|5|)"; res = proxy_ftp_msg_parse_ext_addr(p, msg, addr, PR_CMD_EPSV_ID, NULL); ck_assert_msg(res != NULL, "Failed to handle formatted message '%s': %s", msg, strerror(errno)); msg = "(|||5|)"; res = proxy_ftp_msg_parse_ext_addr(p, msg, addr, PR_CMD_EPSV_ID, NULL); ck_assert_msg(res != NULL, "Failed to handle formatted message '%s': %s", msg, strerror(errno)); ck_assert_msg( strcmp(pr_netaddr_get_ipstr(addr), pr_netaddr_get_ipstr(res)) == 0, "Expected '%s', got '%s'", pr_netaddr_get_ipstr(addr), pr_netaddr_get_ipstr(res)); msg = "(||1.2.3.4|5|)"; res = proxy_ftp_msg_parse_ext_addr(p, msg, addr, PR_CMD_EPSV_ID, NULL); ck_assert_msg(res == NULL, "Failed to handle network protocol mismatch"); ck_assert_msg(errno == EPERM, "Expected EPERM (%d), got %s (%d)", EPERM, strerror(errno), errno); #endif /* PR_USE_IPV6 */ } END_TEST Suite *tests_get_ftp_msg_suite(void) { Suite *suite; TCase *testcase; suite = suite_create("ftp.msg"); testcase = tcase_create("base"); tcase_add_checked_fixture(testcase, set_up, tear_down); tcase_add_test(testcase, fmt_addr_test); tcase_add_test(testcase, fmt_ext_addr_test); tcase_add_test(testcase, parse_addr_test); tcase_add_test(testcase, parse_ext_addr_test); suite_add_tcase(suite, testcase); return suite; } proftpd-mod_proxy-0.9.5/t/api/ftp/sess.c000066400000000000000000000124561475737016700202000ustar00rootroot00000000000000/* * ProFTPD - mod_proxy testsuite * Copyright (c) 2016-2022 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ /* FTP Session API tests. */ #include "../tests.h" static pool *p = NULL; static void set_up(void) { if (p == NULL) { p = permanent_pool = session.pool = make_sub_pool(NULL); session.c = NULL; session.notes = NULL; } init_netaddr(); if (getenv("TEST_VERBOSE") != NULL) { pr_trace_set_levels("proxy.ftp.sess", 1, 20); } } static void tear_down(void) { if (getenv("TEST_VERBOSE") != NULL) { pr_trace_set_levels("proxy.ftp.sess", 0, 0); } if (p) { destroy_pool(p); p = permanent_pool = session.pool = NULL; session.c = NULL; session.notes = NULL; } } START_TEST (get_feat_test) { int res; const struct proxy_session *proxy_sess = NULL; res = proxy_ftp_sess_get_feat(NULL, NULL); ck_assert_msg(res < 0, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_ftp_sess_get_feat(p, NULL); ck_assert_msg(res < 0, "Failed to handle null proxy session"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); proxy_sess = proxy_session_alloc(p); mark_point(); res = proxy_ftp_sess_get_feat(p, proxy_sess); ck_assert_msg(res < 0, "Failed to handle null proxy session"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); proxy_session_free(p, proxy_sess); } END_TEST START_TEST (send_auth_tls_test) { int res; const struct proxy_session *proxy_sess = NULL; res = proxy_ftp_sess_send_auth_tls(NULL, NULL); ck_assert_msg(res < 0, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_ftp_sess_send_auth_tls(p, NULL); ck_assert_msg(res < 0, "Failed to handle null proxy session"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); proxy_sess = proxy_session_alloc(p); mark_point(); res = proxy_ftp_sess_send_auth_tls(p, proxy_sess); #ifdef PR_USE_OPENSSL ck_assert_msg(res < 0, "Sent AUTH TLS unexpectedly"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); #endif /* PR_USE_OPENSSL */ proxy_session_free(p, proxy_sess); } END_TEST START_TEST (send_host_test) { int res; const struct proxy_session *proxy_sess = NULL; res = proxy_ftp_sess_send_host(NULL, NULL); ck_assert_msg(res < 0, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_ftp_sess_send_host(p, NULL); ck_assert_msg(res < 0, "Failed to handle null proxy session"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); proxy_sess = proxy_session_alloc(p); mark_point(); res = proxy_ftp_sess_send_host(p, proxy_sess); ck_assert_msg(res == 0, "Failed to (maybe) send HOST: %s", strerror(errno)); proxy_session_free(p, proxy_sess); } END_TEST START_TEST (send_pbsz_prot_test) { int res; const struct proxy_session *proxy_sess = NULL; res = proxy_ftp_sess_send_pbsz_prot(NULL, NULL); ck_assert_msg(res < 0, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_ftp_sess_send_pbsz_prot(p, NULL); ck_assert_msg(res < 0, "Failed to handle null proxy session"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); proxy_sess = proxy_session_alloc(p); mark_point(); res = proxy_ftp_sess_send_pbsz_prot(p, proxy_sess); ck_assert_msg(res == 0, "Failed to (maybe) send PBSZ/PROT: %s", strerror(errno)); proxy_session_free(p, proxy_sess); } END_TEST Suite *tests_get_ftp_sess_suite(void) { Suite *suite; TCase *testcase; suite = suite_create("ftp.sess"); testcase = tcase_create("base"); tcase_add_checked_fixture(testcase, set_up, tear_down); tcase_add_test(testcase, get_feat_test); tcase_add_test(testcase, send_auth_tls_test); tcase_add_test(testcase, send_host_test); tcase_add_test(testcase, send_pbsz_prot_test); suite_add_tcase(suite, testcase); return suite; } proftpd-mod_proxy-0.9.5/t/api/ftp/xfer.c000066400000000000000000000226241475737016700201650ustar00rootroot00000000000000/* * ProFTPD - mod_proxy testsuite * Copyright (c) 2016-2022 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ /* FTP Transfer API tests. */ #include "../tests.h" extern xaset_t *server_list; static pool *p = NULL; static void create_main_server(void) { server_rec *s; s = pr_parser_server_ctxt_open("127.0.0.1"); s->ServerName = "Test Server"; main_server = s; } static void set_up(void) { if (p == NULL) { p = permanent_pool = session.pool = make_sub_pool(NULL); main_server = NULL; server_list = NULL; session.c = NULL; session.notes = NULL; } init_config(); init_netaddr(); init_netio(); init_inet(); server_list = xaset_create(p, NULL); pr_parser_prepare(p, &server_list); create_main_server(); pr_response_set_pool(p); if (getenv("TEST_VERBOSE") != NULL) { pr_trace_set_levels("proxy.ftp.conn", 1, 20); pr_trace_set_levels("proxy.ftp.xfer", 1, 20); } pr_inet_set_default_family(p, AF_INET); } static void tear_down(void) { pr_inet_set_default_family(p, 0); if (getenv("TEST_VERBOSE") != NULL) { pr_trace_set_levels("proxy.ftp.conn", 0, 0); pr_trace_set_levels("proxy.ftp.xfer", 0, 0); } pr_response_set_pool(NULL); pr_parser_cleanup(); pr_inet_clear(); if (p) { destroy_pool(p); p = permanent_pool = session.pool = NULL; main_server = NULL; server_list = NULL; session.c = NULL; session.notes = NULL; } } START_TEST (prepare_active_test) { int res; cmd_rec *cmd; struct proxy_session *proxy_sess = NULL; res = proxy_ftp_xfer_prepare_active(0, NULL, NULL, NULL, 0); ck_assert_msg(res < 0, "Failed to handle null command"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); cmd = pr_cmd_alloc(p, 1, "FOO"); res = proxy_ftp_xfer_prepare_active(0, cmd, NULL, NULL, 0); ck_assert_msg(res < 0, "Failed to handle null error code"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_ftp_xfer_prepare_active(0, cmd, "500", NULL, 0); ck_assert_msg(res < 0, "Failed to handle null proxy session"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); proxy_sess = (struct proxy_session *) proxy_session_alloc(p); mark_point(); res = proxy_ftp_xfer_prepare_active(0, cmd, "500", proxy_sess, 0); ck_assert_msg(res < 0, "Failed to handle null proxy session"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); proxy_sess->backend_ctrl_conn = pr_inet_create_conn(p, -1, NULL, INPORT_ANY, FALSE); ck_assert_msg(proxy_sess->backend_ctrl_conn != NULL, "Failed to open backend control conn: %s", strerror(errno)); session.c = pr_inet_create_conn(p, -1, NULL, INPORT_ANY, FALSE); ck_assert_msg(session.c != NULL, "Failed to open session control conn: %s", strerror(errno)); session.c->local_addr = session.c->remote_addr = pr_netaddr_get_addr(p, "127.0.0.1", NULL); ck_assert_msg(session.c->remote_addr != NULL, "Failed to get address: %s", strerror(errno)); mark_point(); res = proxy_ftp_xfer_prepare_active(0, cmd, "500", proxy_sess, 0); ck_assert_msg(res < 0, "Failed to handle illegal FTP command"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); /* Prevent NULL pointer dereferences which would only happen during * testing. */ proxy_sess->backend_ctrl_conn->remote_addr = session.c->remote_addr; mark_point(); res = proxy_ftp_xfer_prepare_active(PR_CMD_PORT_ID, cmd, "500", proxy_sess, 0); ck_assert_msg(res < 0, "Failed to handle bad PORT command"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_ftp_xfer_prepare_active(PR_CMD_EPRT_ID, cmd, "500", proxy_sess, 0); ck_assert_msg(res < 0, "Failed to handle bad EPRT command"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); cmd = pr_cmd_alloc(p, 1, "EPRT"); mark_point(); res = proxy_ftp_xfer_prepare_active(0, cmd, "500", proxy_sess, 0); ck_assert_msg(res < 0, "Failed to handle bad EPRT command"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); cmd = pr_cmd_alloc(p, 1, "PORT"); mark_point(); res = proxy_ftp_xfer_prepare_active(0, cmd, "500", proxy_sess, 0); ck_assert_msg(res < 0, "Failed to handle bad PORT command"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); pr_inet_close(p, session.c); proxy_session_free(p, proxy_sess); } END_TEST START_TEST (prepare_passive_test) { const pr_netaddr_t *addr; cmd_rec *cmd; struct proxy_session *proxy_sess; addr = proxy_ftp_xfer_prepare_passive(0, NULL, NULL, NULL, 0); ck_assert_msg(addr == NULL, "Failed to handle null command"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); cmd = pr_cmd_alloc(p, 1, "FOO"); addr = proxy_ftp_xfer_prepare_passive(0, cmd, NULL, NULL, 0); ck_assert_msg(addr == NULL, "Failed to handle null error code"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); addr = proxy_ftp_xfer_prepare_passive(0, cmd, "500", NULL, 0); ck_assert_msg(addr == NULL, "Failed to handle null proxy session"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); proxy_sess = (struct proxy_session *) proxy_session_alloc(p); mark_point(); addr = proxy_ftp_xfer_prepare_passive(0, cmd, "500", proxy_sess, 0); ck_assert_msg(addr == NULL, "Failed to handle null proxy session"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); proxy_sess->backend_ctrl_conn = pr_inet_create_conn(p, -1, NULL, INPORT_ANY, FALSE); ck_assert_msg(proxy_sess->backend_ctrl_conn != NULL, "Failed to open backend control conn: %s", strerror(errno)); session.c = pr_inet_create_conn(p, -1, NULL, INPORT_ANY, FALSE); ck_assert_msg(session.c != NULL, "Failed to open session control conn: %s", strerror(errno)); session.c->local_addr = session.c->remote_addr = pr_netaddr_get_addr(p, "127.0.0.1", NULL); ck_assert_msg(session.c->remote_addr != NULL, "Failed to get address: %s", strerror(errno)); mark_point(); addr = proxy_ftp_xfer_prepare_passive(0, cmd, "500", proxy_sess, 0); ck_assert_msg(addr == NULL, "Failed to handle illegal FTP command"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); /* Prevent NULL pointer dereferences which would only happen during * testing. */ proxy_sess->backend_ctrl_conn->remote_addr = session.c->remote_addr; mark_point(); addr = proxy_ftp_xfer_prepare_passive(PR_CMD_PASV_ID, cmd, "500", proxy_sess, 0); ck_assert_msg(addr == NULL, "Failed to handle bad PASV command"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); addr = proxy_ftp_xfer_prepare_passive(PR_CMD_EPSV_ID, cmd, "500", proxy_sess, 0); ck_assert_msg(addr == NULL, "Failed to handle bad EPSV command"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); cmd = pr_cmd_alloc(p, 1, "EPSV"); mark_point(); addr = proxy_ftp_xfer_prepare_passive(0, cmd, "500", proxy_sess, 0); ck_assert_msg(addr == NULL, "Failed to handle bad EPSV command"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); cmd = pr_cmd_alloc(p, 1, "PASV"); mark_point(); addr = proxy_ftp_xfer_prepare_passive(0, cmd, "500", proxy_sess, 0); ck_assert_msg(addr == NULL, "Failed to handle bad PASV command"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); pr_inet_close(p, session.c); proxy_session_free(p, proxy_sess); } END_TEST Suite *tests_get_ftp_xfer_suite(void) { Suite *suite; TCase *testcase; suite = suite_create("ftp.xfer"); testcase = tcase_create("base"); tcase_add_checked_fixture(testcase, set_up, tear_down); tcase_add_test(testcase, prepare_active_test); tcase_add_test(testcase, prepare_passive_test); suite_add_tcase(suite, testcase); return suite; } proftpd-mod_proxy-0.9.5/t/api/inet.c000066400000000000000000000265001475737016700173640ustar00rootroot00000000000000/* * ProFTPD - mod_proxy testsuite * Copyright (c) 2015-2022 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ /* Proxy Inet API tests. */ #include "tests.h" static pool *p = NULL; /* Fixtures */ static void set_up(void) { if (p == NULL) { p = permanent_pool = make_sub_pool(NULL); session.c = NULL; session.notes = NULL; } init_netaddr(); init_netio(); init_inet(); if (getenv("TEST_VERBOSE") != NULL) { pr_trace_set_levels("netio", 1, 20); pr_trace_set_levels("inet", 1, 20); pr_trace_set_levels("proxy.inet", 1, 20); } pr_inet_set_default_family(p, AF_INET); } static void tear_down(void) { if (getenv("TEST_VERBOSE") != NULL) { pr_trace_set_levels("netio", 0, 0); pr_trace_set_levels("inet", 0, 0); pr_trace_set_levels("proxy.inet", 0, 0); } pr_inet_set_default_family(p, 0); pr_inet_clear(); if (p) { destroy_pool(p); p = permanent_pool = NULL; session.c = NULL; session.notes = NULL; } } /* Tests */ START_TEST (inet_accept_test) { conn_t *conn; mark_point(); conn = proxy_inet_accept(NULL, NULL, NULL, -1, -1, FALSE); ck_assert_msg(conn == NULL, "Failed to handle null arguments"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); } END_TEST START_TEST (inet_close_test) { conn_t *conn; mark_point(); proxy_inet_close(NULL, NULL); conn = pr_inet_create_conn(p, -1, NULL, INPORT_ANY, FALSE); ck_assert_msg(conn != NULL, "Failed to create conn: %s", strerror(errno)); mark_point(); conn->rfd = conn->wfd = 999; proxy_inet_close(NULL, conn); } END_TEST START_TEST (inet_connect_ipv4_test) { int res; conn_t *conn; const pr_netaddr_t *addr; mark_point(); res = proxy_inet_connect(NULL, NULL, NULL, 0); ck_assert_msg(res < 0, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_inet_connect(p, NULL, NULL, 0); ck_assert_msg(res < 0, "Failed to handle null conn"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); conn = pr_inet_create_conn(p, -1, NULL, INPORT_ANY, FALSE); ck_assert_msg(conn != NULL, "Failed to create conn: %s", strerror(errno)); mark_point(); res = proxy_inet_connect(p, conn, NULL, 0); ck_assert_msg(res < 0, "Failed to handle null addr"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); addr = pr_netaddr_get_addr(p, "127.0.0.1", NULL); ck_assert_msg(addr != NULL, "Failed to resolve '127.0.0.1': %s", strerror(errno)); mark_point(); res = proxy_inet_connect(p, conn, addr, 80); ck_assert_msg(res < 0, "Connected to 127.0.0.1#80 unexpectedly"); ck_assert_msg(errno == ECONNREFUSED, "Expected ECONNREFUSED (%d), got '%s' (%d)", ECONNREFUSED, strerror(errno), errno); proxy_inet_close(p, conn); /* Try connecting to Google's DNS server. */ addr = pr_netaddr_get_addr(p, "8.8.8.8", NULL); ck_assert_msg(addr != NULL, "Failed to resolve '8.8.8.8': %s", strerror(errno)); conn = pr_inet_create_conn(p, -1, NULL, INPORT_ANY, FALSE); ck_assert_msg(conn != NULL, "Failed to create conn: %s", strerror(errno)); mark_point(); res = proxy_inet_connect(p, conn, addr, 53); ck_assert_msg(res >= 0, "Failed to connect to 8.8.8.8#53: %s", strerror(errno)); mark_point(); proxy_inet_close(p, conn); /* Now start supplying in/out streams. */ conn = pr_inet_create_conn(p, -1, NULL, INPORT_ANY, FALSE); ck_assert_msg(conn != NULL, "Failed to create conn: %s", strerror(errno)); conn->instrm = pr_netio_open(p, PR_NETIO_STRM_CTRL, -1, PR_NETIO_IO_RD); ck_assert_msg(conn->instrm != NULL, "Failed to open ctrl reading stream: %s", strerror(errno)); mark_point(); res = proxy_inet_connect(p, conn, addr, 53); ck_assert_msg(res >= 0, "Failed to connect to 8.8.8.8#53: %s", strerror(errno)); mark_point(); proxy_inet_close(p, conn); conn = pr_inet_create_conn(p, -1, NULL, INPORT_ANY, FALSE); ck_assert_msg(conn != NULL, "Failed to create conn: %s", strerror(errno)); conn->instrm = pr_netio_open(p, PR_NETIO_STRM_CTRL, -1, PR_NETIO_IO_RD); ck_assert_msg(conn->instrm != NULL, "Failed to open ctrl reading stream: %s", strerror(errno)); conn->outstrm = pr_netio_open(p, PR_NETIO_STRM_OTHR, -1, PR_NETIO_IO_WR); ck_assert_msg(conn->outstrm != NULL, "Failed to open othr writing stream: %s", strerror(errno)); mark_point(); res = proxy_inet_connect(p, conn, addr, 53); ck_assert_msg(res >= 0, "Failed to connect to 8.8.8.8#53: %s", strerror(errno)); mark_point(); proxy_inet_close(p, conn); } END_TEST START_TEST (inet_connect_ipv6_test) { #ifdef PR_USE_IPV6 int res; conn_t *conn; const pr_netaddr_t *addr; unsigned char use_ipv6; use_ipv6 = pr_netaddr_use_ipv6(); pr_netaddr_enable_ipv6(); pr_inet_set_default_family(p, AF_INET6); conn = pr_inet_create_conn(p, -1, NULL, INPORT_ANY, FALSE); ck_assert_msg(conn != NULL, "Failed to create conn: %s", strerror(errno)); addr = pr_netaddr_get_addr(p, "::1", NULL); ck_assert_msg(addr != NULL, "Failed to resolve '::1': %s", strerror(errno)); mark_point(); res = proxy_inet_connect(p, conn, addr, 80); ck_assert_msg(res < 0, "Connected to 127.0.0.1#80 unexpectedly"); ck_assert_msg(errno == ECONNREFUSED || errno == ENETUNREACH || errno == EADDRNOTAVAIL, "Expected ECONNREFUSED (%d), ENETUNREACH (%d), or EADDRNOTAVAIL (%d), got %s (%d)", ECONNREFUSED, ENETUNREACH, EADDRNOTAVAIL, strerror(errno), errno); proxy_inet_close(p, conn); /* Try connecting to Google's DNS server. */ conn = pr_inet_create_conn(p, -1, NULL, INPORT_ANY, FALSE); ck_assert_msg(conn != NULL, "Failed to create conn: %s", strerror(errno)); addr = pr_netaddr_get_addr(p, "2001:4860:4860::8888", NULL); ck_assert_msg(addr != NULL, "Failed to resolve '2001:4860:4860::8888': %s", strerror(errno)); mark_point(); res = proxy_inet_connect(p, conn, addr, 53); if (res < 0) { /* This could be expected, e.g. if there's no route. */ ck_assert_msg(errno == ECONNREFUSED || errno == ENETUNREACH || errno == EADDRNOTAVAIL, "Expected ECONNREFUSED (%d), ENETUNREACH (%d), or EADDRNOTAVAIL (%d), got %s (%d)", ECONNREFUSED, ENETUNREACH, EADDRNOTAVAIL, strerror(errno), errno); } mark_point(); proxy_inet_close(p, conn); pr_inet_set_default_family(p, AF_INET); if (use_ipv6 == FALSE) { pr_netaddr_disable_ipv6(); } #endif /* PR_USE_IPV6 */ } END_TEST START_TEST (inet_listen_test) { int res; conn_t *conn; mark_point(); res = proxy_inet_listen(NULL, NULL, 0, 0); ck_assert_msg(res < 0, "Failed to handle null arguments"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_inet_listen(p, NULL, 0, 0); ck_assert_msg(res < 0, "Failed to handle null conn"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); conn = pr_inet_create_conn(p, -1, NULL, INPORT_ANY, FALSE); ck_assert_msg(conn != NULL, "Failed to create conn: %s", strerror(errno)); mark_point(); res = proxy_inet_listen(p, conn, 5, 0); ck_assert_msg(res == 0, "Failed to listen on conn: %s", strerror(errno)); mark_point(); proxy_inet_close(p, conn); /* Now start providing in/out streams. */ conn = pr_inet_create_conn(p, -1, NULL, INPORT_ANY, FALSE); ck_assert_msg(conn != NULL, "Failed to create conn: %s", strerror(errno)); conn->instrm = pr_netio_open(p, PR_NETIO_STRM_CTRL, -1, PR_NETIO_IO_RD); ck_assert_msg(conn->instrm != NULL, "Failed to open ctrl reading stream: %s", strerror(errno)); mark_point(); res = proxy_inet_listen(p, conn, 5, 0); ck_assert_msg(res == 0, "Failed to listen on conn: %s", strerror(errno)); mark_point(); proxy_inet_close(p, conn); conn = pr_inet_create_conn(p, -1, NULL, INPORT_ANY, FALSE); ck_assert_msg(conn != NULL, "Failed to create conn: %s", strerror(errno)); conn->instrm = pr_netio_open(p, PR_NETIO_STRM_CTRL, -1, PR_NETIO_IO_RD); ck_assert_msg(conn->instrm != NULL, "Failed to open ctrl reading stream: %s", strerror(errno)); conn->outstrm = pr_netio_open(p, PR_NETIO_STRM_OTHR, -1, PR_NETIO_IO_WR); ck_assert_msg(conn->outstrm != NULL, "Failed to open othr writing stream: %s", strerror(errno)); mark_point(); res = proxy_inet_listen(p, conn, 5, 0); ck_assert_msg(res == 0, "Failed to listen on conn: %s", strerror(errno)); mark_point(); proxy_inet_close(p, conn); } END_TEST START_TEST (inet_openrw_test) { conn_t *res, *conn; const pr_netaddr_t *addr; res = proxy_inet_openrw(NULL, NULL, NULL, PR_NETIO_STRM_CTRL, -1, -1, -1, FALSE); ck_assert_msg(res == NULL, "Failed to handle null arguments"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_inet_openrw(p, NULL, NULL, PR_NETIO_STRM_CTRL, -1, -1, -1, FALSE); ck_assert_msg(res == NULL, "Failed to handle null conn"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); conn = pr_inet_create_conn(p, -2, NULL, INPORT_ANY, FALSE); ck_assert_msg(conn != NULL, "Failed to create conn: %s", strerror(errno)); res = proxy_inet_openrw(p, conn, NULL, PR_NETIO_STRM_OTHR, -1, -1, -1, FALSE); ck_assert_msg(res == NULL, "Failed to handle null addr"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); proxy_inet_close(p, conn); addr = pr_netaddr_get_addr(p, "127.0.0.1", NULL); ck_assert_msg(addr != NULL, "Failed to resolve '127.0.0.1': %s", strerror(errno)); res = proxy_inet_openrw(p, conn, addr, PR_NETIO_STRM_OTHR, -1, -1, -1, FALSE); ck_assert_msg(res != NULL, "Failed to open rw conn: %s", strerror(errno)); proxy_inet_close(p, conn); } END_TEST Suite *tests_get_inet_suite(void) { Suite *suite; TCase *testcase; suite = suite_create("inet"); testcase = tcase_create("base"); tcase_add_checked_fixture(testcase, set_up, tear_down); tcase_add_test(testcase, inet_accept_test); tcase_add_test(testcase, inet_close_test); tcase_add_test(testcase, inet_connect_ipv4_test); tcase_add_test(testcase, inet_connect_ipv6_test); tcase_add_test(testcase, inet_listen_test); tcase_add_test(testcase, inet_openrw_test); suite_add_tcase(suite, testcase); return suite; } proftpd-mod_proxy-0.9.5/t/api/netio.c000066400000000000000000000341001475737016700175360ustar00rootroot00000000000000/* * ProFTPD - mod_proxy testsuite * Copyright (c) 2015-2022 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ /* Proxy NetIO API tests. */ #include "tests.h" static pool *p = NULL; static void set_up(void) { if (p == NULL) { p = permanent_pool = make_sub_pool(NULL); session.c = NULL; session.notes = NULL; } init_netio(); if (getenv("TEST_VERBOSE") != NULL) { pr_trace_set_levels("netio", 1, 20); pr_trace_set_levels("proxy.netio", 1, 20); } } static void tear_down(void) { if (getenv("TEST_VERBOSE") != NULL) { pr_trace_set_levels("netio", 0, 0); pr_trace_set_levels("proxy.netio", 0, 0); } if (p) { destroy_pool(p); p = permanent_pool = NULL; session.c = NULL; session.notes = NULL; } } START_TEST (netio_close_test) { int res; res = proxy_netio_close(NULL); ck_assert_msg(res < 0, "Failed to handle null stream"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); } END_TEST START_TEST (netio_open_test) { int res; pr_netio_stream_t *nstrm; nstrm = proxy_netio_open(NULL, 0, -1, PR_NETIO_IO_RD); ck_assert_msg(nstrm == NULL, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); nstrm = proxy_netio_open(p, 77, -1, PR_NETIO_IO_RD); ck_assert_msg(nstrm == NULL, "Failed to handle unsupported stream type"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); nstrm = proxy_netio_open(p, PR_NETIO_STRM_OTHR, -1, PR_NETIO_IO_RD); ck_assert_msg(nstrm != NULL, "Failed to handle othr stream type: %s", strerror(errno)); res = proxy_netio_close(nstrm); ck_assert_msg(res < 0, "Successfully closed stream unexpectedly"); ck_assert_msg(errno == EBADF, "Expected EBADF (%d), got '%s' (%d)", EBADF, strerror(errno), errno); } END_TEST START_TEST (netio_poll_test) { int res; pr_netio_stream_t *nstrm; res = proxy_netio_poll(NULL); ck_assert_msg(res < 0, "Failed to handle null stream"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); nstrm = proxy_netio_open(p, PR_NETIO_STRM_OTHR, -1, PR_NETIO_IO_RD); ck_assert_msg(nstrm != NULL, "Failed to handle othr stream type: %s", strerror(errno)); res = proxy_netio_poll(nstrm); ck_assert_msg(res < 0, "Polled stream unexpectedly"); ck_assert_msg(errno == EBADF, "Expected EBADF (%d), got '%s' (%d)", EBADF, strerror(errno), errno); res = proxy_netio_close(nstrm); ck_assert_msg(res < 0, "Successfully closed stream unexpectedly"); ck_assert_msg(errno == EBADF, "Expected EBADF (%d), got '%s' (%d)", EBADF, strerror(errno), errno); } END_TEST START_TEST (netio_postopen_test) { int res; pr_netio_stream_t *nstrm; res = proxy_netio_postopen(NULL); ck_assert_msg(res < 0, "Failed to handle null stream"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); nstrm = proxy_netio_open(p, PR_NETIO_STRM_OTHR, -1, PR_NETIO_IO_RD); ck_assert_msg(nstrm != NULL, "Failed to handle othr stream type: %s", strerror(errno)); res = proxy_netio_postopen(nstrm); ck_assert_msg(res == 0, "Failed to postopen stream: %s", strerror(errno)); res = proxy_netio_close(nstrm); ck_assert_msg(res < 0, "Successfully closed stream unexpectedly"); ck_assert_msg(errno == EBADF, "Expected EBADF (%d), got '%s' (%d)", EBADF, strerror(errno), errno); } END_TEST START_TEST (netio_printf_test) { int res; pr_netio_stream_t *nstrm; res = proxy_netio_printf(NULL, "%s", "foo"); ck_assert_msg(res < 0, "Failed to handle null stream"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); nstrm = proxy_netio_open(p, PR_NETIO_STRM_OTHR, -1, PR_NETIO_IO_RD); ck_assert_msg(nstrm != NULL, "Failed to handle othr stream type: %s", strerror(errno)); res = proxy_netio_printf(nstrm, "%d", 7); ck_assert_msg(res < 0, "Failed to handle null stream"); ck_assert_msg(errno == EBADF, "Expected EBADF (%d), got '%s' (%d)", EBADF, strerror(errno), errno); res = proxy_netio_close(nstrm); ck_assert_msg(res < 0, "Successfully closed stream unexpectedly"); ck_assert_msg(errno == EBADF, "Expected EBADF (%d), got '%s' (%d)", EBADF, strerror(errno), errno); } END_TEST START_TEST (netio_read_test) { int res; pr_netio_stream_t *nstrm; char *buf; size_t bufsz; bufsz = 1024; buf = palloc(p, bufsz); mark_point(); res = proxy_netio_read(NULL, buf, bufsz, 1); ck_assert_msg(res < 0, "Failed to handle null stream"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); nstrm = proxy_netio_open(p, PR_NETIO_STRM_OTHR, -1, PR_NETIO_IO_RD); ck_assert_msg(nstrm != NULL, "Failed to handle othr stream type: %s", strerror(errno)); mark_point(); res = proxy_netio_read(nstrm, buf, bufsz, 1); ck_assert_msg(res < 0, "Successfully read from stream unexpectedly"); ck_assert_msg(errno == EBADF, "Expected EBADF (%d), got '%s' (%d)", EBADF, strerror(errno), errno); res = proxy_netio_close(nstrm); ck_assert_msg(res < 0, "Successfully closed stream unexpectedly"); ck_assert_msg(errno == EBADF, "Expected EBADF (%d), got '%s' (%d)", EBADF, strerror(errno), errno); } END_TEST START_TEST (netio_reset_poll_interval_test) { int res; pr_netio_stream_t *nstrm; mark_point(); proxy_netio_reset_poll_interval(NULL); nstrm = proxy_netio_open(p, PR_NETIO_STRM_OTHR, -1, PR_NETIO_IO_RD); ck_assert_msg(nstrm != NULL, "Failed to handle othr stream type: %s", strerror(errno)); mark_point(); proxy_netio_reset_poll_interval(nstrm); res = proxy_netio_close(nstrm); ck_assert_msg(res < 0, "Successfully closed stream unexpectedly"); ck_assert_msg(errno == EBADF, "Expected EBADF (%d), got '%s' (%d)", EBADF, strerror(errno), errno); } END_TEST START_TEST (netio_set_poll_interval_test) { int res; pr_netio_stream_t *nstrm; mark_point(); proxy_netio_set_poll_interval(NULL, 1); nstrm = proxy_netio_open(p, PR_NETIO_STRM_OTHR, -1, PR_NETIO_IO_RD); ck_assert_msg(nstrm != NULL, "Failed to handle othr stream type: %s", strerror(errno)); mark_point(); proxy_netio_set_poll_interval(nstrm, 1); res = proxy_netio_close(nstrm); ck_assert_msg(res < 0, "Successfully closed stream unexpectedly"); ck_assert_msg(errno == EBADF, "Expected EBADF (%d), got '%s' (%d)", EBADF, strerror(errno), errno); } END_TEST START_TEST (netio_shutdown_test) { int res; pr_netio_stream_t *nstrm; mark_point(); res = proxy_netio_shutdown(NULL, 0); ck_assert_msg(res < 0, "Failed to handle null stream"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); nstrm = proxy_netio_open(p, PR_NETIO_STRM_OTHR, -1, PR_NETIO_IO_RD); ck_assert_msg(nstrm != NULL, "Failed to handle othr stream type: %s", strerror(errno)); mark_point(); res = proxy_netio_shutdown(nstrm, 0); ck_assert_msg(res < 0, "Successfully shutdown stream unexpectedly"); ck_assert_msg(errno == EBADF, "Expected EBADF (%d), got '%s' (%d)", EBADF, strerror(errno), errno); res = proxy_netio_close(nstrm); ck_assert_msg(res < 0, "Successfully closed stream unexpectedly"); ck_assert_msg(errno == EBADF, "Expected EBADF (%d), got '%s' (%d)", EBADF, strerror(errno), errno); } END_TEST START_TEST (netio_write_test) { int res; pr_netio_stream_t *nstrm; mark_point(); res = proxy_netio_write(NULL, "foo", 3); ck_assert_msg(res < 0, "Failed to handle null stream"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); nstrm = proxy_netio_open(p, PR_NETIO_STRM_OTHR, -1, PR_NETIO_IO_RD); ck_assert_msg(nstrm != NULL, "Failed to handle othr stream type: %s", strerror(errno)); mark_point(); res = proxy_netio_write(nstrm, "foo", 1); ck_assert_msg(res < 0, "Wrote to stream unexpectedly"); ck_assert_msg(errno == EBADF, "Expected EBADF (%d), got '%s' (%d)", EBADF, strerror(errno), errno); res = proxy_netio_close(nstrm); ck_assert_msg(res < 0, "Successfully closed stream unexpectedly"); ck_assert_msg(errno == EBADF, "Expected EBADF (%d), got '%s' (%d)", EBADF, strerror(errno), errno); } END_TEST START_TEST (netio_set_test) { pr_netio_t *netio = NULL; int res, strm_type = PR_NETIO_STRM_OTHR; netio = proxy_netio_unset(strm_type, NULL); ck_assert_msg(netio == NULL, "Failed to handle null function string"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); netio = proxy_netio_unset(strm_type, "foo"); ck_assert_msg(netio == NULL, "Expected null othr NetIO, got %p", netio); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_netio_set(strm_type, netio); ck_assert_msg(res == 0, "Failed to set null othr netio: %s", strerror(errno)); strm_type = PR_NETIO_STRM_CTRL; res = proxy_netio_set(strm_type, netio); ck_assert_msg(res == 0, "Failed to set null ctrl netio: %s", strerror(errno)); res = proxy_netio_set(strm_type, netio); ck_assert_msg(res == 0, "Failed to set null ctrl netio again: %s", strerror(errno)); netio = pr_alloc_netio2(p, NULL, "testsuite"); res = proxy_netio_set(strm_type, netio); ck_assert_msg(res == 0, "Failed to set ctrl netio: %s", strerror(errno)); res = proxy_netio_set(strm_type, netio); ck_assert_msg(res == 0, "Failed to set ctrl netio again: %s", strerror(errno)); netio = proxy_netio_unset(strm_type, "testcase"); ck_assert_msg(netio != NULL, "Failed to unset ctrl netio: %s", strerror(errno)); strm_type = PR_NETIO_STRM_DATA; netio = NULL; res = proxy_netio_set(strm_type, netio); ck_assert_msg(res == 0, "Failed to set null data netio: %s", strerror(errno)); res = proxy_netio_set(strm_type, netio); ck_assert_msg(res == 0, "Failed to set null data netio again: %s", strerror(errno)); netio = pr_alloc_netio2(p, NULL, "testsuite"); res = proxy_netio_set(strm_type, netio); ck_assert_msg(res == 0, "Failed to set data netio: %s", strerror(errno)); res = proxy_netio_set(strm_type, netio); ck_assert_msg(res == 0, "Failed to set data netio again: %s", strerror(errno)); netio = proxy_netio_unset(strm_type, "testcase"); ck_assert_msg(netio != NULL, "Failed to unset data netio: %s", strerror(errno)); } END_TEST START_TEST (netio_use_test) { pr_netio_t *netio = NULL; int res, strm_type = PR_NETIO_STRM_OTHR; res = proxy_netio_using(strm_type, NULL); ck_assert_msg(res < 0, "Failed to handle null argument"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_netio_using(strm_type, &netio); ck_assert_msg(res < 0, "Failed to handle othr stream type"); ck_assert_msg(errno == ENOENT, "Expected ENOENT (%d), got '%s' (%d)", ENOENT, strerror(errno), errno); res = proxy_netio_using(PR_NETIO_STRM_CTRL, &netio); ck_assert_msg(res == 0, "Failed to handle ctrl stream type: %s", strerror(errno)); ck_assert_msg(netio == NULL, "Expected null ctrl netio, got %p", netio); res = proxy_netio_using(PR_NETIO_STRM_DATA, &netio); ck_assert_msg(res == 0, "Failed to handle data stream type: %s", strerror(errno)); ck_assert_msg(netio == NULL, "Expected null data netio, got %p", netio); res = proxy_netio_use(strm_type, NULL); ck_assert_msg(res < 0, "Failed to handle othr stream type"); ck_assert_msg(errno == ENOSYS, "Expected ENOSYS (%d), got '%s' (%d)", ENOSYS, strerror(errno), errno); res = proxy_netio_use(PR_NETIO_STRM_CTRL, NULL); ck_assert_msg(res == 0, "Failed to handle ctrl stream type: %s", strerror(errno)); netio = proxy_netio_unset(PR_NETIO_STRM_CTRL, "testcase"); ck_assert_msg(netio == NULL, "Unset ctrl stream unexpectedly"); ck_assert_msg(errno == ENOSYS, "Expected ENOSYS (%d), got '%s' (%d)", ENOSYS, strerror(errno), errno); res = proxy_netio_use(PR_NETIO_STRM_DATA, NULL); ck_assert_msg(res == 0, "Failed to handle data stream type: %s", strerror(errno)); netio = proxy_netio_unset(PR_NETIO_STRM_DATA, "testcase"); ck_assert_msg(netio == NULL, "Unset data stream unexpectedly"); ck_assert_msg(errno == ENOSYS, "Expected ENOSYS (%d), got '%s' (%d)", ENOSYS, strerror(errno), errno); } END_TEST Suite *tests_get_netio_suite(void) { Suite *suite; TCase *testcase; suite = suite_create("netio"); testcase = tcase_create("base"); tcase_add_checked_fixture(testcase, set_up, tear_down); tcase_add_test(testcase, netio_close_test); tcase_add_test(testcase, netio_open_test); tcase_add_test(testcase, netio_poll_test); tcase_add_test(testcase, netio_postopen_test); tcase_add_test(testcase, netio_printf_test); tcase_add_test(testcase, netio_read_test); tcase_add_test(testcase, netio_reset_poll_interval_test); tcase_add_test(testcase, netio_set_poll_interval_test); tcase_add_test(testcase, netio_shutdown_test); tcase_add_test(testcase, netio_write_test); tcase_add_test(testcase, netio_set_test); tcase_add_test(testcase, netio_use_test); suite_add_tcase(suite, testcase); return suite; } proftpd-mod_proxy-0.9.5/t/api/random.c000066400000000000000000000054771475737016700177170ustar00rootroot00000000000000/* * ProFTPD - mod_proxy testsuite * Copyright (c) 2013-2022 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ /* Random API tests. */ #include "tests.h" static pool *p = NULL; static void set_up(void) { if (p == NULL) { p = make_sub_pool(NULL); session.c = NULL; session.notes = NULL; } proxy_random_init(); } static void tear_down(void) { if (p) { destroy_pool(p); p = NULL; session.c = NULL; session.notes = NULL; } } START_TEST (random_next_range_10_test) { register unsigned int i; long min, max; min = -4; max = 5; for (i = 0; i < 10; i++) { long num; num = proxy_random_next(min, max); ck_assert_msg(num >= min, "random number %ld less than minimum %ld", num, min); ck_assert_msg(num <= max, "random number %ld greater than maximum %ld", num, max); } } END_TEST START_TEST (random_next_range_1000_test) { register int i; long min, max; int count = 10, seen[10]; min = 0; max = count-1; memset(seen, 0, sizeof(seen)); for (i = 0; i < 1000; i++) { long num; num = proxy_random_next(min, max); ck_assert_msg(num >= min, "random number %ld less than minimum %ld", num, min); ck_assert_msg(num <= max, "random number %ld greater than maximum %ld", num, max); seen[num] = 1; } /* In 1000 rounds, the chances of seeing all 10 possible numbers is pretty * good, right? */ for (i = 0; i < count; i++) { ck_assert_msg(seen[i] == 1, "Expected to have generated number %d", i); } } END_TEST Suite *tests_get_random_suite(void) { Suite *suite; TCase *testcase; suite = suite_create("random"); testcase = tcase_create("base"); tcase_add_checked_fixture(testcase, set_up, tear_down); tcase_add_test(testcase, random_next_range_10_test); tcase_add_test(testcase, random_next_range_1000_test); suite_add_tcase(suite, testcase); return suite; } proftpd-mod_proxy-0.9.5/t/api/reverse.c000066400000000000000000000756021475737016700201070ustar00rootroot00000000000000/* * ProFTPD - mod_proxy testsuite * Copyright (c) 2013-2022 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ /* Reverse-proxy API tests */ #include "tests.h" extern xaset_t *server_list; static pool *p = NULL; static const char *test_dir = "/tmp/mod_proxy-test-reverse"; static const char *test_file = "/tmp/mod_proxy-test-reverse/servers.json"; static config_rec *policy_config = NULL; static void create_main_server(void) { server_rec *s; s = pr_parser_server_ctxt_open("127.0.0.1"); s->ServerName = "Test Server"; main_server = s; } static void test_cleanup(pool *cleanup_pool) { (void) unlink(test_file); (void) tests_rmpath(cleanup_pool, test_dir); } static FILE *test_prep(void) { int res; mode_t perms; FILE *fh; perms = 0770; res = mkdir(test_dir, perms); if (res < 0 && errno != EEXIST) { ck_assert_msg(res == 0, "Failed to create tmp directory '%s': %s", test_dir, strerror(errno)); } res = chmod(test_dir, perms); ck_assert_msg(res == 0, "Failed to set perms %04o on directory '%s': %s", perms, test_dir, strerror(errno)); fh = fopen(test_file, "w+"); ck_assert_msg(fh != NULL, "Failed to create tmp file '%s': %s", test_file, strerror(errno)); perms = 0660; res = chmod(test_file, perms); ck_assert_msg(res == 0, "Failed to set perms %04o on file '%s': %s", perms, test_file, strerror(errno)); return fh; } static void set_up(void) { if (p == NULL) { p = permanent_pool = session.pool = make_sub_pool(NULL); main_server = NULL; server_list = NULL; session.c = NULL; session.notes = NULL; } test_cleanup(p); init_config(); init_fs(); init_netaddr(); init_netio(); init_inet(); server_list = xaset_create(p, NULL); pr_parser_prepare(p, &server_list); create_main_server(); proxy_db_init(p); if (getenv("TEST_VERBOSE") != NULL) { pr_trace_set_levels("netio", 1, 20); pr_trace_set_levels("proxy.conn", 1, 20); pr_trace_set_levels("proxy.db", 1, 20); pr_trace_set_levels("proxy.reverse", 1, 20); pr_trace_set_levels("proxy.tls", 1, 20); pr_trace_set_levels("proxy.uri", 1, 20); pr_trace_set_levels("proxy.ftp.ctrl", 1, 20); pr_trace_set_levels("proxy.ftp.sess", 1, 20); } pr_inet_set_default_family(p, AF_INET); } static void tear_down(void) { pr_inet_set_default_family(p, 0); if (getenv("TEST_VERBOSE") != NULL) { pr_trace_set_levels("netio", 0, 0); pr_trace_set_levels("proxy.conn", 0, 0); pr_trace_set_levels("proxy.db", 0, 0); pr_trace_set_levels("proxy.reverse", 0, 0); pr_trace_set_levels("proxy.tls", 0, 0); pr_trace_set_levels("proxy.uri", 0, 0); pr_trace_set_levels("proxy.ftp.ctrl", 0, 0); pr_trace_set_levels("proxy.ftp.sess", 0, 0); } pr_inet_clear(); pr_parser_cleanup(); proxy_db_free(); test_cleanup(p); if (p) { destroy_pool(p); p = permanent_pool = session.pool = NULL; main_server = NULL; server_list = NULL; session.c = NULL; session.notes = NULL; } } START_TEST (reverse_free_test) { int res; res = proxy_reverse_free(NULL); ck_assert_msg(res < 0, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_reverse_free(p); ck_assert_msg(res == 0, "Failed to free Reverse API resources: %s", strerror(errno)); } END_TEST START_TEST (reverse_init_test) { int res, flags = PROXY_DB_OPEN_FL_SKIP_VACUUM; FILE *fh; res = proxy_reverse_init(NULL, NULL, flags); ck_assert_msg(res < 0, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_reverse_init(p, NULL, flags); ck_assert_msg(res < 0, "Failed to handle null tables dir"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); fh = test_prep(); fclose(fh); mark_point(); res = proxy_reverse_init(p, test_dir, flags); ck_assert_msg(res == 0, "Failed to init Reverse API resources: %s", strerror(errno)); res = proxy_reverse_free(p); ck_assert_msg(res == 0, "Failed to free Reverse API resources: %s", strerror(errno)); test_cleanup(p); } END_TEST START_TEST (reverse_sess_free_test) { int res; mark_point(); res = proxy_reverse_sess_free(p, NULL); ck_assert_msg(res == 0, "Failed to free Reverse API session resources: %s", strerror(errno)); } END_TEST START_TEST (reverse_sess_init_test) { int res, flags = PROXY_DB_OPEN_FL_SKIP_VACUUM; config_rec *c; array_header *backends; const char *uri; const struct proxy_conn *pconn; mark_point(); res = proxy_reverse_sess_init(NULL, NULL, NULL, flags); ck_assert_msg(res < 0, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_reverse_sess_init(p, NULL, NULL, flags); ck_assert_msg(res < 0, "Unexpectedly init'd Reverse API session resources"); ck_assert_msg(errno == EPERM, "Expected EPERM (%d), got '%s' (%d)", EPERM, strerror(errno), errno); c = add_config_param("ProxyReverseServers", 2, NULL, NULL); backends = make_array(c->pool, 1, sizeof(struct proxy_conn *)); uri = "ftp://127.0.0.1:21"; pconn = proxy_conn_create(c->pool, uri, 0); *((const struct proxy_conn **) push_array(backends)) = pconn; c->argv[0] = backends; c = add_config_param("ProxyReverseServers", 2, NULL, NULL); backends = make_array(c->pool, 1, sizeof(struct proxy_conn *)); uri = "ftp://127.0.0.1:2121"; pconn = proxy_conn_create(c->pool, uri, 0); *((const struct proxy_conn **) push_array(backends)) = pconn; c->argv[0] = backends; mark_point(); res = proxy_reverse_sess_init(NULL, NULL, NULL, flags); ck_assert_msg(res < 0, "Unexpectedly init'd Reverse API session resources"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_reverse_sess_free(p, NULL); ck_assert_msg(res == 0, "Failed to free Reverse API session resources: %s", strerror(errno)); } END_TEST static int test_connect_policy(int policy_id, array_header *src_backends) { int flags = PROXY_DB_OPEN_FL_SKIP_VACUUM; FILE *fh; config_rec *c; array_header *backends; fh = test_prep(); fclose(fh); mark_point(); if (policy_config != NULL) { c = policy_config; } else { policy_config = c = add_config_param("ProxyReverseConnectPolicy", 1, NULL); c->argv[0] = palloc(c->pool, sizeof(int)); } *((int *) c->argv[0]) = policy_id; mark_point(); c = add_config_param("ProxyReverseServers", 2, NULL, NULL); backends = make_array(c->pool, 1, sizeof(struct proxy_conn *)); if (src_backends == NULL) { const char *uri; const struct proxy_conn *pconn; uri = "ftp://127.0.0.1:21"; pconn = proxy_conn_create(c->pool, uri, 0); *((const struct proxy_conn **) push_array(backends)) = pconn; } else { array_cat(backends, src_backends); } c->argv[0] = backends; mark_point(); return proxy_reverse_init(p, test_dir, flags); } START_TEST (reverse_connect_policy_random_test) { int res; res = test_connect_policy(PROXY_REVERSE_CONNECT_POLICY_RANDOM, NULL); ck_assert_msg(res == 0, "Failed to test ReverseConnectPolicy Random: %s", strerror(errno)); mark_point(); res = proxy_reverse_sess_exit(p); ck_assert_msg(res == 0, "Failed to exit session: %s", strerror(errno)); mark_point(); res = proxy_reverse_free(p); ck_assert_msg(res == 0, "Failed to free Reverse API resources: %s", strerror(errno)); test_cleanup(p); } END_TEST START_TEST (reverse_connect_policy_roundrobin_test) { int res; res = test_connect_policy(PROXY_REVERSE_CONNECT_POLICY_ROUND_ROBIN, NULL); ck_assert_msg(res == 0, "Failed to test ReverseConnectPolicy RoundRobin: %s", strerror(errno)); mark_point(); res = proxy_reverse_sess_exit(p); ck_assert_msg(res == 0, "Failed to exit session: %s", strerror(errno)); mark_point(); res = proxy_reverse_free(p); ck_assert_msg(res == 0, "Failed to free Reverse API resources: %s", strerror(errno)); test_cleanup(p); } END_TEST START_TEST (reverse_connect_policy_leastconns_test) { int res; res = test_connect_policy(PROXY_REVERSE_CONNECT_POLICY_LEAST_CONNS, NULL); ck_assert_msg(res == 0, "Failed to test ReverseConnectPolicy LeastConns: %s", strerror(errno)); mark_point(); res = proxy_reverse_sess_exit(p); ck_assert_msg(res == 0, "Failed to exit session: %s", strerror(errno)); mark_point(); res = proxy_reverse_free(p); ck_assert_msg(res == 0, "Failed to free Reverse API resources: %s", strerror(errno)); test_cleanup(p); } END_TEST START_TEST (reverse_connect_policy_leastresponsetime_test) { int res; res = test_connect_policy(PROXY_REVERSE_CONNECT_POLICY_LEAST_RESPONSE_TIME, NULL); ck_assert_msg(res == 0, "Failed to test ReverseConnectPolicy LeastResponseTime: %s", strerror(errno)); mark_point(); res = proxy_reverse_sess_exit(p); ck_assert_msg(res == 0, "Failed to exit session: %s", strerror(errno)); mark_point(); res = proxy_reverse_free(p); ck_assert_msg(res == 0, "Failed to free Reverse API resources: %s", strerror(errno)); test_cleanup(p); } END_TEST START_TEST (reverse_connect_policy_shuffle_test) { int res; res = test_connect_policy(PROXY_REVERSE_CONNECT_POLICY_SHUFFLE, NULL); ck_assert_msg(res == 0, "Failed to test ReverseConnectPolicy Shuffle: %s", strerror(errno)); mark_point(); res = proxy_reverse_sess_exit(p); ck_assert_msg(res == 0, "Failed to exit session: %s", strerror(errno)); mark_point(); res = proxy_reverse_free(p); ck_assert_msg(res == 0, "Failed to free Reverse API resources: %s", strerror(errno)); test_cleanup(p); } END_TEST START_TEST (reverse_connect_policy_peruser_test) { int res; res = test_connect_policy(PROXY_REVERSE_CONNECT_POLICY_PER_USER, NULL); ck_assert_msg(res == 0, "Failed to test ReverseConnectPolicy PerUser: %s", strerror(errno)); mark_point(); res = proxy_reverse_sess_exit(p); ck_assert_msg(res == 0, "Failed to exit session: %s", strerror(errno)); mark_point(); res = proxy_reverse_free(p); ck_assert_msg(res == 0, "Failed to free Reverse API resources: %s", strerror(errno)); test_cleanup(p); } END_TEST START_TEST (reverse_connect_policy_pergroup_test) { int res; /* Note: This should fail without having the UseReverseProxyAuth ProxyOption * enabled. */ res = test_connect_policy(PROXY_REVERSE_CONNECT_POLICY_PER_GROUP, NULL); ck_assert_msg(res < 0, "Expected ReverseConnectPolicy PerGroup to fail"); ck_assert_msg(errno == EPERM, "Expected EPERM (%d), got %s (%d)", EPERM, strerror(errno), errno); mark_point(); res = proxy_reverse_sess_exit(p); ck_assert_msg(res == 0, "Failed to exit session: %s", strerror(errno)); mark_point(); res = proxy_reverse_free(p); ck_assert_msg(res == 0, "Failed to free Reverse API resources: %s", strerror(errno)); test_cleanup(p); } END_TEST START_TEST (reverse_connect_policy_perhost_test) { int res; res = test_connect_policy(PROXY_REVERSE_CONNECT_POLICY_PER_HOST, NULL); ck_assert_msg(res == 0, "Failed to test ReverseConnectPolicy PerHost: %s", strerror(errno)); mark_point(); res = proxy_reverse_sess_exit(p); ck_assert_msg(res == 0, "Failed to exit session: %s", strerror(errno)); mark_point(); res = proxy_reverse_free(p); ck_assert_msg(res == 0, "Failed to free Reverse API resources: %s", strerror(errno)); test_cleanup(p); } END_TEST static void test_handle_user_pass(int policy_id, array_header *src_backends) { int res, successful = FALSE, block_responses = FALSE; int flags = PROXY_DB_OPEN_FL_SKIP_VACUUM; struct proxy_session *proxy_sess; cmd_rec *cmd; FILE *fh; fh = test_prep(); fclose(fh); mark_point(); res = test_connect_policy(PROXY_REVERSE_CONNECT_POLICY_RANDOM, src_backends); ck_assert_msg(res == 0, "Failed to test ReverseConnectPolicy Random: %s", strerror(errno)); proxy_sess = (struct proxy_session *) proxy_session_alloc(p); session.notes = pr_table_alloc(p, 0); pr_table_add(session.notes, "mod_proxy.proxy-session", proxy_sess, sizeof(struct proxy_session)); session.c = pr_inet_create_conn(p, -1, NULL, INPORT_ANY, FALSE); ck_assert_msg(session.c != NULL, "Failed to open session control conn: %s", strerror(errno)); session.c->local_addr = session.c->remote_addr = pr_netaddr_get_addr(p, "127.0.0.1", NULL); ck_assert_msg(session.c->remote_addr != NULL, "Failed to get address: %s", strerror(errno)); mark_point(); res = proxy_reverse_sess_init(p, test_dir, proxy_sess, flags); ck_assert_msg(res == 0, "Failed to init Reverse API session resources: %s", strerror(errno)); cmd = pr_cmd_alloc(p, 2, "USER", "anonymous"); cmd->arg = pstrdup(p, "anonymous"); mark_point(); res = proxy_reverse_handle_user(cmd, proxy_sess, &successful, &block_responses); ck_assert_msg(res == 1, "Failed to handle USER"); cmd = pr_cmd_alloc(p, 2, "PASS", "ftp@nospam.org"); cmd->arg = pstrdup(p, "ftp@nospam.org"); mark_point(); res = proxy_reverse_handle_pass(cmd, proxy_sess, &successful, &block_responses); ck_assert_msg(res < 0, "Handled PASS unexpectedly"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_reverse_sess_exit(p); ck_assert_msg(res == 0, "Failed to exit session: %s", strerror(errno)); mark_point(); res = proxy_reverse_free(p); ck_assert_msg(res == 0, "Failed to free Reverse API resources: %s", strerror(errno)); proxy_session_free(p, proxy_sess); test_cleanup(p); } START_TEST (reverse_handle_user_pass_random_test) { const char *uri; const struct proxy_conn *pconn; array_header *backends; /* Skip this test on CI builds, for now. It fails unexpectedly. */ if (getenv("CI") != NULL || getenv("TRAVIS") != NULL) { return; } backends = make_array(p, 1, sizeof(struct proxy_conn *)); uri = "ftp://127.0.0.1:21"; pconn = proxy_conn_create(p, uri, 0); *((const struct proxy_conn **) push_array(backends)) = pconn; uri = "ftp://ftp.microsoft.com:21"; pconn = proxy_conn_create(p, uri, 0); *((const struct proxy_conn **) push_array(backends)) = pconn; test_handle_user_pass(PROXY_REVERSE_CONNECT_POLICY_RANDOM, backends); test_handle_user_pass(PROXY_REVERSE_CONNECT_POLICY_RANDOM, backends); } END_TEST START_TEST (reverse_handle_user_pass_roundrobin_test) { const char *uri; const struct proxy_conn *pconn; array_header *backends; /* Skip this test on CI builds, for now. It fails unexpectedly. */ if (getenv("CI") != NULL || getenv("TRAVIS") != NULL) { return; } backends = make_array(p, 1, sizeof(struct proxy_conn *)); uri = "ftp://127.0.0.1:21"; pconn = proxy_conn_create(p, uri, 0); *((const struct proxy_conn **) push_array(backends)) = pconn; uri = "ftp://ftp.microsoft.com:21"; pconn = proxy_conn_create(p, uri, 0); *((const struct proxy_conn **) push_array(backends)) = pconn; test_handle_user_pass(PROXY_REVERSE_CONNECT_POLICY_ROUND_ROBIN, backends); test_handle_user_pass(PROXY_REVERSE_CONNECT_POLICY_ROUND_ROBIN, backends); } END_TEST START_TEST (reverse_handle_user_pass_leastconns_test) { const char *uri; const struct proxy_conn *pconn; array_header *backends; /* Skip this test on CI builds, for now. It fails unexpectedly. */ if (getenv("CI") != NULL || getenv("TRAVIS") != NULL) { return; } backends = make_array(p, 1, sizeof(struct proxy_conn *)); uri = "ftp://127.0.0.1:21"; pconn = proxy_conn_create(p, uri, 0); *((const struct proxy_conn **) push_array(backends)) = pconn; uri = "ftp://ftp.microsoft.com:21"; pconn = proxy_conn_create(p, uri, 0); *((const struct proxy_conn **) push_array(backends)) = pconn; test_handle_user_pass(PROXY_REVERSE_CONNECT_POLICY_LEAST_CONNS, backends); test_handle_user_pass(PROXY_REVERSE_CONNECT_POLICY_LEAST_CONNS, backends); } END_TEST START_TEST (reverse_handle_user_pass_leastresponsetime_test) { const char *uri; const struct proxy_conn *pconn; array_header *backends; /* Skip this test on CI builds, for now. It fails unexpectedly. */ if (getenv("CI") != NULL || getenv("TRAVIS") != NULL) { return; } backends = make_array(p, 1, sizeof(struct proxy_conn *)); uri = "ftp://127.0.0.1:21"; pconn = proxy_conn_create(p, uri, 0); *((const struct proxy_conn **) push_array(backends)) = pconn; uri = "ftp://ftp.microsoft.com:21"; pconn = proxy_conn_create(p, uri, 0); *((const struct proxy_conn **) push_array(backends)) = pconn; test_handle_user_pass(PROXY_REVERSE_CONNECT_POLICY_LEAST_RESPONSE_TIME, backends); test_handle_user_pass(PROXY_REVERSE_CONNECT_POLICY_LEAST_RESPONSE_TIME, backends); } END_TEST START_TEST (reverse_handle_user_pass_shuffle_test) { const char *uri; const struct proxy_conn *pconn; array_header *backends; /* Skip this test on CI builds, for now. It fails unexpectedly. */ if (getenv("CI") != NULL || getenv("TRAVIS") != NULL) { return; } backends = make_array(p, 1, sizeof(struct proxy_conn *)); uri = "ftp://127.0.0.1:21"; pconn = proxy_conn_create(p, uri, 0); *((const struct proxy_conn **) push_array(backends)) = pconn; uri = "ftp://ftp.microsoft.com:21"; pconn = proxy_conn_create(p, uri, 0); *((const struct proxy_conn **) push_array(backends)) = pconn; test_handle_user_pass(PROXY_REVERSE_CONNECT_POLICY_SHUFFLE, backends); test_handle_user_pass(PROXY_REVERSE_CONNECT_POLICY_SHUFFLE, backends); } END_TEST START_TEST (reverse_handle_user_pass_peruser_test) { const char *uri; const struct proxy_conn *pconn; array_header *backends; /* Skip this test on CI builds, for now. It fails unexpectedly. */ if (getenv("CI") != NULL || getenv("TRAVIS") != NULL) { return; } backends = make_array(p, 1, sizeof(struct proxy_conn *)); uri = "ftp://127.0.0.1:21"; pconn = proxy_conn_create(p, uri, 0); *((const struct proxy_conn **) push_array(backends)) = pconn; uri = "ftp://ftp.microsoft.com:21"; pconn = proxy_conn_create(p, uri, 0); *((const struct proxy_conn **) push_array(backends)) = pconn; test_handle_user_pass(PROXY_REVERSE_CONNECT_POLICY_PER_USER, backends); test_handle_user_pass(PROXY_REVERSE_CONNECT_POLICY_PER_USER, backends); } END_TEST START_TEST (reverse_handle_user_pass_pergroup_test) { const char *uri; const struct proxy_conn *pconn; array_header *backends; /* Skip this test on CI builds, for now. It fails unexpectedly. */ if (getenv("CI") != NULL || getenv("TRAVIS") != NULL) { return; } backends = make_array(p, 1, sizeof(struct proxy_conn *)); uri = "ftp://127.0.0.1:21"; pconn = proxy_conn_create(p, uri, 0); *((const struct proxy_conn **) push_array(backends)) = pconn; uri = "ftp://ftp.microsoft.com:21"; pconn = proxy_conn_create(p, uri, 0); *((const struct proxy_conn **) push_array(backends)) = pconn; test_handle_user_pass(PROXY_REVERSE_CONNECT_POLICY_PER_GROUP, backends); test_handle_user_pass(PROXY_REVERSE_CONNECT_POLICY_PER_GROUP, backends); } END_TEST START_TEST (reverse_handle_user_pass_perhost_test) { const char *uri; const struct proxy_conn *pconn; array_header *backends; /* Skip this test on CI builds, for now. It fails unexpectedly. */ if (getenv("CI") != NULL || getenv("TRAVIS") != NULL) { return; } backends = make_array(p, 1, sizeof(struct proxy_conn *)); uri = "ftp://127.0.0.1:21"; pconn = proxy_conn_create(p, uri, 0); *((const struct proxy_conn **) push_array(backends)) = pconn; uri = "ftp://ftp.microsoft.com:21"; pconn = proxy_conn_create(p, uri, 0); *((const struct proxy_conn **) push_array(backends)) = pconn; test_handle_user_pass(PROXY_REVERSE_CONNECT_POLICY_PER_HOST, backends); test_handle_user_pass(PROXY_REVERSE_CONNECT_POLICY_PER_HOST, backends); } END_TEST START_TEST (reverse_json_parse_uris_args_test) { array_header *uris; const char *path; uris = proxy_reverse_json_parse_uris(NULL, NULL, 0); ck_assert_msg(uris == NULL, "Failed to handle null arguments"); ck_assert_msg(errno == EINVAL, "Failed to set errno to EINVAL"); uris = proxy_reverse_json_parse_uris(p, NULL, 0); ck_assert_msg(uris == NULL, "Failed to handle null path argument"); ck_assert_msg(errno == EINVAL, "Failed to set errno to EINVAL"); path = "/tmp/test.dat"; uris = proxy_reverse_json_parse_uris(NULL, path, 0); ck_assert_msg(uris == NULL, "Failed to handle null pool argument"); ck_assert_msg(errno == EINVAL, "Failed to set errno to EINVAL"); } END_TEST START_TEST (reverse_json_parse_uris_isreg_test) { array_header *uris; const char *path; int res; test_cleanup(p); path = "servers.json"; uris = proxy_reverse_json_parse_uris(p, path, 0); ck_assert_msg(uris == NULL, "Failed to handle relative path '%s'", path); ck_assert_msg(errno == EINVAL, "Failed to set errno to EINVAL"); path = test_file; uris = proxy_reverse_json_parse_uris(p, path, 0); ck_assert_msg(uris == NULL, "Failed to handle nonexistent file '%s'", path); ck_assert_msg(errno == ENOENT, "Failed to set errno to ENOENT"); res = mkdir(test_dir, 0777); ck_assert_msg(res == 0, "Failed to create tmp directory '%s': %s", test_dir, strerror(errno)); uris = proxy_reverse_json_parse_uris(p, test_dir, 0); ck_assert_msg(uris == NULL, "Failed to handle directory path '%s'", test_dir); ck_assert_msg(errno == EISDIR, "Failed to set errno to EISDIR"); test_cleanup(p); } END_TEST START_TEST (reverse_json_parse_uris_perms_test) { array_header *uris; const char *path; int fd, res; mode_t perms; /* Note: any extra chmods are necessary to workaround any umask in the * environment. Sigh. */ perms = 0777; res = mkdir(test_dir, perms); ck_assert_msg(res == 0, "Failed to create tmp directory '%s': %s", test_dir, strerror(errno)); res = chmod(test_dir, perms); ck_assert_msg(res == 0, "Failed to set perms %04o on directory '%s': %s", perms, test_dir, strerror(errno)); /* First, make a world-writable file. */ perms = 0666; fd = open(test_file, O_WRONLY|O_CREAT, perms); ck_assert_msg(fd >= 0, "Failed to create tmp file '%s': %s", test_file, strerror(errno)); res = fchmod(fd, perms); ck_assert_msg(res == 0, "Failed to set perms %04o on file '%s': %s", perms, test_file, strerror(errno)); path = test_file; uris = proxy_reverse_json_parse_uris(p, path, 0); ck_assert_msg(uris == NULL, "Failed to handle world-writable file '%s'", path); ck_assert_msg(errno == EPERM, "Failed to set errno to EPERM, got %d (%s)", errno, strerror(errno)); /* Now make the file user/group-writable only, but leave the parent * directory world-writable. */ perms = 0660; res = fchmod(fd, perms); ck_assert_msg(res == 0, "Failed to set perms %04o on file '%s': %s", perms, test_file, strerror(errno)); uris = proxy_reverse_json_parse_uris(p, path, 0); ck_assert_msg(uris == NULL, "Failed to handle world-writable directory '%s'", test_file); ck_assert_msg(errno == EPERM, "Failed to set errno to EPERM, got %d (%s)", errno, strerror(errno)); (void) close(fd); test_cleanup(p); } END_TEST START_TEST (reverse_json_parse_uris_empty_test) { array_header *uris; FILE *fh = NULL; int res; test_cleanup(p); fh = test_prep(); /* Write a file with no lines. */ res = fclose(fh); ck_assert_msg(res >= 0, "Failed to write file '%s': %s", test_file, strerror(errno)); mark_point(); uris = proxy_reverse_json_parse_uris(p, test_file, 0); ck_assert_msg(uris != NULL, "Did not receive parsed list as expected"); ck_assert_msg(uris->nelts == 0, "Expected zero elements, found %d", uris->nelts); test_cleanup(p); } END_TEST START_TEST (reverse_json_parse_uris_malformed_test) { array_header *uris; FILE *fh = NULL; int res; test_cleanup(p); fh = test_prep(); fprintf(fh, "[ \"http://127.0.0.1:80\",\n"); fprintf(fh, "\"ftp:/127.0.0.1::21\",\n"); fprintf(fh, "\"ftp://foo.bar.baz:21\" ]\n"); res = fclose(fh); ck_assert_msg(res >= 0, "Failed to write file '%s': %s", test_file, strerror(errno)); mark_point(); uris = proxy_reverse_json_parse_uris(p, test_file, 0); ck_assert_msg(uris != NULL, "Did not receive parsed list as expected"); ck_assert_msg(uris->nelts == 0, "Expected zero elements, found %d", uris->nelts); test_cleanup(p); } END_TEST START_TEST (reverse_json_parse_uris_usable_test) { array_header *uris; FILE *fh = NULL; int res; unsigned int expected; test_cleanup(p); fh = test_prep(); /* Write a file with usable URLs. */ fprintf(fh, "[ \"ftp://127.0.0.1\",\n"); fprintf(fh, "\"ftp://localhost:2121\",\n"); fprintf(fh, "\"ftp://[::1]:21212\" ]\n"); res = fclose(fh); ck_assert_msg(res >= 0, "Failed to write file '%s': %s", test_file, strerror(errno)); mark_point(); uris = proxy_reverse_json_parse_uris(p, test_file, 0); ck_assert_msg(uris != NULL, "Did not receive parsed list as expected"); expected = 3; ck_assert_msg(uris->nelts == expected, "Expected %d elements, found %d", expected, uris->nelts); test_cleanup(p); } END_TEST START_TEST (reverse_connect_get_policy_id_test) { int res; const char *policy; res = proxy_reverse_connect_get_policy_id(NULL); ck_assert_msg(res < 0, "Failed to handle null argument"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); policy = "foo"; res = proxy_reverse_connect_get_policy_id(policy); ck_assert_msg(res < 0, "Failed to handle unsupported policy '%s'", policy); ck_assert_msg(errno == ENOENT, "Expected ENOENT (%d), got '%s' (%d)", ENOENT, strerror(errno), errno); policy = "random2"; res = proxy_reverse_connect_get_policy_id(policy); ck_assert_msg(res < 0, "Failed to handle unsupported policy '%s'", policy); ck_assert_msg(errno == ENOENT, "Expected ENOENT (%d), got '%s' (%d)", ENOENT, strerror(errno), errno); policy = "random"; res = proxy_reverse_connect_get_policy_id(policy); ck_assert_msg(res == PROXY_REVERSE_CONNECT_POLICY_RANDOM, "Failed to handle supported policy '%s'", policy); policy = "roundrobin"; res = proxy_reverse_connect_get_policy_id(policy); ck_assert_msg(res == PROXY_REVERSE_CONNECT_POLICY_ROUND_ROBIN, "Failed to handle supported policy '%s'", policy); policy = "shuffle"; res = proxy_reverse_connect_get_policy_id(policy); ck_assert_msg(res == PROXY_REVERSE_CONNECT_POLICY_SHUFFLE, "Failed to handle supported policy '%s'", policy); policy = "leastconns"; res = proxy_reverse_connect_get_policy_id(policy); ck_assert_msg(res == PROXY_REVERSE_CONNECT_POLICY_LEAST_CONNS, "Failed to handle supported policy '%s'", policy); policy = "peruser"; res = proxy_reverse_connect_get_policy_id(policy); ck_assert_msg(res == PROXY_REVERSE_CONNECT_POLICY_PER_USER, "Failed to handle supported policy '%s'", policy); policy = "pergroup"; res = proxy_reverse_connect_get_policy_id(policy); ck_assert_msg(res == PROXY_REVERSE_CONNECT_POLICY_PER_GROUP, "Failed to handle supported policy '%s'", policy); policy = "perhost"; res = proxy_reverse_connect_get_policy_id(policy); ck_assert_msg(res == PROXY_REVERSE_CONNECT_POLICY_PER_HOST, "Failed to handle supported policy '%s'", policy); policy = "leastresponsetime"; res = proxy_reverse_connect_get_policy_id(policy); ck_assert_msg(res == PROXY_REVERSE_CONNECT_POLICY_LEAST_RESPONSE_TIME, "Failed to handle supported policy '%s'", policy); } END_TEST START_TEST (reverse_use_proxy_auth_test) { int res; res = proxy_reverse_use_proxy_auth(); ck_assert_msg(res == FALSE, "Expected false, got %d", res); } END_TEST START_TEST (reverse_have_authenticated_test) { int res; cmd_rec *cmd = NULL; res = proxy_reverse_have_authenticated(cmd); ck_assert_msg(res == FALSE, "Expected false, got %d", res); proxy_sess_state |= PROXY_SESS_STATE_BACKEND_AUTHENTICATED; res = proxy_reverse_have_authenticated(cmd); ck_assert_msg(res == TRUE, "Expected true, got %d", res); proxy_sess_state &= ~PROXY_SESS_STATE_BACKEND_AUTHENTICATED; } END_TEST Suite *tests_get_reverse_suite(void) { Suite *suite; TCase *testcase; suite = suite_create("reverse"); testcase = tcase_create("base"); tcase_add_checked_fixture(testcase, set_up, tear_down); tcase_add_test(testcase, reverse_free_test); tcase_add_test(testcase, reverse_init_test); tcase_add_test(testcase, reverse_sess_free_test); tcase_add_test(testcase, reverse_sess_init_test); tcase_add_test(testcase, reverse_connect_policy_random_test); tcase_add_test(testcase, reverse_connect_policy_roundrobin_test); tcase_add_test(testcase, reverse_connect_policy_leastconns_test); tcase_add_test(testcase, reverse_connect_policy_leastresponsetime_test); tcase_add_test(testcase, reverse_connect_policy_shuffle_test); tcase_add_test(testcase, reverse_connect_policy_peruser_test); tcase_add_test(testcase, reverse_connect_policy_pergroup_test); tcase_add_test(testcase, reverse_connect_policy_perhost_test); tcase_add_test(testcase, reverse_handle_user_pass_random_test); tcase_add_test(testcase, reverse_handle_user_pass_roundrobin_test); tcase_add_test(testcase, reverse_handle_user_pass_leastconns_test); tcase_add_test(testcase, reverse_handle_user_pass_leastresponsetime_test); tcase_add_test(testcase, reverse_handle_user_pass_shuffle_test); tcase_add_test(testcase, reverse_handle_user_pass_peruser_test); tcase_add_test(testcase, reverse_handle_user_pass_pergroup_test); tcase_add_test(testcase, reverse_handle_user_pass_perhost_test); tcase_add_test(testcase, reverse_json_parse_uris_args_test); tcase_add_test(testcase, reverse_json_parse_uris_isreg_test); tcase_add_test(testcase, reverse_json_parse_uris_perms_test); tcase_add_test(testcase, reverse_json_parse_uris_empty_test); tcase_add_test(testcase, reverse_json_parse_uris_malformed_test); tcase_add_test(testcase, reverse_json_parse_uris_usable_test); tcase_add_test(testcase, reverse_connect_get_policy_id_test); tcase_add_test(testcase, reverse_use_proxy_auth_test); tcase_add_test(testcase, reverse_have_authenticated_test); suite_add_tcase(suite, testcase); return suite; } proftpd-mod_proxy-0.9.5/t/api/session.c000066400000000000000000000170401475737016700201070ustar00rootroot00000000000000/* * ProFTPD - mod_proxy testsuite * Copyright (c) 2016-2022 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ /* Session API tests. */ #include "tests.h" extern xaset_t *server_list; static pool *p = NULL; static void create_main_server(void) { server_rec *s; s = pr_parser_server_ctxt_open("127.0.0.1"); s->ServerName = "Test Server"; main_server = s; } static void set_up(void) { if (p == NULL) { p = permanent_pool = session.pool = make_sub_pool(NULL); main_server = NULL; server_list = NULL; session.c = NULL; session.notes = NULL; } init_config(); init_netaddr(); init_netio(); init_inet(); init_auth(); server_list = xaset_create(p, NULL); pr_parser_prepare(p, &server_list); create_main_server(); if (getenv("TEST_VERBOSE") != NULL) { pr_trace_set_levels("proxy.session", 1, 20); } pr_inet_set_default_family(p, AF_INET); } static void tear_down(void) { pr_inet_set_default_family(p, 0); if (getenv("TEST_VERBOSE") != NULL) { pr_trace_set_levels("proxy.session", 0, 0); } pr_parser_cleanup(); pr_inet_clear(); if (p) { destroy_pool(p); p = permanent_pool = session.pool = NULL; main_server = NULL; server_list = NULL; session.c = NULL; session.notes = NULL; } } START_TEST (session_free_test) { int res; res = proxy_session_free(NULL, NULL); ck_assert_msg(res < 0, "Failed to handle null arguments"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_session_free(p, NULL); ck_assert_msg(res < 0, "Failed to handle null session"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); } END_TEST START_TEST (session_alloc_test) { struct proxy_session *proxy_sess; proxy_sess = (struct proxy_session *) proxy_session_alloc(NULL); ck_assert_msg(proxy_sess == NULL, "Failed to handle null argument"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); proxy_sess = (struct proxy_session *) proxy_session_alloc(p); ck_assert_msg(proxy_sess != NULL, "Failed to allocate proxy session: %s", strerror(errno)); ck_assert_msg(proxy_sess->use_ftp == TRUE, "Failed to use FTP by default"); ck_assert_msg(proxy_sess->use_ssh == FALSE, "Failed to not use SSH by default"); mark_point(); proxy_session_free(p, proxy_sess); proxy_sess = (struct proxy_session *) proxy_session_alloc(p); ck_assert_msg(proxy_sess != NULL, "Failed to allocate proxy session: %s", strerror(errno)); proxy_sess->frontend_data_conn = pr_inet_create_conn(p, -1, NULL, INPORT_ANY, FALSE); proxy_sess->backend_ctrl_conn = pr_inet_create_conn(p, -1, NULL, INPORT_ANY, FALSE); proxy_sess->backend_data_conn = pr_inet_create_conn(p, -1, NULL, INPORT_ANY, FALSE); mark_point(); proxy_session_free(p, proxy_sess); } END_TEST START_TEST (session_reset_dataxfer_test) { struct proxy_session *proxy_sess; int res; res = proxy_session_reset_dataxfer(NULL); ck_assert_msg(res < 0, "Failed to handle null proxy_sess"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); proxy_sess = (struct proxy_session *) proxy_session_alloc(p); ck_assert_msg(proxy_sess != NULL, "Failed to allocate proxy session: %s", strerror(errno)); res = proxy_session_reset_dataxfer(proxy_sess); ck_assert_msg(res == 0, "Failed to reset proxy session dataxfer: %s", strerror(errno)); mark_point(); proxy_session_free(p, proxy_sess); } END_TEST START_TEST (session_check_password_test) { int res; const char *user, *passwd; mark_point(); res = proxy_session_check_password(NULL, NULL, NULL); ck_assert_msg(res < 0, "Failed to handle null arguments"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_session_check_password(p, NULL, NULL); ck_assert_msg(res < 0, "Failed to handle null user"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); user = "foo"; mark_point(); res = proxy_session_check_password(p, user, NULL); ck_assert_msg(res < 0, "Failed to handle null passwd"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); passwd = "bar"; mark_point(); res = proxy_session_check_password(p, user, passwd); ck_assert_msg(res < 0, "Failed to handle unknown user"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); } END_TEST START_TEST (session_setup_env_test) { int res, flags = 0; const char *user; mark_point(); res = proxy_session_setup_env(NULL, NULL, flags); ck_assert_msg(res < 0, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_session_setup_env(p, NULL, flags); ck_assert_msg(res < 0, "Failed to handle null user"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); session.c = pr_inet_create_conn(p, -1, NULL, INPORT_ANY, FALSE); ck_assert_msg(session.c != NULL, "Failed to open session control conn: %s", strerror(errno)); session.c->remote_name = pstrdup(p, "127.0.0.1"); user = "foo"; mark_point(); res = proxy_session_setup_env(p, user, flags); ck_assert_msg(res == 0, "Failed to setup environment: %s", strerror(errno)); ck_assert_msg(proxy_sess_state & PROXY_SESS_STATE_PROXY_AUTHENTICATED, "Expected PROXY_AUTHENTICATED state set"); proxy_sess_state &= ~PROXY_SESS_STATE_PROXY_AUTHENTICATED; user = "root"; mark_point(); res = proxy_session_setup_env(p, user, flags); ck_assert_msg(res == 0, "Failed to setup environment: %s", strerror(errno)); ck_assert_msg(proxy_sess_state & PROXY_SESS_STATE_PROXY_AUTHENTICATED, "Expected PROXY_AUTHENTICATED state set"); proxy_sess_state &= ~PROXY_SESS_STATE_PROXY_AUTHENTICATED; pr_inet_close(p, session.c); session.c = NULL; } END_TEST Suite *tests_get_session_suite(void) { Suite *suite; TCase *testcase; suite = suite_create("session"); testcase = tcase_create("base"); tcase_add_checked_fixture(testcase, set_up, tear_down); tcase_add_test(testcase, session_free_test); tcase_add_test(testcase, session_alloc_test); tcase_add_test(testcase, session_reset_dataxfer_test); tcase_add_test(testcase, session_check_password_test); tcase_add_test(testcase, session_setup_env_test); suite_add_tcase(suite, testcase); return suite; } proftpd-mod_proxy-0.9.5/t/api/str.c000066400000000000000000000055241475737016700172400ustar00rootroot00000000000000/* * ProFTPD - mod_proxy testsuite * Copyright (c) 2020-2022 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ /* String API tests */ #include "tests.h" static pool *p = NULL; static void set_up(void) { if (p == NULL) { p = permanent_pool = make_sub_pool(NULL); } } static void tear_down(void) { if (p != NULL) { destroy_pool(p); p = permanent_pool = NULL; } } START_TEST (strnstr_test) { const char *s1, *s2; size_t len; char *res; mark_point(); res = proxy_strnstr(NULL, NULL, 0); ck_assert_msg(res == NULL, "Failed to handle null s1"); mark_point(); s1 = "haystack"; res = proxy_strnstr(s1, NULL, 0); ck_assert_msg(res == NULL, "Failed to handle null s2"); mark_point(); s2 = "needle"; res = proxy_strnstr(s1, s2, 0); ck_assert_msg(res == NULL, "Failed to handle zero len"); mark_point(); len = 2; res = proxy_strnstr(s1, s2, len); ck_assert_msg(res == NULL, "Expected null, got %p for len %lu", res, (unsigned long) len); mark_point(); s1 = " "; res = proxy_strnstr(s1, s2, len); ck_assert_msg(res == NULL, "Expected null, got %p for s1 spaces", res); mark_point(); s1 = "haystack"; s2 = ""; res = proxy_strnstr(s1, s2, len); ck_assert_msg(res == NULL, "Expected null, got %p for s2 empty", res); mark_point(); s1 = "haystack"; s2 = "haystack"; len = 8; res = proxy_strnstr(s1, s2, len); ck_assert_msg(res != NULL, "Expected %p, got %p for s1 == s2", s1, res); mark_point(); s1 = "haystack"; s2 = "sta"; len = 7; res = proxy_strnstr(s1, s2, len); ck_assert_msg(res != NULL, "Expected %p, got %p", s1 + 3, res); } END_TEST Suite *tests_get_str_suite(void) { Suite *suite; TCase *testcase; suite = suite_create("str"); testcase = tcase_create("base"); tcase_add_checked_fixture(testcase, set_up, tear_down); tcase_add_test(testcase, strnstr_test); suite_add_tcase(suite, testcase); return suite; } proftpd-mod_proxy-0.9.5/t/api/stubs.c000066400000000000000000000143661475737016700175740ustar00rootroot00000000000000/* * ProFTPD - mod_proxy API testsuite * Copyright (c) 2012-2018 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "tests.h" /* Stubs */ session_t session; int ServerUseReverseDNS = FALSE; server_rec *main_server = NULL; pid_t mpid = 1; unsigned char is_master = TRUE; volatile unsigned int recvd_signal_flags = 0; module *static_modules[] = { NULL }; module *loaded_modules = NULL; xaset_t *server_list = NULL; int proxy_logfd = -1; module proxy_module; pool *proxy_pool = NULL; unsigned long proxy_opts = 0UL; unsigned int proxy_sess_state = 0; int proxy_datastore = 1; void *proxy_datastore_data = NULL; size_t proxy_datastore_datasz = 0; static cmd_rec *next_cmd = NULL; int tests_rmpath(pool *p, const char *path) { DIR *dirh; struct dirent *dent; int res, xerrno = 0; if (path == NULL) { errno = EINVAL; return -1; } dirh = opendir(path); if (dirh == NULL) { xerrno = errno; /* Change the permissions in the directory, and try again. */ if (chmod(path, (mode_t) 0755) == 0) { dirh = opendir(path); } if (dirh == NULL) { pr_trace_msg("testsuite", 9, "error opening '%s': %s", path, strerror(xerrno)); errno = xerrno; return -1; } } while ((dent = readdir(dirh)) != NULL) { struct stat st; char *file; pr_signals_handle(); if (strncmp(dent->d_name, ".", 2) == 0 || strncmp(dent->d_name, "..", 3) == 0) { continue; } file = pdircat(p, path, dent->d_name, NULL); if (stat(file, &st) < 0) { pr_trace_msg("testsuite", 9, "unable to stat '%s': %s", file, strerror(errno)); continue; } if (S_ISDIR(st.st_mode)) { res = tests_rmpath(p, file); if (res < 0) { pr_trace_msg("testsuite", 9, "error removing directory '%s': %s", file, strerror(errno)); } } else { res = unlink(file); if (res < 0) { pr_trace_msg("testsuite", 9, "error removing file '%s': %s", file, strerror(errno)); } } } closedir(dirh); res = rmdir(path); if (res < 0) { xerrno = errno; pr_trace_msg("testsuite", 9, "error removing directory '%s': %s", path, strerror(xerrno)); errno = xerrno; } return res; } int tests_stubs_set_next_cmd(cmd_rec *cmd) { next_cmd = cmd; return 0; } int login_check_limits(xaset_t *set, int recurse, int and, int *found) { return TRUE; } int xferlog_open(const char *path) { return 0; } int pr_cmd_read(cmd_rec **cmd) { if (next_cmd != NULL) { *cmd = next_cmd; next_cmd = NULL; } else { errno = ENOENT; *cmd = NULL; } return 0; } int pr_config_get_server_xfer_bufsz(int direction) { int bufsz = -1; switch (direction) { case PR_NETIO_IO_RD: bufsz = PR_TUNABLE_DEFAULT_RCVBUFSZ; break; case PR_NETIO_IO_WR: bufsz = PR_TUNABLE_DEFAULT_SNDBUFSZ; break; default: errno = EINVAL; return -1; } return bufsz; } void pr_log_auth(int priority, const char *fmt, ...) { if (getenv("TEST_VERBOSE") != NULL) { va_list msg; fprintf(stderr, "AUTH: "); va_start(msg, fmt); vfprintf(stderr, fmt, msg); va_end(msg); fprintf(stderr, "\n"); } } void pr_log_debug(int level, const char *fmt, ...) { if (getenv("TEST_VERBOSE") != NULL) { va_list msg; fprintf(stderr, "DEBUG%d: ", level); va_start(msg, fmt); vfprintf(stderr, fmt, msg); va_end(msg); fprintf(stderr, "\n"); } } int pr_log_event_generate(unsigned int log_type, int log_fd, int log_level, const char *log_msg, size_t log_msglen) { errno = ENOSYS; return -1; } int pr_log_event_listening(unsigned int log_type) { return FALSE; } int pr_log_openfile(const char *log_file, int *log_fd, mode_t log_mode) { int res; struct stat st; if (log_file == NULL || log_fd == NULL) { errno = EINVAL; return -1; } res = stat(log_file, &st); if (res < 0) { if (errno != ENOENT) { return -1; } } else { if (S_ISDIR(st.st_mode)) { errno = EISDIR; return -1; } } *log_fd = STDERR_FILENO; return 0; } void pr_log_pri(int prio, const char *fmt, ...) { if (getenv("TEST_VERBOSE") != NULL) { va_list msg; fprintf(stderr, "PRI%d: ", prio); va_start(msg, fmt); vfprintf(stderr, fmt, msg); va_end(msg); fprintf(stderr, "\n"); } } void pr_log_stacktrace(int fd, const char *name) { } int pr_log_writefile(int fd, const char *name, const char *fmt, ...) { if (getenv("TEST_VERBOSE") != NULL) { va_list msg; fprintf(stderr, "%s: ", name); va_start(msg, fmt); vfprintf(stderr, fmt, msg); va_end(msg); fprintf(stderr, "\n"); } return 0; } int pr_scoreboard_entry_update(pid_t pid, ...) { return 0; } void pr_session_disconnect(module *m, int reason_code, const char *details) { } const char *pr_session_get_protocol(int flags) { return "ftp"; } void pr_signals_handle(void) { } /* Module-specific stubs */ module proxy_module = { /* Always NULL */ NULL, NULL, /* Module API version */ 0x20, /* Module name */ "proxy", /* Module configuration handler table */ NULL, /* Module command handler table */ NULL, /* Module authentication handler table */ NULL, /* Module initialization */ NULL, /* Session initialization */ NULL, /* Module version */ MOD_PROXY_VERSION }; proftpd-mod_proxy-0.9.5/t/api/tests.c000066400000000000000000000102111475737016700175570ustar00rootroot00000000000000/* * ProFTPD - mod_proxy API testsuite * Copyright (c) 2012-2020 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "tests.h" struct testsuite_info { const char *name; Suite *(*get_suite)(void); }; static struct testsuite_info suites[] = { { "db", tests_get_db_suite }, { "dns", tests_get_dns_suite }, { "conn", tests_get_conn_suite }, { "netio", tests_get_netio_suite }, { "inet", tests_get_inet_suite }, { "random", tests_get_random_suite }, { "reverse", tests_get_reverse_suite }, { "forward", tests_get_forward_suite }, { "str", tests_get_str_suite }, { "tls", tests_get_tls_suite }, { "uri", tests_get_uri_suite }, { "session", tests_get_session_suite }, { "ftp.msg", tests_get_ftp_msg_suite }, { "ftp.conn", tests_get_ftp_conn_suite }, { "ftp.ctrl", tests_get_ftp_ctrl_suite }, { "ftp.data", tests_get_ftp_data_suite }, { "ftp.dirlist", tests_get_ftp_dirlist_suite }, { "ftp.facts", tests_get_ftp_facts_suite }, { "ftp.sess", tests_get_ftp_sess_suite }, { "ftp.xfer", tests_get_ftp_xfer_suite }, { NULL, NULL } }; static Suite *tests_get_suite(const char *suite) { register unsigned int i; for (i = 0; suites[i].name != NULL; i++) { if (strcmp(suite, suites[i].name) == 0) { return (*suites[i].get_suite)(); } } errno = ENOENT; return NULL; } int main(int argc, char *argv[]) { const char *log_file = "api-tests.log"; int nfailed = 0; SRunner *runner = NULL; char *requested = NULL; runner = srunner_create(NULL); /* XXX This log name should be set outside this code, e.g. via environment * variable or command-line option. */ srunner_set_log(runner, log_file); requested = getenv("PROXY_TEST_SUITE"); if (requested) { Suite *suite; suite = tests_get_suite(requested); if (suite) { srunner_add_suite(runner, suite); } else { fprintf(stderr, "No such test suite ('%s') requested via PROXY_TEST_SUITE\n", requested); return EXIT_FAILURE; } } else { register unsigned int i; for (i = 0; suites[i].name; i++) { Suite *suite; suite = (suites[i].get_suite)(); if (suite) { srunner_add_suite(runner, suite); } } } /* Configure the Trace API to write to stderr. */ pr_trace_use_stderr(TRUE); requested = getenv("PROXY_TEST_NOFORK"); if (requested) { srunner_set_fork_status(runner, CK_NOFORK); } else { requested = getenv("CK_DEFAULT_TIMEOUT"); if (requested == NULL) { setenv("CK_DEFAULT_TIMEOUT", "60", 1); } } srunner_run_all(runner, CK_NORMAL); nfailed = srunner_ntests_failed(runner); if (runner) srunner_free(runner); if (nfailed != 0) { fprintf(stderr, "-------------------------------------------------\n"); fprintf(stderr, " FAILED %d %s\n\n", nfailed, nfailed != 1 ? "tests" : "test"); fprintf(stderr, " Please send email to:\n\n"); fprintf(stderr, " tj@castaglia.org\n\n"); fprintf(stderr, " containing the `%s' file (in the t/ directory)\n", log_file); fprintf(stderr, " and the output from running `proftpd -V'\n"); fprintf(stderr, "-------------------------------------------------\n"); return EXIT_FAILURE; } return EXIT_SUCCESS; } proftpd-mod_proxy-0.9.5/t/api/tests.h000066400000000000000000000055401475737016700175750ustar00rootroot00000000000000/* * ProFTPD - mod_proxy API testsuite * Copyright (c) 2012-2020 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ /* Testsuite management */ #ifndef MOD_PROXY_TESTS_H #define MOD_PROXY_TESTS_H #include "mod_proxy.h" #include "proxy/random.h" #include "proxy/db.h" #include "proxy/dns.h" #include "proxy/conn.h" #include "proxy/netio.h" #include "proxy/inet.h" #include "proxy/str.h" #include "proxy/uri.h" #include "proxy/tls.h" #include "proxy/tls/db.h" #include "proxy/tls/redis.h" #include "proxy/session.h" #include "proxy/reverse.h" #include "proxy/reverse/db.h" #include "proxy/reverse/redis.h" #include "proxy/forward.h" #include "proxy/ftp/msg.h" #include "proxy/ftp/conn.h" #include "proxy/ftp/ctrl.h" #include "proxy/ftp/data.h" #include "proxy/ftp/dirlist.h" #include "proxy/ftp/facts.h" #include "proxy/ftp/sess.h" #include "proxy/ftp/xfer.h" #ifdef HAVE_CHECK_H # include #else # error "Missing Check installation; necessary for ProFTPD testsuite" #endif int tests_rmpath(pool *p, const char *path); int tests_stubs_set_next_cmd(cmd_rec *cmd); Suite *tests_get_conn_suite(void); Suite *tests_get_db_suite(void); Suite *tests_get_dns_suite(void); Suite *tests_get_inet_suite(void); Suite *tests_get_netio_suite(void); Suite *tests_get_random_suite(void); Suite *tests_get_reverse_suite(void); Suite *tests_get_forward_suite(void); Suite *tests_get_str_suite(void); Suite *tests_get_tls_suite(void); Suite *tests_get_uri_suite(void); Suite *tests_get_session_suite(void); Suite *tests_get_ftp_msg_suite(void); Suite *tests_get_ftp_conn_suite(void); Suite *tests_get_ftp_ctrl_suite(void); Suite *tests_get_ftp_data_suite(void); Suite *tests_get_ftp_dirlist_suite(void); Suite *tests_get_ftp_facts_suite(void); Suite *tests_get_ftp_sess_suite(void); Suite *tests_get_ftp_xfer_suite(void); extern volatile unsigned int recvd_signal_flags; extern pid_t mpid; extern server_rec *main_server; #endif /* MOD_PROXY_TESTS_H */ proftpd-mod_proxy-0.9.5/t/api/tls.c000066400000000000000000000240131475737016700172240ustar00rootroot00000000000000/* * ProFTPD - mod_proxy testsuite * Copyright (c) 2015-2022 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ /* Proxy TLS API tests. */ #include "tests.h" extern xaset_t *server_list; static pool *p = NULL; static const char *test_dir = "/tmp/mod_proxy-test-tls"; static void create_main_server(void) { pool *main_pool; main_pool = make_sub_pool(permanent_pool); pr_pool_tag(main_pool, "testsuite#main_server pool"); server_list = xaset_create(main_pool, NULL); main_server = (server_rec *) pcalloc(main_pool, sizeof(server_rec)); xaset_insert(server_list, (xasetmember_t *) main_server); main_server->pool = main_pool; main_server->conf = xaset_create(main_pool, NULL); main_server->set = server_list; main_server->sid = 1; main_server->notes = pr_table_nalloc(main_pool, 0, 8); /* TCP KeepAlive is enabled by default, with the system defaults. */ main_server->tcp_keepalive = palloc(main_server->pool, sizeof(struct tcp_keepalive)); main_server->tcp_keepalive->keepalive_enabled = TRUE; main_server->tcp_keepalive->keepalive_idle = -1; main_server->tcp_keepalive->keepalive_count = -1; main_server->tcp_keepalive->keepalive_intvl = -1; main_server->ServerName = "Test Server"; main_server->ServerPort = 21; } static int create_test_dir(void) { int res; mode_t perms; perms = 0770; res = mkdir(test_dir, perms); ck_assert_msg(res == 0, "Failed to create tmp directory '%s': %s", test_dir, strerror(errno)); res = chmod(test_dir, perms); ck_assert_msg(res == 0, "Failed to set perms %04o on directory '%s': %s", perms, test_dir, strerror(errno)); return 0; } static void set_up(void) { if (p == NULL) { p = permanent_pool = proxy_pool = make_sub_pool(NULL); server_list = NULL; main_server = NULL; session.c = NULL; session.notes = NULL; } (void) tests_rmpath(p, test_dir); create_main_server(); (void) create_test_dir(); init_netio(); proxy_db_init(p); if (getenv("TEST_VERBOSE") != NULL) { pr_trace_set_levels("proxy.db", 1, 20); pr_trace_set_levels("proxy.tls", 1, 20); } } static void tear_down(void) { if (getenv("TEST_VERBOSE") != NULL) { pr_trace_set_levels("proxy.db", 0, 0); pr_trace_set_levels("proxy.tls", 0, 0); } proxy_db_free(); (void) tests_rmpath(p, test_dir); if (p) { destroy_pool(p); p = permanent_pool = proxy_pool = NULL; server_list = NULL; main_server = NULL; session.c = NULL; session.notes = NULL; } } START_TEST (tls_free_test) { int res; res = proxy_tls_free(NULL); ck_assert_msg(res < 0, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_tls_free(p); ck_assert_msg(res == 0, "Failed to free TLS API resources: %s", strerror(errno)); } END_TEST START_TEST (tls_init_test) { int res, flags = PROXY_DB_OPEN_FL_SKIP_VACUUM; res = proxy_tls_init(NULL, NULL, flags); #if defined(PR_USE_OPENSSL) ck_assert_msg(res < 0, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_tls_init(p, NULL, flags); ck_assert_msg(res < 0, "Failed to handle null tables directory"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_tls_init(p, test_dir, flags); ck_assert_msg(res == 0, "Failed to init TLS API resources: %s", strerror(errno)); res = proxy_tls_free(p); ck_assert_msg(res == 0, "Failed to free TLS API resources: %s", strerror(errno)); #else ck_assert_msg(res == 0, "Failed to init TLS API resources: %s", strerror(errno)); #endif /* PR_USE_OPENSSL */ } END_TEST START_TEST (tls_sess_free_test) { int res; res = proxy_tls_sess_free(NULL); ck_assert_msg(res < 0, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_tls_init(p, test_dir, PROXY_DB_OPEN_FL_SKIP_VACUUM); ck_assert_msg(res == 0, "Failed to init TLS API resources: %s", strerror(errno)); mark_point(); res = proxy_tls_sess_free(p); ck_assert_msg(res == 0, "Failed to release TLS API session resources: %s", strerror(errno)); res = proxy_tls_free(p); ck_assert_msg(res == 0, "Failed to free TLS API resources: %s", strerror(errno)); } END_TEST START_TEST (tls_sess_init_test) { #if defined(PR_USE_OPENSSL) int res, flags = PROXY_DB_OPEN_FL_SKIP_VACUUM; struct proxy_session *proxy_sess; mark_point(); res = proxy_tls_sess_init(NULL, NULL, flags); ck_assert_msg(res < 0, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_tls_sess_init(p, NULL, flags); ck_assert_msg(res < 0, "Failed to handle null proxy_session"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); proxy_sess = (struct proxy_session *) proxy_session_alloc(p); ck_assert_msg(proxy_sess != NULL, "Failed to allocate proxy session: %s", strerror(errno)); mark_point(); res = proxy_tls_sess_init(p, proxy_sess, flags); ck_assert_msg(res < 0, "Failed to handle invalid SSL_CTX"); ck_assert_msg(errno == EPERM, "Expected EPERM (%d), got '%s' (%d)", EPERM, strerror(errno), errno); mark_point(); res = proxy_tls_init(p, test_dir, flags); ck_assert_msg(res == 0, "Failed to init TLS API resources: %s", strerror(errno)); (void) proxy_db_close(p, NULL); mark_point(); res = proxy_tls_sess_init(p, proxy_sess, flags); ck_assert_msg(res == 0, "Failed to init TLS API session resources: %s", strerror(errno)); mark_point(); res = proxy_tls_sess_free(p); ck_assert_msg(res == 0, "Failed to release TLS API session resources: %s", strerror(errno)); mark_point(); res = proxy_tls_free(p); ck_assert_msg(res == 0, "Failed to release TLS API resources: %s", strerror(errno)); mark_point(); proxy_session_free(p, proxy_sess); #endif /* PR_USE_OPENSSL */ } END_TEST START_TEST (tls_using_tls_test) { int res, tls; tls = proxy_tls_using_tls(); #if defined(PR_USE_OPENSSL) ck_assert_msg(tls == PROXY_TLS_ENGINE_AUTO, "Expected TLS auto, got %d", tls); #else ck_assert_msg(tls == PROXY_TLS_ENGINE_OFF, "Expected TLS off, got %d", tls); #endif /* PR_USE_OPENSSL */ res = proxy_tls_set_tls(7); #if defined(PR_USE_OPENSSL) ck_assert_msg(res < 0, "Set TLS unexpectedly"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_tls_set_tls(PROXY_TLS_ENGINE_ON); tls = proxy_tls_using_tls(); ck_assert_msg(tls == PROXY_TLS_ENGINE_ON, "Expected TLS on, got %d", tls); res = proxy_tls_set_tls(PROXY_TLS_ENGINE_OFF); tls = proxy_tls_using_tls(); ck_assert_msg(tls == PROXY_TLS_ENGINE_OFF, "Expected TLS off, got %d", tls); res = proxy_tls_set_tls(PROXY_TLS_ENGINE_AUTO); tls = proxy_tls_using_tls(); ck_assert_msg(tls == PROXY_TLS_ENGINE_AUTO, "Expected TLS auto, got %d", tls); res = proxy_tls_set_tls(PROXY_TLS_ENGINE_IMPLICIT); tls = proxy_tls_using_tls(); ck_assert_msg(tls == PROXY_TLS_ENGINE_IMPLICIT, "Expected TLS implicit, got %d", tls); #endif /* PR_USE_OPENSSL */ } END_TEST START_TEST (tls_match_client_tls_test) { int res; /* Plain FTP */ mark_point(); res = proxy_tls_match_client_tls(); ck_assert_msg(res == 0, "Failed to match plain FTP client: %s", strerror(errno)); /* Explicit FTPS */ mark_point(); session.rfc2228_mech = "TLS"; res = proxy_tls_match_client_tls(); ck_assert_msg(res == 0, "Failed to match explicit FTPS client: %s", strerror(errno)); /* Implicit FTPS */ session.rfc2228_mech = NULL; /* TODO: Add implicit FTPS check; requires setting TLSOptions config_rec, * which pulls in need for server_rec, parser, etc. */ } END_TEST START_TEST (tls_set_data_prot_test) { int res; res = proxy_tls_set_data_prot(TRUE); #if defined(PR_USE_OPENSSL) ck_assert_msg(res == TRUE, "Expected TRUE, got %d", res); res = proxy_tls_set_data_prot(FALSE); ck_assert_msg(res == TRUE, "Expected TRUE, got %d", res); #else ck_assert_msg(res == FALSE, "Expected FALSE, got %d", res); res = proxy_tls_set_data_prot(FALSE); ck_assert_msg(res == FALSE, "Expected FALSE, got %d", res); #endif /* PR_USE_OPENSSL */ res = proxy_tls_set_data_prot(FALSE); ck_assert_msg(res == FALSE, "Expected FALSE, got %d", res); (void) proxy_tls_set_data_prot(TRUE); } END_TEST Suite *tests_get_tls_suite(void) { Suite *suite; TCase *testcase; suite = suite_create("tls"); testcase = tcase_create("base"); tcase_add_checked_fixture(testcase, set_up, tear_down); tcase_add_test(testcase, tls_free_test); tcase_add_test(testcase, tls_init_test); tcase_add_test(testcase, tls_sess_free_test); tcase_add_test(testcase, tls_sess_init_test); tcase_add_test(testcase, tls_using_tls_test); tcase_add_test(testcase, tls_match_client_tls_test); tcase_add_test(testcase, tls_set_data_prot_test); suite_add_tcase(suite, testcase); return suite; } proftpd-mod_proxy-0.9.5/t/api/uri.c000066400000000000000000000571431475737016700172330ustar00rootroot00000000000000/* * ProFTPD - mod_proxy testsuite * Copyright (c) 2012-2022 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ /* URI API tests */ #include "tests.h" static pool *p = NULL; static void set_up(void) { if (p == NULL) { p = permanent_pool = make_sub_pool(NULL); session.c = NULL; session.notes = NULL; } if (getenv("TEST_VERBOSE") != NULL) { pr_trace_set_levels("proxy.uri", 1, 20); } } static void tear_down(void) { if (getenv("TEST_VERBOSE") != NULL) { pr_trace_set_levels("proxy.uri", 0, 0); } if (p != NULL) { destroy_pool(p); p = permanent_pool = NULL; session.c = NULL; session.notes = NULL; } } START_TEST (uri_parse_args_test) { const char *uri; char *scheme, *host, *username, *password; unsigned int port; int res; mark_point(); res = proxy_uri_parse(NULL, NULL, NULL, NULL, NULL, NULL, NULL); ck_assert_msg(res < 0, "Failed to handle null pool"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_uri_parse(p, NULL, NULL, NULL, NULL, NULL, NULL); ck_assert_msg(res < 0, "Failed to handle null URI"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); uri = "foo"; res = proxy_uri_parse(p, uri, NULL, NULL, NULL, NULL, NULL); ck_assert_msg(res < 0, "Failed to handle null scheme"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_uri_parse(p, uri, &scheme, NULL, NULL, NULL, NULL); ck_assert_msg(res < 0, "Failed to handle null host"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_uri_parse(p, uri, &scheme, &host, NULL, NULL, NULL); ck_assert_msg(res < 0, "Failed to handle null port"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_uri_parse(p, uri, &scheme, &host, &port, NULL, NULL); ck_assert_msg(res < 0, "Failed to handle URI missing a colon"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); scheme = host = username = password = NULL; port = 0; uri = "foo:"; res = proxy_uri_parse(p, uri, &scheme, &host, &port, NULL, NULL); ck_assert_msg(res < 0, "Failed to handle unknown/unsupported scheme"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); scheme = host = username = password = NULL; port = 0; uri = "foo@:"; res = proxy_uri_parse(p, uri, &scheme, &host, &port, NULL, NULL); ck_assert_msg(res < 0, "Failed to handle illegal scheme character"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); scheme = host = username = password = NULL; port = 0; uri = "ftp:"; res = proxy_uri_parse(p, uri, &scheme, &host, &port, NULL, NULL); ck_assert_msg(res < 0, "Failed to handle URI lacking double slashes"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); scheme = host = username = password = NULL; port = 0; uri = "ftp:/"; res = proxy_uri_parse(p, uri, &scheme, &host, &port, NULL, NULL); ck_assert_msg(res < 0, "Failed to handle URI lacking double slashes"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); scheme = host = username = password = NULL; port = 0; uri = "ftp:/a"; res = proxy_uri_parse(p, uri, &scheme, &host, &port, NULL, NULL); ck_assert_msg(res < 0, "Failed to handle URI lacking double slashes"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); scheme = host = username = password = NULL; port = 0; uri = "ftp://"; res = proxy_uri_parse(p, uri, &scheme, &host, &port, NULL, NULL); ck_assert_msg(res < 0, "Failed to handle URI lacking hostname/port"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); scheme = host = username = password = NULL; port = 0; uri = "ftp://%2f"; res = proxy_uri_parse(p, uri, &scheme, &host, &port, NULL, NULL); ck_assert_msg(res < 0, "Failed to handle URI using URL encoding"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); } END_TEST START_TEST (uri_parse_ftp_test) { const char *uri; char *scheme, *host, *username, *password; unsigned int port; int res; mark_point(); scheme = host = username = password = NULL; port = 0; uri = "ftp://foo"; res = proxy_uri_parse(p, uri, &scheme, &host, &port, &username, &password); ck_assert_msg(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); ck_assert_msg(strcmp(scheme, "ftp") == 0, "Expected scheme '%s', got '%s'", "ftp", scheme); ck_assert_msg(strcmp(host, "foo") == 0, "Expected host '%s', got '%s'", "foo", host); ck_assert_msg(port == 21, "Expected port '%u', got '%u'", 21, port); ck_assert_msg(username == NULL, "Expected null username, got '%s'", username); ck_assert_msg(password == NULL, "Expected null password, got '%s'", password); mark_point(); scheme = host = username = password = NULL; port = 0; uri = "ftp://foo:2121"; res = proxy_uri_parse(p, uri, &scheme, &host, &port, &username, &password); ck_assert_msg(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); ck_assert_msg(strcmp(scheme, "ftp") == 0, "Expected scheme '%s', got '%s'", "ftp", scheme); ck_assert_msg(strcmp(host, "foo") == 0, "Expected host '%s', got '%s'", "foo", host); ck_assert_msg(port == 2121, "Expected port '%u', got '%u'", 2121, port); ck_assert_msg(username == NULL, "Expected null username, got '%s'", username); ck_assert_msg(password == NULL, "Expected null password, got '%s'", password); mark_point(); scheme = host = username = password = NULL; port = 0; uri = "ftp://127.0.0.1:2121"; res = proxy_uri_parse(p, uri, &scheme, &host, &port, &username, &password); ck_assert_msg(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); ck_assert_msg(strcmp(scheme, "ftp") == 0, "Expected scheme '%s', got '%s'", "ftp", scheme); ck_assert_msg(strcmp(host, "127.0.0.1") == 0, "Expected host '%s', got '%s'", "127.0.0.1", host); ck_assert_msg(port == 2121, "Expected port '%u', got '%u'", 2121, port); ck_assert_msg(username == NULL, "Expected null username, got '%s'", username); ck_assert_msg(password == NULL, "Expected null password, got '%s'", password); mark_point(); scheme = host = username = password = NULL; port = 0; uri = "ftp://[::1]:2121"; res = proxy_uri_parse(p, uri, &scheme, &host, &port, &username, &password); ck_assert_msg(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); ck_assert_msg(strcmp(scheme, "ftp") == 0, "Expected scheme '%s', got '%s'", "ftp", scheme); ck_assert_msg(strcmp(host, "::1") == 0, "Expected host '%s', got '%s'", "::1", host); ck_assert_msg(port == 2121, "Expected port '%u', got '%u'", 2121, port); ck_assert_msg(username == NULL, "Expected null username, got '%s'", username); ck_assert_msg(password == NULL, "Expected null password, got '%s'", password); mark_point(); scheme = host = username = password = NULL; port = 0; uri = "ftp://[::1:2121"; res = proxy_uri_parse(p, uri, &scheme, &host, &port, &username, &password); ck_assert_msg(res < 0, "Failed to reject URI with bad IPv6 host encoding"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); scheme = host = username = password = NULL; port = 0; uri = "ftp://user:password@host:21"; res = proxy_uri_parse(p, uri, &scheme, &host, &port, &username, &password); ck_assert_msg(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); ck_assert_msg(strcmp(scheme, "ftp") == 0, "Expected scheme '%s', got '%s'", "ftp", scheme); ck_assert_msg(strcmp(host, "host") == 0, "Expected host '%s', got '%s'", "host", host); ck_assert_msg(port == 21, "Expected port '%u', got '%u'", 21, port); ck_assert_msg(username != NULL, "Expected non-null username"); ck_assert_msg(strcmp(username, "user") == 0, "Expected username '%s', got '%s'", "user", username); ck_assert_msg(password != NULL, "Expected non-null password"); ck_assert_msg(strcmp(password, "password") == 0, "Expected password '%s', got '%s'", "password", password); mark_point(); scheme = host = username = password = NULL; port = 0; uri = "ftp://user:@host:21"; res = proxy_uri_parse(p, uri, &scheme, &host, &port, &username, &password); ck_assert_msg(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); ck_assert_msg(strcmp(scheme, "ftp") == 0, "Expected scheme '%s', got '%s'", "ftp", scheme); ck_assert_msg(strcmp(host, "host") == 0, "Expected host '%s', got '%s'", "host", host); ck_assert_msg(port == 21, "Expected port '%u', got '%u'", 21, port); ck_assert_msg(username != NULL, "Expected non-null username"); ck_assert_msg(strcmp(username, "user") == 0, "Expected username '%s', got '%s'", "user", username); ck_assert_msg(password != NULL, "Expected non-null password"); ck_assert_msg(strcmp(password, "") == 0, "Expected password '%s', got '%s'", "", password); mark_point(); scheme = host = username = password = NULL; port = 0; uri = "ftp://user@host:21"; res = proxy_uri_parse(p, uri, &scheme, &host, &port, &username, &password); ck_assert_msg(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); ck_assert_msg(strcmp(scheme, "ftp") == 0, "Expected scheme '%s', got '%s'", "ftp", scheme); ck_assert_msg(strcmp(host, "host") == 0, "Expected host '%s', got '%s'", "host", host); ck_assert_msg(port == 21, "Expected port '%u', got '%u'", 21, port); ck_assert_msg(username == NULL, "Expected null username, got '%s'", username); ck_assert_msg(password == NULL, "Expected null password, got '%s'", password); mark_point(); scheme = host = username = password = NULL; port = 0; uri = "ftp://anonymous:email@example.com@host:21"; res = proxy_uri_parse(p, uri, &scheme, &host, &port, &username, &password); ck_assert_msg(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); ck_assert_msg(strcmp(scheme, "ftp") == 0, "Expected scheme '%s', got '%s'", "ftp", scheme); ck_assert_msg(strcmp(host, "host") == 0, "Expected host '%s', got '%s'", "host", host); ck_assert_msg(port == 21, "Expected port '%u', got '%u'", 21, port); ck_assert_msg(username != NULL, "Expected non-null username"); ck_assert_msg(strcmp(username, "anonymous") == 0, "Expected username '%s', got '%s'", "anonymous", username); ck_assert_msg(password != NULL, "Expected non-null password"); ck_assert_msg(strcmp(password, "email@example.com") == 0, "Expected password '%s', got '%s'", "email@example.com", password); mark_point(); scheme = host = username = password = NULL; port = 0; uri = "ftp://user@domain:email@example.com@host:21"; res = proxy_uri_parse(p, uri, &scheme, &host, &port, &username, &password); ck_assert_msg(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); ck_assert_msg(strcmp(scheme, "ftp") == 0, "Expected scheme '%s', got '%s'", "ftp", scheme); ck_assert_msg(strcmp(host, "host") == 0, "Expected host '%s', got '%s'", "host", host); ck_assert_msg(port == 21, "Expected port '%u', got '%u'", 21, port); ck_assert_msg(username != NULL, "Expected non-null username"); ck_assert_msg(strcmp(username, "user@domain") == 0, "Expected username '%s', got '%s'", "user@domain", username); ck_assert_msg(password != NULL, "Expected non-null password"); ck_assert_msg(strcmp(password, "email@example.com") == 0, "Expected password '%s', got '%s'", "email@example.com", password); mark_point(); scheme = host = username = password = NULL; port = 0; uri = "ftp://host:65555"; res = proxy_uri_parse(p, uri, &scheme, &host, &port, NULL, NULL); ck_assert_msg(res < 0, "Failed to reject URI with too-large port"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); scheme = host = username = password = NULL; port = 0; uri = "ftp://foo:2121/home"; res = proxy_uri_parse(p, uri, &scheme, &host, &port, &username, &password); ck_assert_msg(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); ck_assert_msg(strcmp(scheme, "ftp") == 0, "Expected scheme '%s', got '%s'", "ftp", scheme); ck_assert_msg(strcmp(host, "foo") == 0, "Expected host '%s', got '%s'", "foo", host); ck_assert_msg(port == 2121, "Expected port '%u', got '%u'", 2121, port); ck_assert_msg(username == NULL, "Expected null username, got '%s'", username); ck_assert_msg(password == NULL, "Expected null password, got '%s'", password); mark_point(); scheme = host = username = password = NULL; port = 0; uri = "ftp://host:65555:foo"; res = proxy_uri_parse(p, uri, &scheme, &host, &port, NULL, NULL); ck_assert_msg(res < 0, "Failed to reject URI with bad port spec"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); scheme = host = username = password = NULL; port = 0; uri = "ftp://host:70000"; res = proxy_uri_parse(p, uri, &scheme, &host, &port, NULL, NULL); ck_assert_msg(res < 0, "Failed to reject URI with invalid port spec"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); } END_TEST START_TEST (uri_parse_ftps_test) { const char *uri; char *scheme, *host, *username, *password; unsigned int port; int res; mark_point(); scheme = host = username = password = NULL; port = 0; uri = "ftps://foo"; res = proxy_uri_parse(p, uri, &scheme, &host, &port, &username, &password); ck_assert_msg(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); ck_assert_msg(strcmp(scheme, "ftps") == 0, "Expected scheme '%s', got '%s'", "ftps", scheme); ck_assert_msg(strcmp(host, "foo") == 0, "Expected host '%s', got '%s'", "foo", host); ck_assert_msg(port == 21, "Expected port '%u', got '%u'", 21, port); ck_assert_msg(username == NULL, "Expected null username, got '%s'", username); ck_assert_msg(password == NULL, "Expected null password, got '%s'", password); mark_point(); scheme = host = username = password = NULL; port = 0; uri = "ftps://foo:2121"; res = proxy_uri_parse(p, uri, &scheme, &host, &port, &username, &password); ck_assert_msg(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); ck_assert_msg(strcmp(scheme, "ftps") == 0, "Expected scheme '%s', got '%s'", "ftps", scheme); ck_assert_msg(strcmp(host, "foo") == 0, "Expected host '%s', got '%s'", "foo", host); ck_assert_msg(port == 2121, "Expected port '%u', got '%u'", 2121, port); ck_assert_msg(username == NULL, "Expected null username, got '%s'", username); ck_assert_msg(password == NULL, "Expected null password, got '%s'", password); mark_point(); scheme = host = username = password = NULL; port = 0; uri = "ftps://foo:2121/home"; res = proxy_uri_parse(p, uri, &scheme, &host, &port, &username, &password); ck_assert_msg(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); ck_assert_msg(strcmp(scheme, "ftps") == 0, "Expected scheme '%s', got '%s'", "ftps", scheme); ck_assert_msg(strcmp(host, "foo") == 0, "Expected host '%s', got '%s'", "foo", host); ck_assert_msg(port == 2121, "Expected port '%u', got '%u'", 2121, port); ck_assert_msg(username == NULL, "Expected null username, got '%s'", username); ck_assert_msg(password == NULL, "Expected null password, got '%s'", password); } END_TEST START_TEST (uri_parse_sftp_test) { const char *uri; char *scheme, *host, *username, *password; unsigned int port; int res; mark_point(); scheme = host = username = password = NULL; port = 0; uri = "sftp://foo"; res = proxy_uri_parse(p, uri, &scheme, &host, &port, &username, &password); ck_assert_msg(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); ck_assert_msg(strcmp(scheme, "sftp") == 0, "Expected scheme '%s', got '%s'", "sftp", scheme); ck_assert_msg(strcmp(host, "foo") == 0, "Expected host '%s', got '%s'", "foo", host); ck_assert_msg(port == 22, "Expected port '%u', got '%u'", 22, port); ck_assert_msg(username == NULL, "Expected null username, got '%s'", username); ck_assert_msg(password == NULL, "Expected null password, got '%s'", password); mark_point(); scheme = host = username = password = NULL; port = 0; uri = "sftp://foo:2222"; res = proxy_uri_parse(p, uri, &scheme, &host, &port, &username, &password); ck_assert_msg(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); ck_assert_msg(strcmp(scheme, "sftp") == 0, "Expected scheme '%s', got '%s'", "sftp", scheme); ck_assert_msg(strcmp(host, "foo") == 0, "Expected host '%s', got '%s'", "foo", host); ck_assert_msg(port == 2222, "Expected port '%u', got '%u'", 2222, port); ck_assert_msg(username == NULL, "Expected null username, got '%s'", username); ck_assert_msg(password == NULL, "Expected null password, got '%s'", password); mark_point(); scheme = host = username = password = NULL; port = 0; uri = "sftp://foo:2222/home"; res = proxy_uri_parse(p, uri, &scheme, &host, &port, &username, &password); ck_assert_msg(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); ck_assert_msg(strcmp(scheme, "sftp") == 0, "Expected scheme '%s', got '%s'", "sftp", scheme); ck_assert_msg(strcmp(host, "foo") == 0, "Expected host '%s', got '%s'", "foo", host); ck_assert_msg(port == 2222, "Expected port '%u', got '%u'", 2222, port); ck_assert_msg(username == NULL, "Expected null username, got '%s'", username); ck_assert_msg(password == NULL, "Expected null password, got '%s'", password); } END_TEST START_TEST (uri_parse_http_test) { const char *uri; char *scheme, *host, *username, *password; unsigned int port; int res; mark_point(); scheme = host = username = password = NULL; port = 0; uri = "http://host"; res = proxy_uri_parse(p, uri, &scheme, &host, &port, NULL, NULL); ck_assert_msg(res < 0, "Failed to reject URI with unsupported scheme"); ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); } END_TEST /* SRV scheme variants */ START_TEST (uri_parse_srv_test) { const char *uri; char *scheme, *host, *username, *password; unsigned int port; int res; mark_point(); scheme = host = username = password = NULL; port = 0; uri = "ftp+srv://foo:2121/home"; res = proxy_uri_parse(p, uri, &scheme, &host, &port, &username, &password); ck_assert_msg(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); ck_assert_msg(strcmp(scheme, "ftp+srv") == 0, "Expected scheme '%s', got '%s'", "ftp+srv", scheme); ck_assert_msg(strcmp(host, "foo") == 0, "Expected host '%s', got '%s'", "foo", host); ck_assert_msg(port == 0, "Expected port '%u', got '%u'", 0, port); ck_assert_msg(username == NULL, "Expected null username, got '%s'", username); ck_assert_msg(password == NULL, "Expected null password, got '%s'", password); mark_point(); scheme = host = username = password = NULL; port = 0; uri = "ftps+srv://foo.bar"; res = proxy_uri_parse(p, uri, &scheme, &host, &port, &username, &password); ck_assert_msg(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); ck_assert_msg(strcmp(scheme, "ftps+srv") == 0, "Expected scheme '%s', got '%s'", "ftps+srv", scheme); ck_assert_msg(strcmp(host, "foo.bar") == 0, "Expected host '%s', got '%s'", "foo.bar", host); ck_assert_msg(port == 0, "Expected port '%u', got '%u'", 0, port); ck_assert_msg(username == NULL, "Expected null username, got '%s'", username); ck_assert_msg(password == NULL, "Expected null password, got '%s'", password); } END_TEST /* TXT scheme variants */ START_TEST (uri_parse_txt_test) { const char *uri; char *scheme, *host, *username, *password; unsigned int port; int res; mark_point(); scheme = host = username = password = NULL; port = 0; uri = "ftp+txt://foo:2121/home"; res = proxy_uri_parse(p, uri, &scheme, &host, &port, &username, &password); ck_assert_msg(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); ck_assert_msg(strcmp(scheme, "ftp+txt") == 0, "Expected scheme '%s', got '%s'", "ftp+txt", scheme); ck_assert_msg(strcmp(host, "foo") == 0, "Expected host '%s', got '%s'", "foo", host); ck_assert_msg(port == 0, "Expected port '%u', got '%u'", 0, port); ck_assert_msg(username == NULL, "Expected null username, got '%s'", username); ck_assert_msg(password == NULL, "Expected null password, got '%s'", password); mark_point(); scheme = host = username = password = NULL; port = 0; uri = "ftps+txt://foo.bar"; res = proxy_uri_parse(p, uri, &scheme, &host, &port, &username, &password); ck_assert_msg(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); ck_assert_msg(strcmp(scheme, "ftps+txt") == 0, "Expected scheme '%s', got '%s'", "ftps+txt", scheme); ck_assert_msg(strcmp(host, "foo.bar") == 0, "Expected host '%s', got '%s'", "foo.bar", host); ck_assert_msg(port == 0, "Expected port '%u', got '%u'", 0, port); ck_assert_msg(username == NULL, "Expected null username, got '%s'", username); ck_assert_msg(password == NULL, "Expected null password, got '%s'", password); } END_TEST Suite *tests_get_uri_suite(void) { Suite *suite; TCase *testcase; suite = suite_create("uri"); testcase = tcase_create("base"); tcase_add_checked_fixture(testcase, set_up, tear_down); tcase_add_test(testcase, uri_parse_args_test); tcase_add_test(testcase, uri_parse_ftp_test); tcase_add_test(testcase, uri_parse_ftps_test); tcase_add_test(testcase, uri_parse_sftp_test); tcase_add_test(testcase, uri_parse_http_test); tcase_add_test(testcase, uri_parse_srv_test); tcase_add_test(testcase, uri_parse_txt_test); suite_add_tcase(suite, testcase); return suite; } proftpd-mod_proxy-0.9.5/t/etc/000077500000000000000000000000001475737016700162605ustar00rootroot00000000000000proftpd-mod_proxy-0.9.5/t/etc/modules/000077500000000000000000000000001475737016700177305ustar00rootroot00000000000000proftpd-mod_proxy-0.9.5/t/etc/modules/mod_tls/000077500000000000000000000000001475737016700213715ustar00rootroot00000000000000proftpd-mod_proxy-0.9.5/t/etc/modules/mod_tls/psk.dat000066400000000000000000000005011475737016700226540ustar00rootroot0000000000000025e262660dc554c2e77f33e3cdc9e7467070d4e6193a6a87f6ba6c08aeb76246278e8ca50d41254315cbc6f5a3afc87922620151a16fffde6b38dd3c8bf71e7ba87d95d880f2d6049d8b148a5883234189b7a5249d787e0b2fe4befe41bfce0b29634f6acde3db0e477d49efb766772a78de99e0ff316e6910b0c83c2875c77a551bf2e940807b5e705516509a00c9fc7d88956f89b6600efc4ca74bd12493a5 proftpd-mod_proxy-0.9.5/t/lib/000077500000000000000000000000001475737016700162535ustar00rootroot00000000000000proftpd-mod_proxy-0.9.5/t/lib/ProFTPD/000077500000000000000000000000001475737016700174715ustar00rootroot00000000000000proftpd-mod_proxy-0.9.5/t/lib/ProFTPD/Tests/000077500000000000000000000000001475737016700205735ustar00rootroot00000000000000proftpd-mod_proxy-0.9.5/t/lib/ProFTPD/Tests/Modules/000077500000000000000000000000001475737016700222035ustar00rootroot00000000000000proftpd-mod_proxy-0.9.5/t/lib/ProFTPD/Tests/Modules/mod_proxy.pm000066400000000000000000027362711475737016700246030ustar00rootroot00000000000000package ProFTPD::Tests::Modules::mod_proxy; use lib qw(t/lib); use base qw(ProFTPD::TestSuite::Child); use strict; use Carp; use Cwd; use File::Copy; use File::Path qw(mkpath); use File::Spec; use IO::Handle; use IO::Socket::INET; use Time::HiRes qw(gettimeofday tv_interval usleep); use ProFTPD::TestSuite::FTP; use ProFTPD::TestSuite::Utils qw(:auth :config :running :test :testsuite); $| = 1; my $order = 0; my $TESTS = { proxy_sighup => { order => ++$order, test_class => [qw(forking os_linux)], }, proxy_reverse_connect => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_connect_failed_bad_dst_addr => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_connect_failed_non2xx => { order => ++$order, test_class => [qw(forking mod_wrap2 mod_wrap2_file reverse)], }, proxy_reverse_connect_failed_timeout => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_login => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_login_roundrobin_after_host => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_login_peruser_after_host => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_login_extra_user => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_login_extra_pass => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_login_failed => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_login_chrooted => { order => ++$order, test_class => [qw(forking reverse rootprivs)], }, proxy_reverse_login_no_backend_proxy_protocol => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_feat => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_abort => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_list_pasv => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_list_port => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_list_pasv_enoent => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_pasv_allowforeignaddress_false => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_pasv_allowforeignaddress_allowed_by_class_issue223 => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_list_port_enoent => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_port_allowforeignaddress_false => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_port_allowforeignaddress_allowed_by_class_issue223 => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_epsv => { order => ++$order, test_class => [qw(forking reverse)], }, # TODO: proxy_reverse_epsv_all proxy_reverse_eprt_ipv4 => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_eprt_ipv6 => { order => ++$order, test_class => [qw(feature_ipv6 forking reverse)], }, proxy_reverse_eprt_allowforeignaddress_false => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_eprt_allowforeignaddress_allowed_by_class_issue223 => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_retr_pasv_ascii => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_retr_pasv_binary => { order => ++$order, test_class => [qw(forking reverse)], }, # This needs to handle chunks larger than the transfer buffer size; # maybe use SocketOptions to tune them differently; handle short writes # via outer/inner loops in data_send(). proxy_reverse_retr_large_file => { order => ++$order, test_class => [qw(forking reverse slow)], }, proxy_reverse_retr_empty_file => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_retr_abort => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_stor_pasv => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_stor_port => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_stor_large_file => { order => ++$order, test_class => [qw(forking reverse slow)], }, proxy_reverse_stor_empty_file => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_stor_pasv_eperm => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_stor_port_eperm => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_stor_abort => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_rest_retr => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_rest_stor => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_stat => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_unknown_cmd => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_uri_creds_login => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_uri_creds_login_failed_bad_dst_user => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_uri_creds_login_failed_bad_dst_passwd => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_uri_creds_login_with_reverse_proxy_auth => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_passiveports_pasv => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_passiveports_epsv => { order => ++$order, test_class => [qw(forking reverse)], }, # MasqueradeAddress only really applies to PASV proxy_reverse_config_masqueradeaddress => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_allowforeignaddress_port => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_allowforeignaddress_eprt => { order => ++$order, test_class => [qw(forking reverse)], }, # Normal TimeoutIdle, honored by mod_proxy (frontend and backend) proxy_reverse_config_timeoutidle_frontend => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_timeoutidle_backend => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_timeoutlogin_frontend => { order => ++$order, test_class => [qw(forking reverse)], }, # Normal TimeoutNoTransfer, honored by mod_proxy (frontend and backend) proxy_reverse_config_timeoutnoxfer_frontend => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_timeoutnoxfer_backend => { order => ++$order, test_class => [qw(forking reverse)], }, # Normal TimeoutStalled, honored by mod_proxy (frontend and backend) proxy_reverse_config_timeoutstalled_frontend => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_timeoutstalled_backend => { order => ++$order, test_class => [qw(forking reverse)], }, # XXX What about TimeoutSession, TimeoutLinger? proxy_reverse_config_datatransferpolicy_pasv_list_pasv => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_datatransferpolicy_pasv_list_port => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_datatransferpolicy_port_list_pasv => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_datatransferpolicy_port_list_port => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_datatransferpolicy_epsv_list_pasv => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_datatransferpolicy_epsv_list_port => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_datatransferpolicy_eprt_list_pasv => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_datatransferpolicy_eprt_list_port => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_datatransferpolicy_active_list_pasv => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_datatransferpolicy_active_list_port => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_datatransferpolicy_active_list_eprt_port_fallback => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_datatransferpolicy_passive_list_pasv => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_datatransferpolicy_passive_list_epsv_pasv_fallback => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_datatransferpolicy_passive_list_port => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_datatransferpolicy_client => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_directorylistpolicy_client => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_directorylistpolicy_list_unix => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_directorylistpolicy_list_unix_use_slink => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_directorylistpolicy_list_unix_wide_dir => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_directorylistpolicy_list_windows => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_directorylistpolicy_list_backend_error => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_directorylistpolicy_list_opts_mlst => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_connect_policy_random => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_connect_policy_shuffle => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_connect_policy_roundrobin => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_connect_policy_roundrobin_issue132 => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_connect_policy_leastconns => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_connect_policy_leastresponsetime => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_connect_policy_per_host => { order => ++$order, test_class => [qw(forking mod_ifsession reverse)], }, proxy_reverse_config_connect_policy_per_user => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_connect_policy_per_user_by_json => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_connect_policy_per_user_no_fallback_issue148 => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_connect_policy_per_group => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_connect_policy_per_group_by_json => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_reverseservers_json => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_reverseservers_json_per_user => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_transfer_rate_retr => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_use_reverse_proxy_auth_login => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_use_reverse_proxy_auth_login_extra_user => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_use_reverse_proxy_auth_login_extra_pass => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_use_reverse_proxy_auth_login_failed_bad_passwd => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_use_direct_data_transfers_port => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_use_direct_data_transfers_port_failed => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_use_direct_data_transfers_list_failed => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_config_use_direct_data_transfers_pasv => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_limit_list_deny_all => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_limit_list_deny_user => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_limit_list_deny_group => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_limit_mlsd_deny_all => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_limit_retr_deny_all => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_limit_stor_deny_all => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_limit_read_deny_all => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_limit_write_deny_all => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_limit_dirs_deny_all => { order => ++$order, test_class => [qw(forking reverse)], }, # TransferLog entries (binary/ascii, upload/download, complete/aborted) # Note that TransferLog, as supported by mod_proxy, CANNOT have the absolute # path of the file transferred; we can only know path as requested by # the client. proxy_reverse_xferlog_retr_ascii_ok => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_xferlog_retr_ascii_chrooted_ok => { order => ++$order, test_class => [qw(forking reverse rootprivs)], }, proxy_reverse_xferlog_retr_binary_ok => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_xferlog_stor_ascii_ok => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_xferlog_stor_binary_ok => { order => ++$order, test_class => [qw(forking reverse)], }, # ExtendedLog entries. The most affected will be %D/%d and %F/%f. proxy_reverse_extlog_retr_var_F_f => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_extlog_stor_var_F_f => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_extlog_list_var_D_d => { order => ++$order, test_class => [qw(forking reverse)], }, # LastLog? WtmpLog? # HiddenStore? (should have no effect) # TransferPriority? proxy_reverse_proxy_protocol_v1_ipv4 => { order => ++$order, test_class => [qw(forking mod_proxy_protocol reverse)], }, proxy_reverse_proxy_protocol_v1_ipv6 => { order => ++$order, test_class => [qw(feature_ipv6 forking mod_proxy_protocol reverse)], }, proxy_reverse_proxy_protocol_v2_ipv4 => { order => ++$order, test_class => [qw(forking mod_proxy_protocol reverse)], }, proxy_reverse_proxy_protocol_v2_ipv6 => { order => ++$order, test_class => [qw(feature_ipv6 forking mod_proxy_protocol reverse)], }, proxy_reverse_proxy_protocol_v2_tlv_alpn => { order => ++$order, test_class => [qw(forking mod_proxy_protocol reverse)], }, proxy_reverse_proxy_protocol_v2_tlv_authority => { order => ++$order, test_class => [qw(forking mod_proxy_protocol reverse)], }, proxy_reverse_proxy_protocol_v2_tlv_unique_id => { order => ++$order, test_class => [qw(forking mod_proxy_protocol mod_unique_id reverse)], }, # proxy_reverse_proxy_protocol_v1_ipv6_useipv6_off # proxy_reverse_proxy_protocol_v1_unknown # proxy_reverse_proxy_protocol_v2_ipv6_useipv6_off # proxy_reverse_proxy_protocol_v2_unknown proxy_forward_connect => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_connect_failed_timeout => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_noproxyauth_login => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_noproxyauth_login_after_host => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_noproxyauth_login_extra_user => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_noproxyauth_login_extra_pass => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_noproxyauth_login_ipv6_dst_addr => { order => ++$order, test_class => [qw(feature_ipv6 forking forward)], }, proxy_forward_noproxyauth_login_netftp_fw_type_1 => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_noproxyauth_login_failed_bad_dst_addr => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_noproxyauth_login_failed_proxy_dst_addr => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_noproxyauth_login_failed_non2xx => { order => ++$order, test_class => [qw(forking forward mod_wrap2 mod_wrap2_file)], }, proxy_forward_noproxyauth_login_failed_login_limit => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_noproxyauth_login_failed_bad_sequence => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_noproxyauth_login_failed_bad_dst_passwd => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_noproxyauth_sni_login_backend_ftp => { order => ++$order, test_class => [qw(forking forward mod_tls)], }, proxy_forward_noproxyauth_sni_login_backend_ftps => { order => ++$order, test_class => [qw(forking forward mod_tls)], }, proxy_forward_noproxyauth_sni_login_failed_no_tls => { order => ++$order, test_class => [qw(forking forward mod_tls)], }, proxy_forward_noproxyauth_sni_login_failed_no_tls_sni => { order => ++$order, test_class => [qw(forking forward mod_tls)], }, proxy_forward_noproxyauth_sni_login_failed_proxyforwardto_mismatch => { order => ++$order, test_class => [qw(forking forward mod_tls)], }, proxy_forward_login_feat_first => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_list_pasv => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_list_port => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_epsv => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_eprt_ipv4 => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_eprt_ipv6 => { order => ++$order, test_class => [qw(feature_ipv6 forking forward)], }, proxy_forward_retr_port => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_stor_pasv => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_userwithproxyauth_login => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_userwithproxyauth_login_extra_user => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_userwithproxyauth_login_extra_pass => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_userwithproxyauth_login_user_incl_at_symbol => { order => ++$order, test_class => [qw(forking forward)], }, # Note: The following test is disabled because Net::FTP has a bug in the # handling of a FTP_FIREWALL_TYPE=2. Specifically, in its login() method, # it does not properly construct the $fwuser variable unless there is a # ~/.netrc file -- and such a file cannot be used for tests like this. # # proxy_forward_userwithproxyauth_login_netftp_fw_type_2 proxy_forward_userwithproxyauth_login_failed_bad_dst_addr => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_userwithproxyauth_login_failed_non2xx => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_userwithproxyauth_login_failed_limit_login => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_userwithproxyauth_login_failed_bad_sequence => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_userwithproxyauth_login_failed_bad_proxy_passwd => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_userwithproxyauth_login_failed_bad_dst_passwd => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_userwithproxyauth_bad_sequence_no_dst_login => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_proxyuserwithproxyauth_login => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_proxyuserwithproxyauth_login_extra_user => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_proxyuserwithproxyauth_login_extra_pass => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_proxyuserwithproxyauth_login_user_incl_at_symbol => { order => ++$order, test_class => [qw(forking forward)], }, # Note: The following test is disabled because Net::FTP has a bug in the # handling of a FTP_FIREWALL_TYPE=6. Specifically, in its login() method, # it does not properly construct the $fwuser variable unless there is a # ~/.netrc file -- and such a file cannot be used for tests like this. # # proxy_forward_proxyuserwithproxyauth_login_netftp_fw_type6 proxy_forward_proxyuserwithproxyauth_login_failed_bad_dst_addr => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_proxyuserwithproxyauth_login_failed_non2xx => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_proxyuserwithproxyauth_login_failed_limit_login => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_proxyuserwithproxyauth_login_failed_bad_sequence => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_proxyuserwithproxyauth_login_failed_bad_proxy_passwd => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_proxyuserwithproxyauth_login_failed_bad_dst_passwd => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_proxyuserwithproxyauth_bad_sequence_no_dst_login => { order => ++$order, test_class => [qw(forking forward)], }, # proxy_forward_config_displayconnect proxy_forward_config_forward_to => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_config_forward_to_negated => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_config_use_direct_data_transfers_port => { order => ++$order, test_class => [qw(forking forward)], }, proxy_forward_config_use_direct_data_transfers_pasv => { order => ++$order, test_class => [qw(forking forward)], }, # proxy_forward_config_timeoutlogin_frontend # proxy_forward_config_timeoutlogin_backend # proxy_forward_config_maxloginattempts (frontend or backend?) # proxy_forward_xferlog_retr_ascii_ok # proxy_forward_xferlog_retr_binary_ok # proxy_forward_xferlog_stor_ascii_ok # proxy_forward_xferlog_stor_binary_ok # proxy_forward_extlog_retr_var_F_f # proxy_forward_extlog_stor_var_F_f # proxy_forward_extlog_list_var_D_d # XXX TODO Issue #21 # proxy_forward_config_directorylistpolicy_client # proxy_forward_config_directorylistpolicy_list_unix # proxy_forward_config_directorylistpolicy_list_windows }; sub new { return shift()->SUPER::new(@_); } sub list_tests { # Check for the required Perl modules: # # Net-SSLeay # IO-Socket-SSL # Net-FTPSSL my $required = [qw( Net::SSLeay IO::Socket::SSL Net::FTPSSL )]; foreach my $req (@$required) { eval "use $req"; if ($@) { print STDERR "\nWARNING:\n + Module '$req' not found, skipping all tests\n"; if ($ENV{TEST_VERBOSE}) { print STDERR "Unable to load $req: $@\n"; } return qw(testsuite_empty_test); } } return testsuite_get_runnable_tests($TESTS); } sub config_hash2array { my $hash = shift; my $array = []; foreach my $key (keys(%$hash)) { push(@$array, "$key $hash->{$key}\n"); } return $array; } sub get_reverse_proxy_config { my $tmpdir = shift; my $log_file = shift; my $vhost_port = shift; my $table_dir = File::Spec->rel2abs("$tmpdir/var/proxy"); my $config = { ProxyEngine => 'on', ProxyLog => $log_file, ProxyReverseServers => "ftp://127.0.0.1:$vhost_port", ProxyRole => 'reverse', ProxyTables => $table_dir, }; return $config; } sub get_forward_proxy_config { my $tmpdir = shift; my $log_file = shift; my $vhost_port = shift; my $table_dir = File::Spec->rel2abs("$tmpdir/var/proxy"); my $config = { ProxyEngine => 'on', ProxyLog => $log_file, ProxyRole => 'forward', ProxyTables => $table_dir, ProxyTimeoutConnect => '1sec', Class => { 'forward-proxy' => { From => '127.0.0.1', ProxyForwardEnabled => 'on', }, }, }; return $config; } sub ftp_list { my $self = shift; my $client = shift; my $conn = $client->list_raw(); unless ($conn) { die("Failed to LIST: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 10); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); ($resp_code, $resp_msg) = $client->quit(); my $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); 1; } sub get_server_pid { my $pid_file = shift; my $pid; if (open(my $fh, "< $pid_file")) { $pid = <$fh>; chomp($pid); close($fh); } else { croak("Can't read $pid_file: $!"); } return $pid; } sub server_open_fds { my $pid_file = shift; my $pid = get_server_pid($pid_file); my $proc_dir = "/proc/$pid/fd"; if (opendir(my $dirh, $proc_dir)) { my $count = 0; # Only count entries whose names are numbers while (my $dent = readdir($dirh)) { if ($dent =~ /^\d+$/) { $count++; } } closedir($dirh); return $count; } else { croak("Can't open directory '$proc_dir': $!"); } } sub proxy_sighup { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:10 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Start the server server_start($setup->{config_file}); sleep(1); # Use proc(5) filesystem to count the number of open fds in the daemon my $orig_nfds = server_open_fds($setup->{pid_file}); if ($ENV{TEST_VERBOSE}) { print STDERR "Found $orig_nfds open fds after server startup\n"; } # Restart the server server_restart($setup->{pid_file}); sleep(1); # Count the open fds again, make sure we haven't leaked any my $restart_nfds = server_open_fds($setup->{pid_file}); if ($ENV{TEST_VERBOSE}) { print STDERR "Found $restart_nfds open fds after server restart #1\n"; } # Note that we expect at least one more fd, for the session SQLite database file. my $expected_nfds = $orig_nfds + 1; $self->assert($expected_nfds == $restart_nfds || $orig_nfds == $restart_nfds, test_msg("Expected $expected_nfds open fds, found $restart_nfds")); # Restart the server server_restart($setup->{pid_file}); sleep(1); # And count the open fds one more time, to make doubly sure we are not # leaking fds. $restart_nfds = server_open_fds($setup->{pid_file}); if ($ENV{TEST_VERBOSE}) { print STDERR "Found $restart_nfds open fds after server restart #2\n"; } $self->assert($expected_nfds == $restart_nfds || $orig_nfds == $restart_nfds, test_msg("Expected $expected_nfds open fds, found $restart_nfds")); # Stop server server_stop($setup->{pid_file}); unlink($setup->{log_file}); } sub proxy_reverse_connect { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.reverse:20 proxy.reverse.db:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $client->quit(); my $expected; $expected = 220; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Real Server'; $self->assert(qr/$expected/, $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_connect_failed_bad_dst_addr { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyReverseServers} = 'ftp://1.2.3.4:5678'; $proxy_config->{ProxyRetryCount} = 1; $proxy_config->{ProxyTimeoutConnect} = '1s'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); eval { ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1) }; unless ($@) { die("Unexpectedly connected successfully"); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_connect_failed_non2xx { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $allow_file = File::Spec->rel2abs("$tmpdir/wrap2.allow"); if (open(my $fh, "> $allow_file")) { unless (close($fh)) { die("Can't write $allow_file: $!"); } } else { die("Can't open $allow_file: $!"); } my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none WrapEngine on WrapLog $log_file WrapTables file:$allow_file builtin:all WrapOptions CheckOnConnect EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); eval { ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1) }; unless ($@) { die("Unexpectedly connected successfully"); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_connect_failed_timeout { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $dst_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); my $listening = IO::Socket::INET->new( LocalHost => '127.0.0.1', LocalPort => $dst_port, Proto => 'tcp', Type => SOCK_STREAM, Listen => 5, ReuseAddr => 1, ReusePort => 1, ); my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyReverseServers} = "ftp://127.0.0.1:$dst_port"; $proxy_config->{ProxyRetryCount} = 1; $proxy_config->{ProxyTimeoutConnect} = '1s'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $start = [gettimeofday()]; eval { ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1, 1) }; my $elapsed = tv_interval($start); unless ($@) { die("Connection to 127.0.0.1:$port succeeded unexpectedly"); } $self->assert($elapsed < 4, test_msg("ProxyTimeoutConnect 1 not honored (elapsed $elapsed)")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } eval { $listening->close() }; # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_login { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($user, $passwd); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_login_roundrobin_after_host { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $host = 'ftp.castaglia.org'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 binding:30 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', DefaultServer => 'on', ServerName => '"Default Server"', SocketBindTight => 'on', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { my $tables_dir = File::Spec->rel2abs("$tmpdir/var/proxy"); print $fh < ProxyTables $tables_dir Port $port ServerAlias $host ServerName "Namebased Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c DelayEngine off ProxyEngine on ProxyLog $log_file ProxyReverseServers ftp://127.0.0.1:$vhost_port ProxyRole reverse Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); my ($resp_code, $resp_msg) = $client->host($host); my $expected = 220; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); # The default reverse connect policy is RoundRobin; this means # that the backend server is selected at connect time. By sending HOST, # we change that selected backend server, and thus we should get the # "real" proxied backend server banner. $expected = 'Real Server'; $self->assert(qr/$expected/, $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->login($user, $passwd); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_login_peruser_after_host { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $host = 'localhost'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 binding:20 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', DefaultServer => 'on', ServerName => '"Default Server"', SocketBindTight => 'on', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { my $tables_dir = File::Spec->rel2abs("$tmpdir/var/proxy"); my $vhost_port2 = $vhost_port - 7; print $fh < ProxyTables $tables_dir Port $port ServerAlias $host ServerName "Namebased Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c DelayEngine off ProxyEngine on ProxyLog $log_file ProxyTimeoutConnect 1sec ProxyRole reverse ProxyReverseConnectPolicy PerUser ProxyReverseServers ftp://127.0.0.1:$vhost_port ftp://127.0.0.1:$vhost_port2 Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none Port $vhost_port2 ServerName "Other Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); my ($resp_code, $resp_msg) = $client->host($host); my $expected = 220; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); # Our reverse connect policy is PerUser; this means that the backend # server is selected at USER time. By sending HOST, we do NOT change # the selected backend server, and thus we should get the namebased # server banner. $expected = 'Namebased'; $self->assert(qr/$expected/, $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->login($user, $passwd); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_login_extra_user { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($setup->{user}, $setup->{passwd}); # Note that this changed due to Bug#4217 my ($resp_code, $resp_msg) = $client->user($setup->{user}); my $expected = 230; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "User $setup->{user} logged in"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got $resp_msg")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_login_extra_pass { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($user, $passwd); eval { $client->pass($passwd) }; unless ($@) { die("Extra PASS succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 503; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'You are already logged in'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got $resp_msg")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_login_failed { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); eval { $client->login($user, 'foobar') }; unless ($@) { die("Login succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Login incorrect.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_login_chrooted { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); # Different distributions have variants of this group. my $group_name = 'nobody'; unless (getgrnam($group_name)) { $group_name = 'nogroup'; } my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', # For dropping privs User => 'nobody', Group => $group_name, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow server to start up sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($setup->{user}, $setup->{passwd}); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_login_no_backend_proxy_protocol { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyOptions} = 'UseProxyProtocol'; $proxy_config->{ProxyTLSEngine} = 'off'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20 proxy.reverse:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow server to start up sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($setup->{user}, $setup->{passwd}); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_feat { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($user, $passwd); $client->feat(); my $resp_code = $client->response_code(); my $resp_msgs = $client->response_msgs(); my $expected = 211; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Features:'; $self->assert($expected eq $resp_msgs->[0], test_msg("Expected first response message '$expected', got '$resp_msgs->[0]'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_abort { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($setup->{user}, $setup->{passwd}); $client->quote('ABOR'); my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 226; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Abort successful'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_list_pasv { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($user, $passwd); my $conn = $client->list_raw(); unless ($conn) { die("Failed to LIST: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 10); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); ($resp_code, $resp_msg) = $client->quit(); my $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_pasv_allowforeignaddress_false { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($setup->{user}, $setup->{passwd}); # We use PASV here, and AllowForeignAddress is off. The data transfer # should still succeed, because we connect to the server's data port # from the same IP as our control connection. my $conn = $client->list_raw(); unless ($conn) { die("Failed to LIST: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 10); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); ($resp_code, $resp_msg) = $client->quit(); my $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); if (defined($ex)) { test_cleanup($setup->{log_file}, $ex); die($ex); } eval { if (open(my $fh, "< $setup->{log_file}")) { my $ok = 1; while (my $line = <$fh>) { chomp($line); if ($ENV{TEST_VERBOSE}) { print STDERR "$line\n"; } if ($line =~ /SECURITY VIOLATION/) { $ok = 0; last; } if ($line =~ /Passive connection from IP address \S+ matches control connection address; skipping '\S+'/) { last; } } close($fh); $self->assert($ok, "Did not see expected log messages"); } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_pasv_allowforeignaddress_allowed_by_class_issue223 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); my $class_name = 'allow_fxp'; my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', AllowForeignAddress => $class_name, SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < From 127.0.0.0/8 Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($setup->{user}, $setup->{passwd}); # We use PASV here, and AllowForeignAddress is off. The data transfer # should still succeed, because we connect to the server's data port # from the same IP as our control connection. my $conn = $client->list_raw(); unless ($conn) { die("Failed to LIST: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 10); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); ($resp_code, $resp_msg) = $client->quit(); my $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); if (defined($ex)) { test_cleanup($setup->{log_file}, $ex); die($ex); } eval { if (open(my $fh, "< $setup->{log_file}")) { my $ok = 1; while (my $line = <$fh>) { chomp($line); if ($ENV{TEST_VERBOSE}) { print STDERR "$line\n"; } if ($line =~ /SECURITY VIOLATION/) { $ok = 0; last; } if ($line =~ /Passive connection from IP address \S+ matches control connection address; skipping '\S+'/) { last; } } close($fh); $self->assert($ok, "Did not see expected log messages"); } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_list_port { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1, 1); $client->login($user, $passwd); my $conn = $client->list_raw(); unless ($conn) { die("Failed to LIST: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 10); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); ($resp_code, $resp_msg) = $client->quit(); my $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_list_pasv_enoent { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($user, $passwd); my $enoent_dir = '/foo/bar/baz'; eval { $client->list($enoent_dir) }; unless ($@) { die("LIST succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 450; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "$enoent_dir: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); ($resp_code, $resp_msg) = $client->quit(); $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_list_port_enoent { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1, 1); $client->login($user, $passwd); my $enoent_dir = '/foo/bar/baz'; eval { $client->list($enoent_dir) }; unless ($@) { die("LIST succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 450; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "$enoent_dir: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); ($resp_code, $resp_msg) = $client->quit(); $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_port_allowforeignaddress_false { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1, 1); $client->login($setup->{user}, $setup->{passwd}); # Deliberately provide an IP address that does not match our source # IP address, to trigger the AllowForeignAddress restriction. my $port_addr = '1,2,3,4,192,6'; eval { $client->port($port_addr) }; unless ($@) { die("PORT $port_addr succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 500; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Illegal PORT command'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); ($resp_code, $resp_msg) = $client->quit(); $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_port_allowforeignaddress_allowed_by_class_issue223 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); my $class_name = 'allow_fxp'; my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', AllowForeignAddress => $class_name, SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < From 1.2.3.4/8 Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1, 1); $client->login($setup->{user}, $setup->{passwd}); # Deliberately provide an IP address that does not match our source # IP address, to attempt to trigger the AllowForeignAddress restriction. my $port_addr = '1,2,3,4,192,6'; my ($resp_code, $resp_msg) = $client->port($port_addr); my $expected = 200; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'PORT command successful'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); ($resp_code, $resp_msg) = $client->quit(); $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_epsv { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($user, $passwd); my ($resp_code, $resp_msg) = $client->epsv(); my $expected = 229; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = '^Entering Extended Passive Mode \(\|\|\|\d+\|\)'; $self->assert(qr/$expected/, $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_eprt_ipv4 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($user, $passwd); my ($resp_code, $resp_msg) = $client->eprt('|1|127.0.0.1|4856|'); my $expected; $expected = 200; $self->assert($expected == $resp_code, test_msg("Expected $expected, got $resp_code")); $expected = "EPRT command successful"; $self->assert($expected eq $resp_msg, test_msg("Expected '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_eprt_ipv6 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', UseIPv6 => 'on', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($user, $passwd); my ($resp_code, $resp_msg) = $client->eprt('|2|::ffff:127.0.0.1|4856|'); my $expected = 200; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "EPRT command successful"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_eprt_allowforeignaddress_false { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($setup->{user}, $setup->{passwd}); # Deliberately provide an IP address that does not match our source # IP address, to trigger the AllowForeignAddress restriction. my $eprt_addr = '|1|1.2.3.4|49158|'; eval { $client->eprt($eprt_addr) }; unless ($@) { die("EPRT $eprt_addr succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 500; $self->assert($expected == $resp_code, "Expected response code $expected, got $resp_code"); $expected = 'Illegal EPRT command'; $self->assert($expected eq $resp_msg, "Expected response message '$expected', got '$resp_msg'"); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_eprt_allowforeignaddress_allowed_by_class_issue223 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); my $class_name = 'allow_fxp'; my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', AllowForeignAddress => $class_name, SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < From 1.2.3.4/8
    Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($setup->{user}, $setup->{passwd}); # Deliberately provide an IP address that does not match our source # IP address, to attempt to trigger the AllowForeignAddress restriction. my $eprt_addr = '|1|1.2.3.4|49158|'; my ($resp_code, $resp_msg) = $client->eprt($eprt_addr); my $expected = 200; $self->assert($expected == $resp_code, "Expected response code $expected, got $resp_code"); $expected = 'EPRT command successful'; $self->assert($expected eq $resp_msg, "Expected response message '$expected', got '$resp_msg'"); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_retr_pasv_ascii { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $test_data = "Hello, Proxying World!\n"; my $test_file = File::Spec->rel2abs("$tmpdir/test.txt"); if (open(my $fh, "> $test_file")) { print $fh $test_data; unless (close($fh)) { die("Unable to write $test_file: $!"); } # Make sure that, if we're running as root, that the test file has # permissions/privs set for the account we create if ($< == 0) { unless (chown($setup->{uid}, $setup->{gid}, $test_file)) { die("Can't set owner of $test_file to $setup->{uid}/$setup->{gid}: $!"); } } } else { die("Unable to open $test_file: $!"); } my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off RootLogin on TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow server to start up sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($setup->{user}, $setup->{passwd}); $client->type('ascii'); my $conn = $client->retr_raw($test_file); unless ($conn) { die("RETR failed: " . $client->response_code() . " " . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 30); my $size = $conn->bytes_read(); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); $client->quit(); # The length of 'Hello, Proxying World!\n' is 23, but we expect 24 # here because of the ASCII conversion of the bare LF to a CRLF. my $expected = length($test_data) + 1; $self->assert($expected == $size, test_msg("Expected $expected, got $size")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_retr_pasv_binary { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $test_data = "Hello, Proxying World!\n"; my $test_file = File::Spec->rel2abs("$tmpdir/test.txt"); if (open(my $fh, "> $test_file")) { print $fh $test_data; unless (close($fh)) { die("Unable to write $test_file: $!"); } # Make sure that, if we're running as root, that the test file has # permissions/privs set for the account we create if ($< == 0) { unless (chown($setup->{uid}, $setup->{gid}, $test_file)) { die("Can't set owner of $test_file to $setup->{uid}/$setup->{gid}: $!"); } } } else { die("Unable to open $test_file: $!"); } my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off RootLogin on TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow server to start up sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($setup->{user}, $setup->{passwd}); $client->type('binary'); my $conn = $client->retr_raw($test_file); unless ($conn) { die("RETR failed: " . $client->response_code() . " " . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 30); my $size = $conn->bytes_read(); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); $client->quit(); # The length of 'Hello, Proxying World!\n' is 23, so that is what # we expect here; no ASCII conversion to change things. my $expected = length($test_data); $self->assert($expected == $size, test_msg("Expected $expected, got $size")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_retr_large_file { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $test_datalen = (4 * 1024 * 1024); my $test_file = File::Spec->rel2abs("$tmpdir/test.txt"); if (open(my $fh, "> $test_file")) { print $fh 'R' x $test_datalen; unless (close($fh)) { die("Unable to write $test_file: $!"); } # Make sure that, if we're running as root, that the test file has # permissions/privs set for the account we create if ($< == 0) { unless (chown($setup->{uid}, $setup->{gid}, $test_file)) { die("Can't set owner of $test_file to $setup->{uid}/$setup->{gid}: $!"); } } } else { die("Unable to open $test_file: $!"); } my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); my $timeout_idle = 120; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off RootLogin on TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow server to start up sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($setup->{user}, $setup->{passwd}); $client->type('binary'); my $conn = $client->retr_raw($test_file); unless ($conn) { die("RETR failed: " . $client->response_code() . " " . $client->response_msg()); } my $buf; while ($conn->read($buf, 32768, 30)) { # Delay a little between reads, to try to force mod_proxy to deal # with a slow consumer (thus leading to short writes). my $sleep_ms = 150; my $sleep_usecs = ($sleep_ms * 1000); usleep($sleep_usecs); } my $size = $conn->bytes_read(); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); $client->quit(); my $expected = $test_datalen; $self->assert($expected == $size, test_msg("Expected $expected, got $size")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_retr_empty_file { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $test_datalen = 0; my $test_file = File::Spec->rel2abs("$tmpdir/test.txt"); if (open(my $fh, "> $test_file")) { unless (close($fh)) { die("Unable to write $test_file: $!"); } # Make sure that, if we're running as root, that the test file has # permissions/privs set for the account we create if ($< == 0) { unless (chown($setup->{uid}, $setup->{gid}, $test_file)) { die("Can't set owner of $test_file to $setup->{uid}/$setup->{gid}: $!"); } } } else { die("Unable to open $test_file: $!"); } my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); my $timeout_idle = 120; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off RootLogin on TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow server to start up sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 3); $client->login($setup->{user}, $setup->{passwd}); $client->type('binary'); my $conn = $client->retr_raw($test_file); unless ($conn) { die("RETR failed: " . $client->response_code() . " " . $client->response_msg()); } my $buf; while ($conn->read($buf, 32768, 30)) { # Delay a little between reads, to try to force mod_proxy to deal # with a slow consumer (thus leading to short writes). my $sleep_ms = 150; my $sleep_usecs = ($sleep_ms * 1000); usleep($sleep_usecs); } my $size = $conn->bytes_read(); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); $client->quit(); my $expected = $test_datalen; $self->assert($expected == $size, test_msg("Expected $expected, got $size")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_retr_abort { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $test_datalen = (4 * 1024 * 1024); my $test_file = File::Spec->rel2abs("$tmpdir/test.txt"); if (open(my $fh, "> $test_file")) { print $fh 'R' x $test_datalen; unless (close($fh)) { die("Unable to write $test_file: $!"); } # Make sure that, if we're running as root, that the test file has # permissions/privs set for the account we create if ($< == 0) { unless (chown($setup->{uid}, $setup->{gid}, $test_file)) { die("Can't set owner of $test_file to $setup->{uid}/$setup->{gid}: $!"); } } } else { die("Unable to open $test_file: $!"); } my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, TimeoutLinger => 1, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off RootLogin on TimeoutIdle $timeout_idle TimeoutLinger 1 TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow server to start up sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($setup->{user}, $setup->{passwd}); $client->type('binary'); my $conn = $client->retr_raw($test_file); unless ($conn) { die("RETR failed: " . $client->response_code() . " " . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 30); eval { $client->quote('ABOR') }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg, 1); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_stor_pasv { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $test_data = "Hello, Proxying World!\n"; my $test_file = File::Spec->rel2abs("$tmpdir/test.txt"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off RootLogin on TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow server to start up sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->stor_raw($test_file); unless ($conn) { die("STOR failed: " . $client->response_code() . " " . $client->response_msg()); } my $buf = $test_data; $conn->write($buf, length($buf), 30); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); $client->quit(); $self->assert(-f $test_file, test_msg("File $test_file does not exist as expected")); my $expected = length($test_data); my $size = -s $test_file; $self->assert($expected == $size, test_msg("Expected size $expected, got $size")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_stor_port { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $test_data = "Hello, Proxying World!\n"; my $test_file = File::Spec->rel2abs("$tmpdir/test.txt"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off RootLogin on TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow server to start up sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1, 1); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->stor_raw($test_file); unless ($conn) { die("STOR failed: " . $client->response_code() . " " . $client->response_msg()); } my $buf = $test_data; $conn->write($buf, length($buf), 30); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); $client->quit(); $self->assert(-f $test_file, test_msg("File $test_file does not exist as expected")); my $expected = length($test_data); my $size = -s $test_file; $self->assert($expected == $size, test_msg("Expected size $expected, got $size")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_stor_large_file { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $test_datalen = (4 * 1024 * 1024); my $test_file = File::Spec->rel2abs("$tmpdir/test.txt"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); my $timeout_idle = 120; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off RootLogin on TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow server to start up sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($setup->{user}, $setup->{passwd}); $client->type('binary'); my $conn = $client->stor_raw($test_file); unless ($conn) { die("STOR failed: " . $client->response_code() . " " . $client->response_msg()); } my $buf = 'R' x $test_datalen; my $size = $conn->write($buf, length($buf), 30); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); $client->quit(); my $expected = $test_datalen; $self->assert($expected == $size, test_msg("Expected sent size $expected, got $size")); $self->assert(-f $test_file, test_msg("File $test_file does not exist as expected")); my $filesize = -s $test_file; $self->assert($expected == $filesize, test_msg("Expected file size $expected, got $filesize")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_stor_empty_file { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $test_datalen = 0; my $test_file = File::Spec->rel2abs("$tmpdir/test.txt"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); my $timeout_idle = 120; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off RootLogin on TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow server to start up sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($setup->{user}, $setup->{passwd}); $client->type('binary'); my $conn = $client->stor_raw($test_file); unless ($conn) { die("STOR failed: " . $client->response_code() . " " . $client->response_msg()); } eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); $client->quit(); $self->assert(-f $test_file, test_msg("File $test_file does not exist as expected")); my $expected = $test_datalen; my $filesize = -s $test_file; $self->assert($expected == $filesize, test_msg("Expected file size $expected, got $filesize")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_stor_pasv_eperm { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $test_data = "Hello, Proxying World!\n"; my $test_datalen = length($test_data); my $test_file = File::Spec->rel2abs("$tmpdir/test.txt"); if (open(my $fh, "> $test_file")) { print $fh $test_data; unless (close($fh)) { die("Unable to write $test_file: $!"); } # Make sure that, if we're running as root, that the test file has # permissions/privs set for the account we create if ($< == 0) { unless (chown($setup->{uid}, $setup->{gid}, $test_file)) { die("Can't set owner of $test_file to $setup->{uid}/$setup->{gid}: $!"); } } } else { die("Unable to open $test_file: $!"); } my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off AllowOverwrite off RootLogin on TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow server to start up sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($setup->{user}, $setup->{passwd}); $client->type('binary'); my $conn = $client->stor_raw($test_file); if ($conn) { die("STOR succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $client->quit(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "$test_file: Overwrite permission denied"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_stor_port_eperm { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $test_data = "Hello, Proxying World!\n"; my $test_datalen = length($test_data); my $test_file = File::Spec->rel2abs("$tmpdir/test.txt"); if (open(my $fh, "> $test_file")) { print $fh $test_data; unless (close($fh)) { die("Unable to write $test_file: $!"); } # Make sure that, if we're running as root, that the test file has # permissions/privs set for the account we create if ($< == 0) { unless (chown($setup->{uid}, $setup->{gid}, $test_file)) { die("Can't set owner of $test_file to $setup->{uid}/$setup->{gid}: $!"); } } } else { die("Unable to open $test_file: $!"); } my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off AllowOverwrite off RootLogin on TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow server to start up sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1, 1); $client->login($setup->{user}, $setup->{passwd}); $client->type('binary'); my $conn = $client->stor_raw($test_file); if ($conn) { die("STOR succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $client->quit(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "$test_file: Overwrite permission denied"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_stor_abort { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $test_datalen = (4 * 1024 * 1024); my $test_file = File::Spec->rel2abs("$tmpdir/test.txt"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, TimeoutLinger => 1, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off RootLogin on TimeoutIdle $timeout_idle TimeoutLinger 1 TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow server to start up sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($setup->{user}, $setup->{passwd}); $client->type('binary'); my $conn = $client->stor_raw($test_file); unless ($conn) { die("STOR failed: " . $client->response_code() . " " . $client->response_msg()); } my $buf = 'R' x $test_datalen; $conn->write($buf, 8192, 30); eval { $conn->abort() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg, 1); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_rest_retr { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $test_data = "Hello, Proxying World!\n"; my $test_datalen = length($test_data); my $test_file = File::Spec->rel2abs("$tmpdir/test.txt"); if (open(my $fh, "> $test_file")) { print $fh $test_data; unless (close($fh)) { die("Unable to write $test_file: $!"); } # Make sure that, if we're running as root, that the test file has # permissions/privs set for the account we create if ($< == 0) { unless (chown($setup->{uid}, $setup->{gid}, $test_file)) { die("Can't set owner of $test_file to $setup->{uid}/$setup->{gid}: $!"); } } } else { die("Unable to open $test_file: $!"); } my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off RootLogin on TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow server to start up sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($setup->{user}, $setup->{passwd}); $client->type('binary'); my $rest_len = $test_datalen - 1; my ($resp_code, $resp_msg) = $client->rest($rest_len); my $expected = 350; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "Restarting at $rest_len. Send STORE or RETRIEVE to initiate transfer"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); my $conn = $client->retr_raw($test_file); unless ($conn) { die("RETR failed: " . $client->response_code() . " " . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 30); my $size = $conn->bytes_read(); eval { $conn->close() }; $resp_code = $client->response_code(); $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); $client->quit(); $expected = 1; $self->assert($expected == $size, test_msg("Expected received size $expected, got $size")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_rest_stor { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $test_data = "Hello, Proxying World!\n"; my $test_datalen = length($test_data); my $test_file = File::Spec->rel2abs("$tmpdir/test.txt"); if (open(my $fh, "> $test_file")) { print $fh $test_data; unless (close($fh)) { die("Unable to write $test_file: $!"); } # Make sure that, if we're running as root, that the test file has # permissions/privs set for the account we create if ($< == 0) { unless (chown($setup->{uid}, $setup->{gid}, $test_file)) { die("Can't set owner of $test_file to $setup->{uid}/$setup->{gid}: $!"); } } } else { die("Unable to open $test_file: $!"); } my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off AllowOverwrite on AllowStoreRestart on RootLogin on TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow server to start up sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($setup->{user}, $setup->{passwd}); $client->type('binary'); my $rest_len = $test_datalen; my ($resp_code, $resp_msg) = $client->rest($rest_len); my $expected = 350; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "Restarting at $rest_len. Send STORE or RETRIEVE to initiate transfer"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); my $conn = $client->stor_raw($test_file); unless ($conn) { die("STOR failed: " . $client->response_code() . " " . $client->response_msg()); } my $buf = $test_data; my $size = $conn->write($buf, length($buf), 30); eval { $conn->close() }; $resp_code = $client->response_code(); $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); $client->quit(); $expected = 2 * $test_datalen; $size = -s $test_file; $self->assert($expected == $size, test_msg("Expected file size $expected, got $size")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_stat { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off AllowOverwrite on AllowStoreRestart on RootLogin on TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow server to start up sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($setup->{user}, $setup->{passwd}); $client->type('binary'); my ($resp_code, $resp_msg) = $client->stat(); my $expected = 211; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = ' Connected from 127.0.0.1 (127.0.0.1)'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); ($resp_code, $resp_msg) = $client->stat($setup->{config_file}); $expected = 213; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'proxy\.conf$'; $self->assert(qr/$expected/, $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_unknown_cmd { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($user, $passwd); my $unknown_cmd = 'FOOBAR'; eval { $client->quote($unknown_cmd, "BAZ") }; unless ($@) { die("Unknown FTP command '$unknown_cmd' succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 500; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "$unknown_cmd not understood"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_uri_creds_login { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyReverseServers} = "ftp://$user:$passwd\@127.0.0.1:$vhost_port"; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); # Since we have overridden the credentials vi URI, it shouldn't matter # what usernames/passwords we use here. my $bad_user = 'foo'; my $bad_pass = 'bar'; $client->login($bad_user, $bad_pass); my $resp_msg = $client->response_msg(); my $expected = "User $bad_user logged in"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_uri_creds_login_failed_bad_dst_user { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $bad_user = 'foo'; my $bad_pass = 'bar'; my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyReverseServers} = "ftp://$bad_user:$passwd\@127.0.0.1:$vhost_port"; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); eval { $client->login($bad_user, $bad_pass) }; unless ($@) { die("Login succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Login incorrect.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_uri_creds_login_failed_bad_dst_passwd { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $bad_user = 'foo'; my $bad_pass = 'bar'; my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyReverseServers} = "ftp://$user:$bad_pass\@127.0.0.1:$vhost_port"; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); eval { $client->login($bad_user, $bad_pass) }; unless ($@) { die("Login succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Login incorrect.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_uri_creds_login_with_reverse_proxy_auth { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/real.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/real.group"); my $proxy_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $proxy_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $proxy_user = 'proxy'; my $passwd = 'test'; my $proxy_passwd = 'proxy'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); auth_user_write($proxy_user_file, $proxy_user, $proxy_passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($proxy_group_file, $group, $gid, $proxy_user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyReverseServers} = "ftp://$user:$passwd\@127.0.0.1:$vhost_port"; $proxy_config->{ProxyOptions} = 'UseReverseProxyAuth'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $proxy_user_file, AuthGroupFile => $proxy_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($proxy_user, $proxy_passwd); my $resp_msg = $client->response_msg(); my $expected = "User $proxy_user logged in"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_passiveports_pasv { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $timeout_idle = 10; my $min_port = 49152; my $max_port = 49652; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, PassivePorts => "$min_port $max_port", IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($user, $passwd); my ($resp_code, $resp_msg) = $client->pasv(); my $expected; $expected = 227; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = '^Entering Passive Mode \(\d+,\d+,\d+,\d+,\d+,\d+\)'; $self->assert(qr/$expected/, $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); # Make sure that the chosen port is within our configured PassivePorts # range. if ($resp_msg =~ /^Entering Passive Mode \(\d+,\d+,\d+,\d+,(\d+),(\d+)\)/) { my $p1 = $1; my $p2 = $2; my $port = ($p1 * 256) + $p2; $self->assert($port >= $min_port && $port <= $max_port, test_msg("Selected port $port not within PassivePorts $min_port $max_port")); } else { die("PASV response message '$resp_msg' not matched"); } ($resp_code, $resp_msg) = $client->quit(); $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_passiveports_epsv { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $timeout_idle = 10; my $min_port = 49152; my $max_port = 49652; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, PassivePorts => "$min_port $max_port", IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($user, $passwd); my ($resp_code, $resp_msg) = $client->epsv(); my $expected; $expected = 229; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = '^Entering Extended Passive Mode \(\|\|\|\d+\|\)'; $self->assert(qr/$expected/, $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); # Make sure that the chosen port is within our configured PassivePorts # range. if ($resp_msg =~ /^Entering Extended Passive Mode \(\|\|\|(\d+)\|\)/) { my $port = $1; $self->assert($port >= $min_port && $port <= $max_port, test_msg("Selected port $port not within PassivePorts $min_port $max_port")); } else { die("EPSV response message '$resp_msg' not matched"); } ($resp_code, $resp_msg) = $client->quit(); $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_masqueradeaddress { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $timeout_idle = 10; my $masq_addr = '1.2.3.4'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, MasqueradeAddress => $masq_addr, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($user, $passwd); my ($resp_code, $resp_msg) = $client->pasv(); my $expected; $expected = 227; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = '^Entering Passive Mode \(\d+,\d+,\d+,\d+,\d+,\d+\)'; $self->assert(qr/$expected/, $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); # Make sure that the reported address is our MasqueradeAddress. if ($resp_msg =~ /^Entering Passive Mode \((\d+,\d+,\d+,\d+),\d+,\d+\)/) { my $addr = $1; $addr =~ s/,/./g; $self->assert($addr eq $masq_addr, test_msg("Expected address '$masq_addr', got '$addr'")); } else { die("PASV response message '$resp_msg' not matched"); } ($resp_code, $resp_msg) = $client->quit(); $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_allowforeignaddress_port { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, AllowForeignAddress => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($user, $passwd); my ($resp_code, $resp_msg) = $client->port('1,2,3,4,192,168'); my $expected; $expected = 200; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'PORT command successful'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); ($resp_code, $resp_msg) = $client->quit(); $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_allowforeignaddress_eprt { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, AllowForeignAddress => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($user, $passwd); my ($resp_code, $resp_msg) = $client->eprt('|1|1.2.3.4|49152|'); my $expected; $expected = 200; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'EPRT command successful'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); ($resp_code, $resp_msg) = $client->quit(); $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_timeoutidle_frontend { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $frontend_timeoutidle = 4; my $frontend_timeout_delay = $frontend_timeoutidle + 2; my $backend_timeoutidle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $frontend_timeoutidle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $backend_timeoutidle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($user, $passwd); # Wait for more than the frontend TimeoutIdle period if ($ENV{TEST_VERBOSE}) { print STDOUT " + sleeping for $frontend_timeout_delay secs for TimeoutIdle\n"; } sleep($frontend_timeout_delay); eval { $client->noop() }; unless ($@) { die("NOOP succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected; $expected = 421; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "Idle timeout ($frontend_timeoutidle seconds): closing control connection"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $backend_timeoutidle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_timeoutidle_backend { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $frontend_timeoutidle = 10; my $frontend_timeout_delay = $frontend_timeoutidle - 2; my $backend_timeoutidle = 4; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $frontend_timeoutidle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $backend_timeoutidle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($user, $passwd); # Wait for more than the frontend TimeoutIdle period if ($ENV{TEST_VERBOSE}) { print STDOUT " + sleeping for $frontend_timeout_delay secs for TimeoutIdle\n"; } sleep($frontend_timeout_delay); eval { $client->noop() }; unless ($@) { die("NOOP succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected; $expected = 421; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "Idle timeout ($backend_timeoutidle seconds): closing control connection"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $frontend_timeoutidle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_timeoutlogin_frontend { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $frontend_timeoutidle = 10; my $frontend_timeoutlogin = 2; my $frontend_timeout_delay = $frontend_timeoutlogin + 2; my $backend_timeoutidle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $frontend_timeoutidle, TimeoutLogin => $frontend_timeoutlogin, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $backend_timeoutidle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($user, $passwd); # Wait for more than the frontend TimeoutLogin period if ($ENV{TEST_VERBOSE}) { print STDOUT " + sleeping for $frontend_timeout_delay secs for TimeoutLogin\n"; } sleep($frontend_timeout_delay); # Since we've authenticated to the backend, the frontend's TimeoutLogin # should not kick in; mod_proxy should have removed it. Which means # that this NOOP should work. $client->noop(); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $frontend_timeoutidle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_timeoutnoxfer_frontend { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $frontend_timeoutnoxfer = 2; my $frontend_timeout_delay = $frontend_timeoutnoxfer + 2; my $backend_timeoutnoxfer = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutNoTransfer => $frontend_timeoutnoxfer, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutNoTransfer $backend_timeoutnoxfer TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($user, $passwd); # Wait for more than the frontend TimeoutNoTransfer period if ($ENV{TEST_VERBOSE}) { print STDOUT " + sleeping for $frontend_timeout_delay secs for TimeoutNoTransfer\n"; } sleep($frontend_timeout_delay); my $conn = $client->list_raw(); if ($conn) { die("LIST succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected; $expected = 421; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "No transfer timeout ($frontend_timeoutnoxfer seconds): closing control connection"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $backend_timeoutnoxfer + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_timeoutnoxfer_backend { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $frontend_timeoutnoxfer = 10; my $frontend_timeout_delay = $frontend_timeoutnoxfer - 2; my $backend_timeoutnoxfer = 2; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutNoTransfer => $frontend_timeoutnoxfer, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutNoTransfer $backend_timeoutnoxfer TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($user, $passwd); # Wait for more than the frontend TimeoutNoTransfer period if ($ENV{TEST_VERBOSE}) { print STDOUT " + sleeping for $frontend_timeout_delay secs for TimeoutNoTransfer\n"; } sleep($frontend_timeout_delay); my $conn = $client->list_raw(); if ($conn) { die("LIST succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected; $expected = 421; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "No transfer timeout ($backend_timeoutnoxfer seconds): closing control connection"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $frontend_timeoutnoxfer + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_timeoutstalled_frontend { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); my $test_file = File::Spec->rel2abs("$tmpdir/test.txt"); if (open(my $fh, "> $test_file")) { unless (close($fh)) { die("Can't write $test_file: $!"); } # Make sure that, if we're running as root, that the test file has # permissions/privs set for the account we create if ($< == 0) { unless (chown($setup->{uid}, $setup->{gid}, $test_file)) { die("Can't set owner of $test_file to $setup->{uid}/$setup->{gid}: $!"); } } } else { die("Can't open $test_file: $!"); } my $frontend_timeoutstalled = 2; my $frontend_timeout_delay = $frontend_timeoutstalled + 2; my $backend_timeoutstalled = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutStalled => $frontend_timeoutstalled, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off AllowOverwrite on TimeoutStalled $backend_timeoutstalled TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow server to start up sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->stor_raw('test.txt'); unless ($conn) { die("STOR failed: " . $client->response_code() . " " . $client->response_msg()); } # Wait for more than the frontend TimeoutStalled period if ($ENV{TEST_VERBOSE}) { print STDOUT " + sleeping for $frontend_timeout_delay secs for TimeoutStalled\n"; } sleep($frontend_timeout_delay); my $buf = "Hello, World!\n"; $conn->write($buf, length($buf), 25); eval { $conn->close() }; eval { $client->noop() }; unless ($@) { die("NOOP succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); if ($ENV{TEST_VERBOSE}) { print STDERR "# response: $resp_code $resp_msg\n"; } my $expected; # Perl's Net::Cmd module (depending on version!) uses a very non-standard # 599 code to indicate that the connection is closed. Other versions # use the more standard 421 response code. $self->assert($resp_code == 421 || $resp_code == 599,, test_msg("Expected response code 421 or 599, got $resp_code")); $expected = 'Connection closed'; $self->assert(qr/$expected/, $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $backend_timeoutstalled + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_config_timeoutstalled_backend { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); my $test_file = File::Spec->rel2abs("$tmpdir/test.txt"); if (open(my $fh, "> $test_file")) { unless (close($fh)) { die("Can't write $test_file: $!"); } # Make sure that, if we're running as root, that the test file has # permissions/privs set for the account we create if ($< == 0) { unless (chown($setup->{uid}, $setup->{gid}, $test_file)) { die("Can't set owner of $test_file to $setup->{uid}/$setup->{gid}: $!"); } } } else { die("Can't open $test_file: $!"); } my $frontend_timeoutstalled = 12; my $backend_timeoutstalled = 2; my $frontend_timeout_delay = $backend_timeoutstalled + 2; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutStalled => $frontend_timeoutstalled, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off AllowOverwrite on TimeoutStalled $backend_timeoutstalled TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow server to start up sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->stor_raw('test.txt'); unless ($conn) { die("STOR failed: " . $client->response_code() . " " . $client->response_msg()); } # Wait for more than the backend TimeoutStalled period if ($ENV{TEST_VERBOSE}) { print STDOUT " + sleeping for $frontend_timeout_delay secs for TimeoutStalled\n"; } sleep($frontend_timeout_delay); my $buf = "Hello, World!\n"; $conn->write($buf, length($buf), 25); eval { $conn->close() }; eval { $client->noop() }; unless ($@) { die("NOOP succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); # Perl's Net::Cmd module (depending on version!) uses a very non-standard # 599 code to indicate that the connection is closed. Other versions # use the more standard 421 response code. $self->assert($resp_code == 421 || $resp_code == 599,, test_msg("Expected response code 421 or 599, got $resp_code")); my $expected = 'Connection closed'; $self->assert(qr/$expected/, $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $frontend_timeoutstalled + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_config_datatransferpolicy_pasv_list_pasv { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyDataTransferPolicy} = 'PASV'; my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($user, $passwd); my $conn = $client->list_raw(); unless ($conn) { die("Failed to LIST: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 10); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); ($resp_code, $resp_msg) = $client->quit(); my $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_datatransferpolicy_pasv_list_port { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyDataTransferPolicy} = 'PASV'; my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1, 1); $client->login($user, $passwd); my $conn = $client->list_raw(); unless ($conn) { die("Failed to LIST: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 10); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); ($resp_code, $resp_msg) = $client->quit(); my $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_datatransferpolicy_port_list_pasv { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyDataTransferPolicy} = 'PORT'; my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($user, $passwd); my $conn = $client->list_raw(); unless ($conn) { die("Failed to LIST: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 10); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); ($resp_code, $resp_msg) = $client->quit(); my $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_datatransferpolicy_port_list_port { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyDataTransferPolicy} = 'PORT'; my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1, 1); $client->login($user, $passwd); my $conn = $client->list_raw(); unless ($conn) { die("Failed to LIST: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 10); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); ($resp_code, $resp_msg) = $client->quit(); my $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_datatransferpolicy_epsv_list_pasv { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyDataTransferPolicy} = 'EPSV'; my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($user, $passwd); my $conn = $client->list_raw(); unless ($conn) { die("Failed to LIST: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 10); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); ($resp_code, $resp_msg) = $client->quit(); my $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_datatransferpolicy_epsv_list_port { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyDataTransferPolicy} = 'EPSV'; my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.xfer:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1, 1); $client->login($user, $passwd); my $conn = $client->list_raw(); unless ($conn) { die("Failed to LIST: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 10); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); ($resp_code, $resp_msg) = $client->quit(); my $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_datatransferpolicy_eprt_list_pasv { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyDataTransferPolicy} = 'EPRT'; my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($user, $passwd); my $conn = $client->list_raw(); unless ($conn) { die("Failed to LIST: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 10); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); ($resp_code, $resp_msg) = $client->quit(); my $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_datatransferpolicy_eprt_list_port { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyDataTransferPolicy} = 'EPRT'; my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1, 1); $client->login($user, $passwd); my $conn = $client->list_raw(); unless ($conn) { die("Failed to LIST: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 10); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); ($resp_code, $resp_msg) = $client->quit(); my $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_datatransferpolicy_active_list_pasv { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyDataTransferPolicy} = 'active'; my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($user, $passwd); my $conn = $client->list_raw(); unless ($conn) { die("Failed to LIST: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 10); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); ($resp_code, $resp_msg) = $client->quit(); my $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_datatransferpolicy_active_list_port { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyDataTransferPolicy} = 'active'; my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1, 1); $client->login($user, $passwd); my $conn = $client->list_raw(); unless ($conn) { die("Failed to LIST: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 10); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); ($resp_code, $resp_msg) = $client->quit(); my $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_datatransferpolicy_active_list_eprt_port_fallback { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyDataTransferPolicy} = 'active'; my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off DenyAll EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($user, $passwd); my $conn = $client->list_raw(); unless ($conn) { die("Failed to LIST: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 10); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); ($resp_code, $resp_msg) = $client->quit(); my $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_datatransferpolicy_passive_list_pasv { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyDataTransferPolicy} = 'passive'; my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($user, $passwd); my $conn = $client->list_raw(); unless ($conn) { die("Failed to LIST: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 10); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); ($resp_code, $resp_msg) = $client->quit(); my $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_datatransferpolicy_passive_list_epsv_pasv_fallback { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyDataTransferPolicy} = 'passive'; my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off DenyAll EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($user, $passwd); my $conn = $client->list_raw(); unless ($conn) { die("Failed to LIST: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 10); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); ($resp_code, $resp_msg) = $client->quit(); my $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_datatransferpolicy_passive_list_port { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyDataTransferPolicy} = 'passive'; my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1, 1); $client->login($user, $passwd); my $conn = $client->list_raw(); unless ($conn) { die("Failed to LIST: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 10); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); ($resp_code, $resp_msg) = $client->quit(); my $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_datatransferpolicy_client { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyDataTransferPolicy} = 'client'; my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1, 5); $client->login($user, $passwd); my $conn = $client->list_raw(); unless ($conn) { die("Failed to LIST: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 10); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); ($resp_code, $resp_msg) = $client->quit(); my $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_directorylistpolicy_client { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyDirectoryListPolicy} = 'client'; my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off FactsAdvertise off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1, 1); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->mlsd_raw(); unless ($conn) { die("Failed to MLSD: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 10); eval { $conn->close() }; if ($ENV{TEST_VERBOSE}) { print STDERR "# Response:\n$buf\n"; } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); ($resp_code, $resp_msg) = $client->quit(); my $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); # We have to be careful of the fact that readdir returns directory # entries in an unordered fashion. my $res = {}; my $lines = [split(/(\r)?\n/, $buf)]; $self->assert(scalar(@$lines) > 1, test_msg("Expected several MLSD lines, got " . scalar(@$lines))); foreach my $line (@$lines) { $line = '' unless defined($line); if ($line =~ /^modify=\S+;perm=\S+;type=\S+;unique=\S+;UNIX\.group=\d+;UNIX\.groupname=\S+;UNIX\.mode=\d+;UNIX\.owner=\d+;UNIX\.ownername=\S+; (.*?)$/) { $res->{$1} = 1; } } if (scalar(keys(%$res)) == 0) { die("Failed to parse MLSD data"); } $expected = { '.' => 1, '..' => 1, 'var' => 1, 'proxy.conf' => 1, 'proxy.group' => 1, 'proxy.passwd' => 1, 'proxy.pid' => 1, 'proxy.scoreboard' => 1, 'proxy.scoreboard.lck' => 1, }; my $ok = 1; my $mismatch; foreach my $name (keys(%$res)) { unless (defined($expected->{$name})) { $mismatch = $name; $ok = 0; last; } } unless ($ok) { die("Unexpected name '$mismatch' appeared in MLSD data") } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_config_directorylistpolicy_list_unix { my $self = shift; my $tmpdir = $self->{tmpdir}; # Explicitly provide these IDs, so that the generated AuthUserFile, # AuthGroupFile entries match our effective IDs, and thus can match up # IDs to names. This means that "LIST -al" provides textual owner names, # rather than IDs. my $uid = $<; my $gid = (split(/\s+/, $)))[0]; my $setup = test_setup($tmpdir, 'proxy', undef, undef, undef, $uid, $gid); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyDirectoryListPolicy} = 'LIST'; my $timeout_idle = 10; my $test_file = File::Spec->rel2abs("$tmpdir/test.dat"); if (open(my $fh, "> $test_file")) { print $fh "Hello, World!\n"; unless (close($fh)) { die("Can't write $test_file: $!"); } } else { die("Can't open $test_file: $!"); } # Make the timestamps on our file older than 6 months, for the year/HH:MM # code path. my $ts = time() - (9 * 30 * 24 * 60 * 60); unless (utime($ts, $ts, $test_file)) { die("Can't change times on $test_file: $!"); } my $sub_dir = File::Spec->rel2abs("$tmpdir/sub.d"); mkpath($sub_dir); my $cwd = getcwd(); unless (chdir($tmpdir)) { die("Can't chdir to $tmpdir: $!"); } unless (symlink('./test.dat', 'test.lnk')) { die("Can't symlink './test.dat' to 'test.lnk': $!"); } unless (symlink('./sub.d', 'subd.lnk')) { die("Can't symlink './sub.d' to 'subd.lnk': $!"); } unless (chdir($cwd)) { die("Can't chdir to $cwd: $!"); } my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:30 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.dirlist:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', RootLogin => 'on', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off RootLogin on TimeoutIdle $timeout_idle TransferLog none WtmpLog off FactsAdvertise off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1, 1); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->mlsd_raw(); unless ($conn) { die("Failed to MLSD: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 10); eval { $conn->close() }; if ($ENV{TEST_VERBOSE}) { print STDERR "# Response:\n$buf\n"; } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); ($resp_code, $resp_msg) = $client->quit(); my $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); # We have to be careful of the fact that readdir returns directory # entries in an unordered fashion. my $res = {}; my $lines = [split(/(\r)?\n/, $buf)]; $self->assert(scalar(@$lines) > 1, test_msg("Expected several MLSD lines, got " . scalar(@$lines))); foreach my $line (@$lines) { $line = '' unless defined($line); if ($line =~ /^modify=\S+;perm=\S+;type=\S+;UNIX\.groupname=\S+;UNIX\.mode=\d+;UNIX\.ownername=\S+; (.*?)$/) { $res->{$1} = 1; } } if (scalar(keys(%$res)) == 0) { die("Failed to parse MLSD data"); } $expected = { '.' => 1, '..' => 1, 'sub.d' => 1, 'subd.lnk' => 1, 'test.dat' => 1, 'test.lnk' => 1, 'var' => 1, 'proxy.conf' => 1, 'proxy.group' => 1, 'proxy.passwd' => 1, 'proxy.pid' => 1, 'proxy.scoreboard' => 1, 'proxy.scoreboard.lck' => 1, }; my $ok = 1; my $mismatch; foreach my $name (keys(%$res)) { unless (defined($expected->{$name})) { $mismatch = $name; $ok = 0; last; } } unless ($ok) { die("Unexpected name '$mismatch' appeared in MLSD data") } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_config_directorylistpolicy_list_unix_use_slink { my $self = shift; my $tmpdir = $self->{tmpdir}; # Explicitly provide these IDs, so that the generated AuthUserFile, # AuthGroupFile entries match our effective IDs, and thus can match up # IDs to names. This means that "LIST -al" provides textual owner names, # rather than IDs. my $uid = $<; my $gid = (split(/\s+/, $)))[0]; my $setup = test_setup($tmpdir, 'proxy', undef, undef, undef, $uid, $gid); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); # Use the UseSlink option as well. $proxy_config->{ProxyDirectoryListPolicy} = 'LIST UseSlink'; my $timeout_idle = 10; my $test_file = File::Spec->rel2abs("$tmpdir/test.dat"); if (open(my $fh, "> $test_file")) { print $fh "Hello, World!\n"; unless (close($fh)) { die("Can't write $test_file: $!"); } } else { die("Can't open $test_file: $!"); } # Make the timestamps on our file older than 6 months, for the year/HH:MM # code path. my $ts = time() - (9 * 30 * 24 * 60 * 60); unless (utime($ts, $ts, $test_file)) { die("Can't change times on $test_file: $!"); } my $sub_dir = File::Spec->rel2abs("$tmpdir/sub.d"); mkpath($sub_dir); my $cwd = getcwd(); unless (chdir($tmpdir)) { die("Can't chdir to $tmpdir: $!"); } unless (symlink('./test.dat', 'test.lnk')) { die("Can't symlink './test.dat' to 'test.lnk': $!"); } unless (symlink('./sub.d', 'subd.lnk')) { die("Can't symlink './sub.d' to 'subd.lnk': $!"); } unless (chdir($cwd)) { die("Can't chdir to $cwd: $!"); } my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:30 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.dirlist:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', RootLogin => 'on', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off RootLogin on TimeoutIdle $timeout_idle TransferLog none WtmpLog off FactsAdvertise off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1, 1); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->mlsd_raw(); unless ($conn) { die("Failed to MLSD: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 10); eval { $conn->close() }; if ($ENV{TEST_VERBOSE}) { print STDERR "# Response:\n$buf\n"; } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); ($resp_code, $resp_msg) = $client->quit(); my $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); my $saw_slink = 0; # We have to be careful of the fact that readdir returns directory # entries in an unordered fashion. my $res = {}; my $lines = [split(/(\r)?\n/, $buf)]; $self->assert(scalar(@$lines) > 1, test_msg("Expected several MLSD lines, got " . scalar(@$lines))); foreach my $line (@$lines) { $line = '' unless defined($line); if ($line =~ /^modify=\S+;perm=\S+;type=\S+;UNIX\.groupname=\S+;UNIX\.mode=\d+;UNIX\.ownername=\S+; (.*?)$/) { $res->{$1} = 1; } if ($line =~ /type=OS.unix=slink:/) { $saw_slink = 1; } } if (scalar(keys(%$res)) == 0) { die("Failed to parse MLSD data"); } $self->assert($saw_slink, test_msg("Did not see 'OS.unix=slink' as expected")); $expected = { '.' => 1, '..' => 1, 'sub.d' => 1, 'subd.lnk' => 1, 'test.dat' => 1, 'test.lnk' => 1, 'var' => 1, 'proxy.conf' => 1, 'proxy.group' => 1, 'proxy.passwd' => 1, 'proxy.pid' => 1, 'proxy.scoreboard' => 1, 'proxy.scoreboard.lck' => 1, }; my $ok = 1; my $mismatch; foreach my $name (keys(%$res)) { unless (defined($expected->{$name})) { $mismatch = $name; $ok = 0; last; } } unless ($ok) { die("Unexpected name '$mismatch' appeared in MLSD data") } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_config_directorylistpolicy_list_unix_wide_dir { my $self = shift; my $tmpdir = $self->{tmpdir}; # Explicitly provide these IDs, so that the generated AuthUserFile, # AuthGroupFile entries match our effective IDs, and thus can match up # IDs to names. This means that "LIST -al" provides textual owner names, # rather than IDs. my $uid = $<; my $gid = (split(/\s+/, $)))[0]; my $setup = test_setup($tmpdir, 'proxy', undef, undef, undef, $uid, $gid); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyDirectoryListPolicy} = 'LIST'; # For this test, we need to create many files to be listed. my $test_file_prefix = File::Spec->rel2abs($tmpdir); my $count = 1000; print STDOUT "# Creating $count files in $tmpdir\n"; for (my $i = 1; $i <= $count; $i++) { my $test_file = 'test_' . sprintf("%07s", $i); my $test_path = "$test_file_prefix/$test_file"; if (open(my $fh, "> $test_path")) { close($fh); } else { die("Can't open $test_path: $!"); } if ($i % 1000 == 0) { print STDOUT "# Created file $test_file\n"; } } my $timeout_idle = 30; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:30 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:30 proxy.ftp.dirlist:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', RootLogin => 'on', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off RootLogin on TimeoutIdle $timeout_idle TransferLog none WtmpLog off FactsAdvertise off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1, 1); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->mlsd_raw(); unless ($conn) { die("Failed to MLSD: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf = ''; my $tmp; while ($conn->read($tmp, 8192, 30)) { $buf .= $tmp; } eval { $conn->close() }; if ($ENV{TEST_VERBOSE}) { print STDERR "# Response:\n$buf\n"; } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); ($resp_code, $resp_msg) = $client->quit(); my $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); my $saw_slink = 0; # We have to be careful of the fact that readdir returns directory # entries in an unordered fashion. my $res = {}; my $lines = [split(/(\r)?\n/, $buf)]; $self->assert(scalar(@$lines) > 1, test_msg("Expected several MLSD lines, got " . scalar(@$lines))); foreach my $line (@$lines) { $line = '' unless defined($line); if ($line =~ /^modify=\S+;perm=\S+;type=\S+;UNIX\.groupname=\S+;UNIX\.mode=\d+;UNIX\.ownername=\S+; (.*?)$/) { $res->{$1} = 1; } if ($line =~ /type=OS.unix=slink:/) { $saw_slink = 1; } } if (scalar(keys(%$res)) == 0) { die("Failed to parse MLSD data"); } $expected = { '.' => 1, '..' => 1, 'sub.d' => 1, 'subd.lnk' => 1, 'test.dat' => 1, 'test.lnk' => 1, 'var' => 1, 'proxy.conf' => 1, 'proxy.group' => 1, 'proxy.passwd' => 1, 'proxy.pid' => 1, 'proxy.scoreboard' => 1, 'proxy.scoreboard.lck' => 1, }; my $ok = 1; my $mismatch; foreach my $name (keys(%$res)) { next if $name =~ /^test_/; unless (defined($expected->{$name})) { $mismatch = $name; $ok = 0; last; } } unless ($ok) { die("Unexpected name '$mismatch' appeared in MLSD data") } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_config_directorylistpolicy_list_windows { my $self = shift; my $tmpdir = $self->{tmpdir}; # Explicitly provide these IDs, so that the generated AuthUserFile, # AuthGroupFile entries match our effective IDs, and thus can match up # IDs to names. This means that "LIST -al" provides textual owner names, # rather than IDs. my $uid = $<; my $gid = (split(/\s+/, $)))[0]; my $setup = test_setup($tmpdir, 'proxy', undef, undef, undef, $uid, $gid); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyDirectoryListPolicy} = 'LIST'; my $timeout_idle = 10; # Make sure we use a non-UTC timezone, so the strftime(3)'s `%p` is # populated properly. # TODO: Make mod_ls, mod_proxy handle this UTC case better! $ENV{TZ} = 'PST'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.dirlist:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', RootLogin => 'on', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off RootLogin on TimeoutIdle $timeout_idle TransferLog none WtmpLog off FactsAdvertise off # Emit Windows-style directory listing data ListStyle Windows EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->mlsd_raw(); unless ($conn) { die("Failed to MLSD: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 10); eval { $conn->close() }; if ($ENV{TEST_VERBOSE}) { print STDERR "# Response:\n$buf\n"; } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); ($resp_code, $resp_msg) = $client->quit(); my $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); # We have to be careful of the fact that readdir returns directory # entries in an unordered fashion. my $res = {}; my $lines = [split(/(\r)?\n/, $buf)]; $self->assert(scalar(@$lines) > 1, test_msg("Expected several MLSD lines, got " . scalar(@$lines))); foreach my $line (@$lines) { $line = '' unless defined($line); if ($line =~ /^modify=\S+;(size=\d+;)?type=\S+; (.*?)$/) { $res->{$2} = 1; } } if (scalar(keys(%$res)) == 0) { die("Failed to parse MLSD data"); } $expected = { '.' => 1, '..' => 1, 'var' => 1, 'proxy.conf' => 1, 'proxy.group' => 1, 'proxy.passwd' => 1, 'proxy.pid' => 1, 'proxy.scoreboard' => 1, 'proxy.scoreboard.lck' => 1, }; my $ok = 1; my $mismatch; foreach my $name (keys(%$res)) { unless (defined($expected->{$name})) { $mismatch = $name; $ok = 0; last; } } unless ($ok) { die("Unexpected name '$mismatch' appeared in MLSD data") } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_config_directorylistpolicy_list_backend_error { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyDirectoryListPolicy} = 'LIST'; my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.dirlist:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off FactsAdvertise off # Block the LIST command, to test mod_proxy error handling DenyAll EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1, 1); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->mlsd_raw(); unless ($conn) { die("Failed to MLSD: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 10); eval { $conn->close() }; if ($ENV{TEST_VERBOSE}) { print STDERR "# Response:\n$buf\n"; } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); ($resp_code, $resp_msg) = $client->quit(); my $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $self->assert($buf eq '', test_msg("Expected empty directory listing, got '$buf'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_config_directorylistpolicy_list_opts_mlst { my $self = shift; my $tmpdir = $self->{tmpdir}; # Explicitly provide these IDs, so that the generated AuthUserFile, # AuthGroupFile entries match our effective IDs, and thus can match up # IDs to names. This means that "LIST -al" provides textual owner names, # rather than IDs. my $uid = $<; my $gid = (split(/\s+/, $)))[0]; my $setup = test_setup($tmpdir, 'proxy', undef, undef, undef, $uid, $gid); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyDirectoryListPolicy} = 'LIST'; my $timeout_idle = 10; my $test_file = File::Spec->rel2abs("$tmpdir/test.dat"); if (open(my $fh, "> $test_file")) { print $fh "Hello, World!\n"; unless (close($fh)) { die("Can't write $test_file: $!"); } } else { die("Can't open $test_file: $!"); } # Make the timestamps on our file older than 6 months, for the year/HH:MM # code path. my $ts = time() - (9 * 30 * 24 * 60 * 60); unless (utime($ts, $ts, $test_file)) { die("Can't change times on $test_file: $!"); } my $sub_dir = File::Spec->rel2abs("$tmpdir/sub.d"); mkpath($sub_dir); my $cwd = getcwd(); unless (chdir($tmpdir)) { die("Can't chdir to $tmpdir: $!"); } unless (symlink('./test.dat', 'test.lnk')) { die("Can't symlink './test.dat' to 'test.lnk': $!"); } unless (symlink('./sub.d', 'subd.lnk')) { die("Can't symlink './sub.d' to 'subd.lnk': $!"); } unless (chdir($cwd)) { die("Can't chdir to $cwd: $!"); } my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:30 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.dirlist:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', RootLogin => 'on', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off RootLogin on TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1, 1); $client->login($setup->{user}, $setup->{passwd}); my $opts_args = 'MLST modify;size;type;'; my ($resp_code, $resp_msg) = $client->opts($opts_args); my $expected = 200; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'MLST OPTS modify;size;type;'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); my $conn = $client->mlsd_raw(); unless ($conn) { die("Failed to MLSD: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 10); eval { $conn->close() }; if ($ENV{TEST_VERBOSE}) { print STDERR "# Response:\n$buf\n"; } $resp_code = $client->response_code(); $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); ($resp_code, $resp_msg) = $client->quit(); $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); # We have to be careful of the fact that readdir returns directory # entries in an unordered fashion. my $res = {}; my $lines = [split(/(\r)?\n/, $buf)]; $self->assert(scalar(@$lines) > 1, test_msg("Expected several MLSD lines, got " . scalar(@$lines))); foreach my $line (@$lines) { $line = '' unless defined($line); if ($line =~ /^modify=\S+;size=\d+;type=\S+; (.*?)$/) { $res->{$1} = 1; } } if (scalar(keys(%$res)) == 0) { die("Failed to parse MLSD data"); } $expected = { '.' => 1, '..' => 1, 'sub.d' => 1, 'subd.lnk' => 1, 'test.dat' => 1, 'test.lnk' => 1, 'var' => 1, 'proxy.conf' => 1, 'proxy.group' => 1, 'proxy.passwd' => 1, 'proxy.pid' => 1, 'proxy.scoreboard' => 1, 'proxy.scoreboard.lck' => 1, }; my $ok = 1; my $mismatch; foreach my $name (keys(%$res)) { unless (defined($expected->{$name})) { $mismatch = $name; $ok = 0; last; } } unless ($ok) { die("Unexpected name '$mismatch' appeared in MLSD data") } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_config_connect_policy_random { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyReverseConnectPolicy} = 'Random'; # For now, we cheat and simply repeat the same vhost three times $proxy_config->{ProxyReverseServers} = "ftp://127.0.0.1:$vhost_port ftp://127.0.0.1:$vhost_port ftp://127.0.0.1:$vhost_port"; my $nbackends = 3; my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.reverse:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { for (my $i = 0; $i < $nbackends+1; $i++) { sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1, 1); $client->login($user, $passwd); ftp_list($self, $client); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_connect_policy_shuffle { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyReverseConnectPolicy} = 'Shuffle'; # For now, we cheat and simply repeat the same vhost three times $proxy_config->{ProxyReverseServers} = "ftp://127.0.0.1:$vhost_port ftp://127.0.0.1:$vhost_port ftp://127.0.0.1:$vhost_port"; my $nbackends = 3; my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.reverse:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { for (my $i = 0; $i < $nbackends+1; $i++) { sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1, 1); $client->login($user, $passwd); ftp_list($self, $client); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_connect_policy_roundrobin { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyReverseConnectPolicy} = 'RoundRobin'; # For now, we cheat and simply repeat the same vhost three times $proxy_config->{ProxyReverseServers} = "ftp://127.0.0.1:$vhost_port ftp://127.0.0.1:$vhost_port ftp://127.0.0.1:$vhost_port"; my $nbackends = 3; my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.reverse:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { for (my $i = 0; $i < $nbackends+1; $i++) { sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1, 1); $client->login($user, $passwd); ftp_list($self, $client); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_connect_policy_roundrobin_issue132 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyReverseConnectPolicy} = 'RoundRobin'; # For now, we cheat and simply repeat the same vhost three times. Only # the first connect should succeed; the next two should fail, if we are doing # RoundRobin properly (Issue #132). $proxy_config->{ProxyReverseServers} = "ftp://127.0.0.1:$vhost_port ftp://127.0.0.1:4321 ftp://127.0.0.1:5432"; my $nbackends = 3; # Ensure we only retry once $proxy_config->{ProxyRetryCount} = 1; my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.reverse:20 proxy.reverse.db:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(2); # First session should succeed. my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1, 1); $client->login($user, $passwd); ftp_list($self, $client); # Next session should fail. eval { ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1, 1) }; unless ($@) { die("Second connect succeeded unexpectedly"); } my $err = $@; my $expected = 'Unable to connect to .*: Timed out'; $self->assert(qr/$expected/, $err, test_msg("Expected error '$expected', got '$err'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_connect_policy_leastconns { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyReverseConnectPolicy} = 'LeastConns'; # For now, we cheat and simply repeat the same vhost three times $proxy_config->{ProxyReverseServers} = "ftp://127.0.0.1:$vhost_port ftp://127.0.0.1:$vhost_port ftp://127.0.0.1:$vhost_port"; my $nbackends = 3; my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.reverse:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { for (my $i = 0; $i < $nbackends+1; $i++) { sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1, 1); $client->login($user, $passwd); ftp_list($self, $client); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_connect_policy_leastresponsetime { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyReverseConnectPolicy} = 'LeastResponseTime'; # For now, we cheat and simply repeat the same vhost three times $proxy_config->{ProxyReverseServers} = "ftp://127.0.0.1:$vhost_port ftp://127.0.0.1:$vhost_port ftp://127.0.0.1:$vhost_port"; my $nbackends = 3; my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.reverse:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { for (my $i = 0; $i < $nbackends+1; $i++) { sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($user, $passwd); ftp_list($self, $client); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_connect_policy_per_host { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $vhost_port2 = $vhost_port - 7; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyTimeoutConnect} = '1sec'; $proxy_config->{ProxyReverseConnectPolicy} = 'PerHost'; $proxy_config->{ProxyReverseServers} = "ftp://127.0.0.1:$vhost_port ftp://127.0.0.1:$vhost_port2"; my $nbackends = 2; my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.reverse:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off Port $vhost_port2 ServerName "Other Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { for (my $i = 0; $i < $nbackends+1; $i++) { sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 0); $client->login($user, $passwd); ftp_list($self, $client); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_connect_policy_per_user { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $vhost_port2 = $vhost_port - 7; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyTimeoutConnect} = '1sec'; $proxy_config->{ProxyReverseConnectPolicy} = 'PerUser'; $proxy_config->{ProxyReverseServers} = "ftp://127.0.0.1:$vhost_port ftp://127.0.0.1:$vhost_port2"; my $nbackends = 2; my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.reverse:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off Port $vhost_port2 ServerName "Other Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { for (my $i = 0; $i < $nbackends+1; $i++) { sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 0); $client->login($user, $passwd); ftp_list($self, $client); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_connect_policy_per_user_by_json { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $vhost_port2 = $vhost_port - 7; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyTimeoutConnect} = '1sec'; $proxy_config->{ProxyReverseConnectPolicy} = 'PerUser'; $proxy_config->{ProxyReverseServers} = "ftp://127.0.0.1:$vhost_port"; my $user_path = File::Spec->rel2abs("$tmpdir/$user-servers.json"); if (open(my $fh, "> $user_path")) { print $fh "[ \"ftp://127.0.0.1:$vhost_port2\" ]\n"; unless (close($fh)) { die("Can't write $user_path: $!"); } } else { die("Can't open $user_path: $!"); } my $uservar_path = File::Spec->rel2abs("$tmpdir/%U-servers.json"); # Since we need multiple ProxyReverseServers directives, convert this # hashref into an arrayref. $proxy_config = config_hash2array($proxy_config); push(@$proxy_config, "ProxyReverseServers file:$uservar_path"); my $nbackends = 1; my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.reverse:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off Port $vhost_port2 ServerName "Other Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { for (my $i = 0; $i < $nbackends+1; $i++) { sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 0); $client->login($user, $passwd); ftp_list($self, $client); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_connect_policy_per_user_no_fallback_issue148 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $vhost_port2 = $vhost_port - 7; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyTimeoutConnect} = '1sec'; $proxy_config->{ProxyReverseConnectPolicy} = 'PerUser'; # For this issue, we want NO fallback/default servers. delete($proxy_config->{ProxyReverseServers}); my $user_path = File::Spec->rel2abs("$tmpdir/$user-servers.json"); if (open(my $fh, "> $user_path")) { print $fh "[ \"ftp://127.0.0.1:$vhost_port2\" ]\n"; unless (close($fh)) { die("Can't write $user_path: $!"); } } else { die("Can't open $user_path: $!"); } my $uservar_path = File::Spec->rel2abs("$tmpdir/%U-servers.json"); # Since we need multiple ProxyReverseServers directives, convert this # hashref into an arrayref. $proxy_config = config_hash2array($proxy_config); push(@$proxy_config, "ProxyReverseServers file:$uservar_path"); my $nbackends = 1; my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.reverse:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off Port $vhost_port2 ServerName "Other Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(2); # Deliberately use the wrong password; we need to ensure we do not # leak information in such cases (Issue #148). my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 0); eval { $client->login($user, 'WRONG PASSWORD') }; unless ($@) { die("Login with wrong password succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Login incorrect.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); # Deliberately use the wrong username; we need to ensure we do not # leak information in such cases. eval { $client->user('WrongUser') }; unless ($@) { die("USER with unknown username succeeded unexpectedly"); } $resp_code = $client->response_code(); $resp_msg = $client->response_msg(); $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Login incorrect.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_connect_policy_per_group { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $vhost_port2 = $vhost_port - 7; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyTimeoutConnect} = '1sec'; $proxy_config->{ProxyReverseConnectPolicy} = 'PerGroup'; $proxy_config->{ProxyOptions} = 'UseReverseProxyAuth'; $proxy_config->{ProxyReverseServers} = "ftp://127.0.0.1:$vhost_port ftp://127.0.0.1:$vhost_port2"; my $nbackends = 2; my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.reverse:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off Port $vhost_port2 ServerName "Other Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { for (my $i = 0; $i < $nbackends+1; $i++) { sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 0); $client->login($user, $passwd); ftp_list($self, $client); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_connect_policy_per_group_by_json { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $vhost_port2 = $vhost_port - 7; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyTimeoutConnect} = '1sec'; $proxy_config->{ProxyReverseConnectPolicy} = 'PerGroup'; $proxy_config->{ProxyOptions} = 'UseReverseProxyAuth'; $proxy_config->{ProxyReverseServers} = "ftp://127.0.0.1:$vhost_port"; my $group_path = File::Spec->rel2abs("$tmpdir/$group-servers.json"); if (open(my $fh, "> $group_path")) { print $fh "[ \"ftp://127.0.0.1:$vhost_port2\" ]\n"; unless (close($fh)) { die("Can't write $group_path: $!"); } } else { die("Can't open $group_path: $!"); } my $groupvar_path = File::Spec->rel2abs("$tmpdir/%g-servers.json"); # Since we need multiple ProxyReverseServers directives, convert this # hashref into an arrayref. $proxy_config = config_hash2array($proxy_config); push(@$proxy_config, "ProxyReverseServers file:$groupvar_path"); my $nbackends = 1; my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.reverse:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off Port $vhost_port2 ServerName "Other Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { for (my $i = 0; $i < $nbackends+1; $i++) { sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 0); $client->login($user, $passwd); ftp_list($self, $client); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_transfer_rate_retr { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $test_file = File::Spec->rel2abs("$tmpdir/test.txt"); if (open(my $fh, "> $test_file")) { print $fh "ABCDefgh" x 1024, "\n"; unless (close($fh)) { die("Can't write $test_file: $!"); } } else { die("Can't open $test_file: $!"); } my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $timeout_idle = 30; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, # 1 KB/sec for downloads TransferRate => 'RETR 1', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); my $conn = $client->retr_raw('test.txt'); unless ($conn) { die("RETR failed: " . $client->response_code() . " " . $client->response_msg()); } my $buf = ''; my $tmp; my $xfer_start = [gettimeofday()]; while ($conn->read($tmp, 8192, 10)) { $buf .= $tmp; } eval { $conn->close() }; # Allow time for the 226 response to reach us, too. sleep(1); my $xfer_elapsed = tv_interval($xfer_start); my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); ($resp_code, $resp_msg) = $client->quit(); my $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); # We configured a TransferRate of 1 KB/sec, and retrieved 8 KB; # thus make sure that the transfer time is more(-ish) than 8 secs. $self->assert($xfer_elapsed > 6, test_msg("Expected > 6 secs, got $xfer_elapsed")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_use_reverse_proxy_auth_login { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyOptions} = 'UseReverseProxyAuth'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); my $resp_code = $client->response_code(); my $expected = 230; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_use_reverse_proxy_auth_login_extra_user { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyOptions} = 'UseReverseProxyAuth'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($setup->{user}, $setup->{passwd}); my $resp_code = $client->response_code(); my $expected = 230; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); # Note that this behavior changed due to Bug#4217 $client->user($setup->{user}); $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $expected = 230; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "User $setup->{user} logged in"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got $resp_msg")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_config_use_reverse_proxy_auth_login_extra_pass { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyOptions} = 'UseReverseProxyAuth'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); my $resp_code = $client->response_code(); my $expected = 230; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); eval { $client->pass($passwd) }; unless ($@) { die("Extra PASS succeeded unexpectedly"); } $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $expected = 503; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'You are already logged in'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got $resp_msg")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_use_reverse_proxy_auth_login_failed_bad_passwd { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyOptions} = 'UseReverseProxyAuth'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); eval { $client->login($user, 'foobar') }; unless ($@) { die("Login succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Login incorrect.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_use_direct_data_transfers_port { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyOptions} = 'UseDirectDataTransfers'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1, 1); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->list_raw(); unless ($conn) { die("Failed to LIST: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 10); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($self->{log_file}, $ex); } sub proxy_reverse_config_use_direct_data_transfers_port_failed { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyOptions} = 'UseDirectDataTransfers'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none DenyAll EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1, 1); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->list_raw(); if ($conn) { $ex = "LIST succeeded unexpectedly"; } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 501; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'PORT: Operation not permitted'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($self->{log_file}, $ex); } sub proxy_reverse_config_use_direct_data_transfers_list_failed { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyOptions} = 'UseDirectDataTransfers'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none DenyAll EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1, 1); $client->login($setup->{user}, $setup->{passwd}); eval { $client->nlst('.') }; unless ($@) { die("LIST succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 450; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = '.: No such file or directory'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($self->{log_file}, $ex); } sub proxy_reverse_config_use_direct_data_transfers_pasv { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyOptions} = 'UseDirectDataTransfers'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->list_raw(); unless ($conn) { die("Failed to LIST: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 10); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($self->{log_file}, $ex); } sub proxy_reverse_limit_list_deny_all { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, LIST => { DenyAll => '', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); eval { $client->list('.') }; unless ($@) { die("LIST succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'LIST: Operation not permitted'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_limit_list_deny_user { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, LIST => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); eval { $client->list('.') }; unless ($@) { die("LIST succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'LIST: Operation not permitted'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_limit_list_deny_group { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, LIST => { DenyGroup => $group, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); # Even though we configured a "DenyGroup ftpd" limit, this command # should still succeed, because we didn't do proxy auth for this user # which means we don't KNOW the groups. $client->list('.'); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_limit_mlsd_deny_all { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, MLSD => { DenyAll => '', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); eval { $client->mlsd('.') }; unless ($@) { die("MLSD succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'MLSD: Operation not permitted'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_limit_retr_deny_all { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, RETR => { DenyAll => '', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); my $conn = $client->retr_raw($config_file); if ($conn) { die("RETR succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'RETR: Operation not permitted'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_limit_stor_deny_all { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, STOR => { DenyAll => '', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); my $conn = $client->stor_raw('test.dat'); if ($conn) { die("STOR succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'STOR: Operation not permitted'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_limit_dirs_deny_all { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, DIRS => { DenyAll => '', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); eval { $client->mlsd('.') }; unless ($@) { die("MLSD succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'MLSD: Operation not permitted'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_limit_read_deny_all { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, READ => { DenyAll => '', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); my $conn = $client->retr_raw($config_file); if ($conn) { die("RETR succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'RETR: Operation not permitted'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_limit_write_deny_all { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, WRITE => { DenyAll => '', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); my $conn = $client->stor_raw('test.dat'); if ($conn) { die("STOR succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'STOR: Operation not permitted'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_xferlog_retr_ascii_ok { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $test_data = "Hello, Proxying World!\n"; my $test_file = File::Spec->rel2abs("$tmpdir/test.txt"); if (open(my $fh, "> $test_file")) { print $fh $test_data; unless (close($fh)) { die("Unable to write $test_file: $!"); } } else { die("Unable to open $test_file: $!"); } my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $timeout_idle = 10; my $xfer_log = File::Spec->rel2abs("$tmpdir/xfer.log"); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, TransferLog => $xfer_log, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); $client->type('ascii'); my $conn = $client->retr_raw($test_file); unless ($conn) { die("RETR failed: " . $client->response_code() . " " . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 30); my $size = $conn->bytes_read(); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); $client->quit(); # The length of 'Hello, Proxying World!\n' is 23, but we expect 24 # here because of the ASCII conversion of the bare LF to a CRLF. my $expected = length($test_data) + 1; $self->assert($expected == $size, test_msg("Expected $expected, got $size")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $xfer_log")) { my $line = <$fh>; chomp($line); if ($ENV{TEST_VERBOSE}) { print STDOUT "# $line\n"; } my $expected = '^\S+\s+\S+\s+\d+\s+\d+:\d+:\d+\s+\d+\s+\d+\s+(\S+)\s+(\d+)\s+(\S+)\s+(\S+)\s+_\s+o\s+r\s+(\S+)\s+ftp\s+0\s+\*\s+c$'; $self->assert(qr/$expected/, $line, test_msg("Expected '$expected', got '$line'")); if ($line =~ /$expected/) { my $remote_host = $1; my $filesz = $2; my $filename = $3; my $xfer_type = $4; my $user_name = $5; $expected = '127.0.0.1'; $self->assert($expected eq $remote_host, test_msg("Expected host '$expected', got '$remote_host'")); # The length of 'Hello, Proxying World!\n' is 23, but we expect 24 # here because of the ASCII conversion of the bare LF to a CRLF. $expected = length($test_data) + 1; $self->assert($expected == $filesz, test_msg("Expected file size '$expected', got '$filesz'")); $expected = $test_file; $self->assert($expected eq $filename, test_msg("Expected file name '$expected', got '$filename'")); $expected = 'a'; $self->assert($expected eq $xfer_type, test_msg("Expected transfer type '$expected', got '$xfer_type'")); $expected = $user; $self->assert($expected eq $user_name, test_msg("Expected user '$expected', got '$user_name'")); } my $next_line = ''; $next_line = <$fh>; close($fh); if (defined($next_line)) { chomp($next_line); if (length($next_line) > 0) { if ($ENV{TEST_VERBOSE}) { print STDOUT "# $next_line\n"; } die("Expected only one TransferLog entry, got more than one"); } } } else { die("Can't read $xfer_log: $!"); } }; if ($@) { $ex = $@; } if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_xferlog_retr_ascii_chrooted_ok { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $test_data = "Hello, Proxying World!\n"; my $test_file = File::Spec->rel2abs("$tmpdir/test.txt"); if (open(my $fh, "> $test_file")) { print $fh $test_data; unless (close($fh)) { die("Unable to write $test_file: $!"); } } else { die("Unable to open $test_file: $!"); } my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $timeout_idle = 10; my $xfer_log = File::Spec->rel2abs("$tmpdir/xfer.log"); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, TransferLog => $xfer_log, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); $client->type('ascii'); my $conn = $client->retr_raw($test_file); unless ($conn) { die("RETR failed: " . $client->response_code() . " " . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 30); my $size = $conn->bytes_read(); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); $client->quit(); # The length of 'Hello, Proxying World!\n' is 23, but we expect 24 # here because of the ASCII conversion of the bare LF to a CRLF. my $expected = length($test_data) + 1; $self->assert($expected == $size, test_msg("Expected $expected, got $size")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $xfer_log")) { my $line = <$fh>; chomp($line); close($fh); my $expected = '^\S+\s+\S+\s+\d+\s+\d+:\d+:\d+\s+\d+\s+\d+\s+(\S+)\s+(\d+)\s+(\S+)\s+(\S+)\s+_\s+o\s+r\s+(\S+)\s+ftp\s+0\s+\*\s+c$'; $self->assert(qr/$expected/, $line, test_msg("Expected '$expected', got '$line'")); if ($line =~ /$expected/) { my $remote_host = $1; my $filesz = $2; my $filename = $3; my $xfer_type = $4; my $user_name = $5; $expected = '127.0.0.1'; $self->assert($expected eq $remote_host, test_msg("Expected host '$expected', got '$remote_host'")); # The length of 'Hello, Proxying World!\n' is 23, but we expect 24 # here because of the ASCII conversion of the bare LF to a CRLF. $expected = length($test_data) + 1; $self->assert($expected == $filesz, test_msg("Expected file size '$expected', got '$filesz'")); $expected = $test_file; $self->assert($expected eq $filename, test_msg("Expected file name '$expected', got '$filename'")); $expected = 'a'; $self->assert($expected eq $xfer_type, test_msg("Expected transfer type '$expected', got '$xfer_type'")); $expected = $user; $self->assert($expected eq $user_name, test_msg("Expected user '$expected', got '$user_name'")); } } else { die("Can't read $xfer_log: $!"); } }; if ($@) { $ex = $@; } if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_xferlog_retr_binary_ok { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $test_data = "Hello, Proxying World!\n"; my $test_file = File::Spec->rel2abs("$tmpdir/test.txt"); if (open(my $fh, "> $test_file")) { print $fh $test_data; unless (close($fh)) { die("Unable to write $test_file: $!"); } } else { die("Unable to open $test_file: $!"); } my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $timeout_idle = 10; my $xfer_log = File::Spec->rel2abs("$tmpdir/xfer.log"); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, TransferLog => $xfer_log, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); $client->type('binary'); my $conn = $client->retr_raw($test_file); unless ($conn) { die("RETR failed: " . $client->response_code() . " " . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 30); my $size = $conn->bytes_read(); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); $client->quit(); # The length of 'Hello, Proxying World!\n' is 23, so that is what # we expect here; no ASCII conversion to change things. my $expected = length($test_data); $self->assert($expected == $size, test_msg("Expected $expected, got $size")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $xfer_log")) { my $line = <$fh>; chomp($line); close($fh); my $expected = '^\S+\s+\S+\s+\d+\s+\d+:\d+:\d+\s+\d+\s+\d+\s+(\S+)\s+(\d+)\s+(\S+)\s+(\S+)\s+_\s+o\s+r\s+(\S+)\s+ftp\s+0\s+\*\s+c$'; $self->assert(qr/$expected/, $line, test_msg("Expected '$expected', got '$line'")); if ($line =~ /$expected/) { my $remote_host = $1; my $filesz = $2; my $filename = $3; my $xfer_type = $4; my $user_name = $5; $expected = '127.0.0.1'; $self->assert($expected eq $remote_host, test_msg("Expected host '$expected', got '$remote_host'")); # The length of 'Hello, Proxying World!\n' is 23, so that is what # we expect here; no ASCII conversion to change things. $expected = length($test_data); $self->assert($expected == $filesz, test_msg("Expected file size '$expected', got '$filesz'")); $expected = $test_file; $self->assert($expected eq $filename, test_msg("Expected file name '$expected', got '$filename'")); $expected = 'b'; $self->assert($expected eq $xfer_type, test_msg("Expected transfer type '$expected', got '$xfer_type'")); $expected = $user; $self->assert($expected eq $user_name, test_msg("Expected user '$expected', got '$user_name'")); } } else { die("Can't read $xfer_log: $!"); } }; if ($@) { $ex = $@; } if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_xferlog_stor_ascii_ok { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $test_data = "Hello, Proxying World!\n"; my $test_file = File::Spec->rel2abs("$tmpdir/test.txt"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $timeout_idle = 10; my $xfer_log = File::Spec->rel2abs("$tmpdir/xfer.log"); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, TransferLog => $xfer_log, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); $client->type('ascii'); my $conn = $client->stor_raw($test_file); unless ($conn) { die("STOR failed: " . $client->response_code() . " " . $client->response_msg()); } my $buf = $test_data; $conn->write($buf, length($buf), 30); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $xfer_log")) { my $line = <$fh>; chomp($line); close($fh); my $expected = '^\S+\s+\S+\s+\d+\s+\d+:\d+:\d+\s+\d+\s+\d+\s+(\S+)\s+(\d+)\s+(\S+)\s+(\S+)\s+_\s+i\s+r\s+(\S+)\s+ftp\s+0\s+\*\s+c$'; $self->assert(qr/$expected/, $line, test_msg("Expected '$expected', got '$line'")); if ($line =~ /$expected/) { my $remote_host = $1; my $filesz = $2; my $filename = $3; my $xfer_type = $4; my $user_name = $5; $expected = '127.0.0.1'; $self->assert($expected eq $remote_host, test_msg("Expected host '$expected', got '$remote_host'")); # The length of 'Hello, Proxying World!\n' is 23, but we expect 24 # here because of the ASCII conversion of the bare LF to a CRLF. $expected = length($test_data) + 1; $self->assert($expected == $filesz, test_msg("Expected file size '$expected', got '$filesz'")); $expected = $test_file; $self->assert($expected eq $filename, test_msg("Expected file name '$expected', got '$filename'")); $expected = 'a'; $self->assert($expected eq $xfer_type, test_msg("Expected transfer type '$expected', got '$xfer_type'")); $expected = $user; $self->assert($expected eq $user_name, test_msg("Expected user '$expected', got '$user_name'")); } } else { die("Can't read $xfer_log: $!"); } }; if ($@) { $ex = $@; } if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_xferlog_stor_binary_ok { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $test_data = "Hello, Proxying World!\n"; my $test_file = File::Spec->rel2abs("$tmpdir/test.txt"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $timeout_idle = 10; my $xfer_log = File::Spec->rel2abs("$tmpdir/xfer.log"); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, TransferLog => $xfer_log, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); $client->type('binary'); my $conn = $client->stor_raw($test_file); unless ($conn) { die("STOR failed: " . $client->response_code() . " " . $client->response_msg()); } my $buf = $test_data; $conn->write($buf, length($buf), 30); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $xfer_log")) { my $line = <$fh>; chomp($line); close($fh); my $expected = '^\S+\s+\S+\s+\d+\s+\d+:\d+:\d+\s+\d+\s+\d+\s+(\S+)\s+(\d+)\s+(\S+)\s+(\S+)\s+_\s+i\s+r\s+(\S+)\s+ftp\s+0\s+\*\s+c$'; $self->assert(qr/$expected/, $line, test_msg("Expected '$expected', got '$line'")); if ($line =~ /$expected/) { my $remote_host = $1; my $filesz = $2; my $filename = $3; my $xfer_type = $4; my $user_name = $5; $expected = '127.0.0.1'; $self->assert($expected eq $remote_host, test_msg("Expected host '$expected', got '$remote_host'")); # The length of 'Hello, Proxying World!\n' is 23, so that is what # we expect here; no ASCII conversion to change things. $expected = length($test_data); $self->assert($expected == $filesz, test_msg("Expected file size '$expected', got '$filesz'")); $expected = $test_file; $self->assert($expected eq $filename, test_msg("Expected file name '$expected', got '$filename'")); $expected = 'b'; $self->assert($expected eq $xfer_type, test_msg("Expected transfer type '$expected', got '$xfer_type'")); $expected = $user; $self->assert($expected eq $user_name, test_msg("Expected user '$expected', got '$user_name'")); } } else { die("Can't read $xfer_log: $!"); } }; if ($@) { $ex = $@; } if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_extlog_retr_var_F_f { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $test_data = "Hello, Proxying World!\n"; my $test_file = File::Spec->rel2abs("$tmpdir/test.txt"); if (open(my $fh, "> $test_file")) { print $fh $test_data; unless (close($fh)) { die("Unable to write $test_file: $!"); } } else { die("Unable to open $test_file: $!"); } my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $timeout_idle = 10; my $ext_log = File::Spec->rel2abs("$tmpdir/ext.log"); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, LogFormat => 'custom "%F|%f"', ExtendedLog => "$ext_log READ custom", IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); $client->type('binary'); my $conn = $client->retr_raw($test_file); unless ($conn) { die("RETR failed: " . $client->response_code() . " " . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 30); my $size = $conn->bytes_read(); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); $client->quit(); # The length of 'Hello, Proxying World!\n' is 23, so that is what # we expect here; no ASCII conversion to change things. my $expected = length($test_data); $self->assert($expected == $size, test_msg("Expected $expected, got $size")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $ext_log")) { my $line = <$fh>; chomp($line); close($fh); # Due to the fact that these tests run both the proxy server and the # backend server on the same box, it means that the proxy server's # mod_log IS in a position to get the full path for %f. But in other # cases, the proxy server won't have that file present (or it might # even be the wrong file). Thus %F/%f are to be distrusted in the # ExtendedLog entries generated by mod_proxy. } else { die("Can't read $ext_log: $!"); } }; if ($@) { $ex = $@; } if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_extlog_stor_var_F_f { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $test_data = "Hello, Proxying World!\n"; my $test_file = File::Spec->rel2abs("$tmpdir/test.txt"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $timeout_idle = 10; my $ext_log = File::Spec->rel2abs("$tmpdir/ext.log"); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, LogFormat => 'custom "%F|%f"', ExtendedLog => "$ext_log WRITE custom", IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); $client->type('binary'); my $conn = $client->stor_raw($test_file); unless ($conn) { die("STOR failed: " . $client->response_code() . " " . $client->response_msg()); } my $buf = $test_data; $conn->write($buf, length($test_data), 30); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $ext_log")) { my $line = <$fh>; chomp($line); close($fh); # Due to the fact that these tests run both the proxy server and the # backend server on the same box, it means that the proxy server's # mod_log IS in a position to get the full path for %f. But in other # cases, the proxy server won't have that file present (or it might # even be the wrong file). Thus %F/%f are to be distrusted in the # ExtendedLog entries generated by mod_proxy. } else { die("Can't read $ext_log: $!"); } }; if ($@) { $ex = $@; } if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_extlog_list_var_D_d { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $timeout_idle = 10; my $ext_log = File::Spec->rel2abs("$tmpdir/ext.log"); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, LogFormat => 'custom "%D|%d"', ExtendedLog => "$ext_log DIRS custom", IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); my $conn = $client->list_raw(); unless ($conn) { die("LIST failed: " . $client->response_code() . " " . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 30); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $ext_log")) { my $line = <$fh>; chomp($line); close($fh); # Due to the fact that these tests run both the proxy server and the # backend server on the same box, it means that the proxy server's # mod_log IS in a position to get the full path for %d. But in other # cases, the proxy server won't have that file present (or it might # even be the wrong file). Thus %D/%d are to be distrusted in the # ExtendedLog entries generated by mod_proxy. } else { die("Can't read $ext_log: $!"); } }; if ($@) { $ex = $@; } if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_proxy_protocol_v1_ipv4 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyOptions} = 'UseProxyProtocol'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:10 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy_protocol:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TransferLog none WtmpLog off ProxyProtocolEngine on EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_proxy_protocol_v1_ipv6 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyReverseServers} = "ftp://[::1]:$vhost_port"; $proxy_config->{ProxyOptions} = 'UseProxyProtocolV1'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:10 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy_protocol:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TransferLog none WtmpLog off ProxyProtocolEngine on EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_proxy_protocol_v2_ipv4 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyOptions} = 'UseProxyProtocolV2'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:10 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy_protocol:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TransferLog none WtmpLog off ProxyProtocolEngine on ProxyProtocolVersion haproxyV2 EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_proxy_protocol_v2_ipv6 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyReverseServers} = "ftp://[::1]:$vhost_port"; $proxy_config->{ProxyOptions} = 'UseProxyProtocolV2'; $proxy_config->{ProxySourceAddress} = '::1'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:10 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy_protocol:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TransferLog none WtmpLog off ProxyProtocolEngine on ProxyProtocolVersion haproxyV2 EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_proxy_protocol_v2_tlv_alpn { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyOptions} = 'UseProxyProtocolV2 UseProxyProtocolV2TLVs'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:10 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy_protocol:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', # Include the ALPN TLV in an ExtendedLog LogFormat => 'session "ALPN=%{note:mod_proxy_protocol.alpn}"', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TransferLog none WtmpLog off ProxyProtocolEngine on ProxyProtocolVersion haproxyV2 ExtendedLog $setup->{log_file} ALL session EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $setup->{log_file}")) { my $ok = 0; while (my $line = <$fh>) { chomp($line); if ($ENV{TEST_VERBOSE}) { print STDERR "# $line\n"; } if ($line =~ /^ALPN=ftp$/) { $ok = 1; last; } } close($fh); $self->assert($ok, test_msg("Did not see expected ALPN TLV log message")); } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_proxy_protocol_v2_tlv_authority { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyOptions} = 'UseProxyProtocolV2 UseProxyProtocolV2TLVs'; $proxy_config->{ProxyReverseConnectPolicy} = 'PerUser'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:10 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy_protocol:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', # Include the Authority TLV in an ExtendedLog LogFormat => 'session "AUTHORITY=%{note:mod_proxy_protocol.authority}"', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TransferLog none WtmpLog off ProxyProtocolEngine on ProxyProtocolVersion haproxyV2 ExtendedLog $setup->{log_file} ALL session EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->host('127.0.0.1'); $client->login($setup->{user}, $setup->{passwd}); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $setup->{log_file}")) { my $ok = 0; while (my $line = <$fh>) { chomp($line); if ($ENV{TEST_VERBOSE}) { print STDERR "# $line\n"; } if ($line =~ /^AUTHORITY=127\.0\.0\.1$/) { $ok = 1; last; } } close($fh); $self->assert($ok, test_msg("Did not see expected Authority TLV log message")); } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_proxy_protocol_v2_tlv_unique_id { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyOptions} = 'UseProxyProtocolV2 UseProxyProtocolV2TLVs'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:10 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy_protocol:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', # Include the Unique ID TLV in an ExtendedLog LogFormat => 'session "UNIQUE_ID=%{note:mod_proxy_protocol.unique-id}"', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TransferLog none WtmpLog off ProxyProtocolEngine on ProxyProtocolVersion haproxyV2 ExtendedLog $setup->{log_file} ALL session EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $setup->{log_file}")) { my $ok = 0; while (my $line = <$fh>) { chomp($line); if ($ENV{TEST_VERBOSE}) { print STDERR "# $line\n"; } if ($line =~ /^UNIQUE_ID=.+$/) { $ok = 1; last; } } close($fh); $self->assert($ok, test_msg("Did not see expected Unique ID TLV log message")); } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_config_reverseservers_json { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $proxy_hosts_file = File::Spec->rel2abs("$tmpdir/backends.json"); if (open(my $fh, "> $proxy_hosts_file")) { print $fh "[ \"ftp://127.0.0.1:$vhost_port\" ]\n"; unless (close($fh)) { die("Can't write $proxy_hosts_file: $!"); } } else { die("Can't open $proxy_hosts_file: $!"); } # Make sure that mod_proxy correctly handles a list of backend servers # read from a file. $proxy_config->{ProxyReverseServers} = "file:$proxy_hosts_file"; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_reverseservers_json_per_user { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $proxy_hosts_path = File::Spec->rel2abs("$tmpdir/$user.json"); if (open(my $fh, "> $proxy_hosts_path")) { print $fh "[\n \"ftp://127.0.0.1:$vhost_port\"\n]\n"; unless (close($fh)) { die("Can't write $proxy_hosts_path: $!"); } } else { die("Can't open $proxy_hosts_path: $!"); } my $proxy_hosts_file = File::Spec->rel2abs("$tmpdir/%U.json"); # Make sure that mod_proxy correctly handles a list of backend servers # read from a file. $proxy_config->{ProxyReverseServers} = "file:$proxy_hosts_file"; $proxy_config->{ProxyReverseConnectPolicy} = 'PerUser'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.reverse:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($user, $passwd); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_connect { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $client->quit(); my $expected; $expected = 220; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Forward Proxy Server'; $self->assert(qr/$expected/, $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_forward_connect_failed_timeout { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@host'; $proxy_config->{ProxyTimeoutConnect} = 1; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { my $dst_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); my $listening = IO::Socket::INET->new( LocalHost => '127.0.0.1', LocalPort => $dst_port, Proto => 'tcp', Type => SOCK_STREAM, Listen => 5, ReuseAddr => 1, ReusePort => 1, ); sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); my $start = [gettimeofday()]; eval { $client->login("$setup->{user}\@127.0.0.1:$dst_port", $setup->{passwd}) }; my $elapsed = tv_interval($start); unless ($@) { die("Login succeeded unexpectedly"); } eval { $listening->close() }; $self->assert($elapsed < 3, test_msg("ProxyTimeoutConnect 1 not honored (elapsed $elapsed)")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_forward_noproxyauth_login { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@host'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login("$setup->{user}\@127.0.0.1:$vhost_port", $setup->{passwd}); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_forward_noproxyauth_login_after_host { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $host = 'localhost'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 binding:20 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', DefaultServer => 'on', ServerName => '"Default Server"', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { my $tables_dir = File::Spec->rel2abs("$tmpdir/var/proxy"); print $fh < ProxyTables $tables_dir From 127.0.0.1 ProxyForwardEnabled on Port $port ServerAlias $host ServerName "Namebased Server" DelayEngine off ProxyEngine on ProxyLog $log_file ProxyRole forward ProxyForwardMethod user\@host Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); my ($resp_code, $resp_msg) = $client->host($host); my $expected = 220; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Namebased'; $self->assert(qr/$expected/, $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->login("$user\@127.0.0.1:$vhost_port", $passwd); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_noproxyauth_login_extra_user { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@host'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login("$setup->{user}\@127.0.0.1:$vhost_port", $setup->{passwd}); # Note that this behavior changed due to Bug#4217 $client->user($setup->{user}); my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 230; $self->assert($resp_code == $expected, test_msg("Expected response code $expected, got $resp_code")); $expected = "User $setup->{user} logged in"; $self->assert($resp_msg eq $expected, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_forward_noproxyauth_login_extra_pass { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@host'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login("$user\@127.0.0.1:$vhost_port", $passwd); eval { $client->pass($passwd) }; unless ($@) { die("Second PASS succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 503; $self->assert($resp_code == $expected, test_msg("Expected response code $expected, got $resp_code")); $expected = 'You are already logged in'; $self->assert($resp_msg eq $expected, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_noproxyauth_login_ipv6_dst_addr { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@host'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', UseIPv6 => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login("$user\@[::ffff:127.0.0.1]:$vhost_port", $passwd); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_noproxyauth_login_netftp_fw_type_1 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@host'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow server to start up sleep(1); # Set the appropriate Net::FTP environment variables for "FTP firewalls". $ENV{FTP_FIREWALL_TYPE} = 1; my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login("$setup->{user}\@127.0.0.1:$vhost_port", $setup->{passwd}); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_forward_noproxyauth_login_failed_bad_dst_addr { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@host'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 netio:20 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 2); my $bad_addr = '1.2.3.4:5678'; eval { $client->user("$setup->{user}\@$bad_addr") }; unless ($@) { die("Login succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "Unable to connect to $bad_addr: ((Connection|Operation) timed out|Transport endpoint is not connected)"; $self->assert(qr/$expected/, $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_forward_noproxyauth_login_failed_proxy_dst_addr { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@host'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); my $proxy_addr = "127.0.0.1:$port"; eval { $client->user("$setup->{user}\@$proxy_addr") }; unless ($@) { die("Login succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $client->quit(); my $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "Unable to connect to $proxy_addr: Operation not permitted"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_forward_noproxyauth_login_failed_non2xx { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@host'; my $allow_file = File::Spec->rel2abs("$tmpdir/wrap2.allow"); if (open(my $fh, "> $allow_file")) { unless (close($fh)) { die("Can't write $allow_file: $!"); } } else { die("Can't open $allow_file: $!"); } my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none WrapEngine on WrapLog $log_file WrapTables file:$allow_file builtin:all WrapOptions CheckOnConnect EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); eval { $client->login("$user\@127.0.0.1:$vhost_port", $passwd) }; unless ($@) { die("Login succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected; $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Access denied'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_noproxyauth_login_failed_login_limit { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@host'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none DenyAll EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); eval { $client->user("$setup->{user}\@127.0.0.1:$vhost_port") }; unless ($@) { die("Login succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $client->quit(); my $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Unable to connect to 127.0.0.1:\d+: Operation not permitted'; $self->assert(qr/$expected/, $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_forward_noproxyauth_login_failed_bad_sequence { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@host'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->user("$setup->{user}\@127.0.0.1:$vhost_port"); eval { $client->pwd() }; unless ($@) { die("Out-of-sequence command succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $client->quit(); my $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Please login with USER and PASS'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_forward_noproxyauth_login_failed_bad_dst_passwd { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@host'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); my $bad_passwd = "BadPassword"; eval { $client->login("$user\@127.0.0.1:$vhost_port", $bad_passwd) }; unless ($@) { die("Login succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected; $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Login incorrect.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_noproxyauth_sni_login_backend_ftp { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@sni'; $proxy_config->{ProxyTLSVerifyServer} = 'off'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 tls:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_proxy.c' => $proxy_config, 'mod_tls.c' => { TLSEngine => 'on', TLSLog => $setup->{log_file}, TLSRequired => 'on', TLSRSACertificateFile => $cert_file, TLSCACertificateFile => $ca_file, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::FTPSSL; my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(2); my $client_opts = { Encryption => 'E', Port => $port, SSL_hostname => "localhost:$vhost_port", SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE(), }; if ($ENV{TEST_VERBOSE}) { $client_opts->{Debug} = 1; } my $client = Net::FTPSSL->new('127.0.0.1', %$client_opts); unless ($client) { die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr()); } unless ($client->login($setup->{user}, $setup->{passwd})) { die("Can't login: " . $client->last_message()); } $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_forward_noproxyauth_sni_login_backend_ftps { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@sni'; $proxy_config->{ProxyTLSVerifyServer} = 'off'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 tls:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_proxy.c' => $proxy_config, 'mod_tls.c' => { TLSEngine => 'on', TLSLog => $setup->{log_file}, TLSRequired => 'on', TLSRSACertificateFile => $cert_file, TLSCACertificateFile => $ca_file, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none TLSEngine on TLSLog $setup->{log_file} TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::FTPSSL; my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(2); my $client_opts = { Encryption => 'E', Port => $port, SSL_hostname => "localhost:$vhost_port", SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE(), }; if ($ENV{TEST_VERBOSE}) { $client_opts->{Debug} = 1; } my $client = Net::FTPSSL->new('127.0.0.1', %$client_opts); unless ($client) { die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr()); } unless ($client->login($setup->{user}, $setup->{passwd})) { die("Can't login: " . $client->last_message()); } $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_forward_noproxyauth_sni_login_failed_no_tls { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@sni'; $proxy_config->{ProxyTLSVerifyServer} = 'off'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 tls:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_proxy.c' => $proxy_config, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); eval { $client->login($setup->{user}, $setup->{passwd}) }; unless ($@) { die("Login without TLS succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 530; $self->assert($resp_code == $expected, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Login incorrect.'; $self->assert($resp_msg eq $expected, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_forward_noproxyauth_sni_login_failed_no_tls_sni { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@sni'; $proxy_config->{ProxyTLSVerifyServer} = 'off'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 tls:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_proxy.c' => $proxy_config, 'mod_tls.c' => { TLSEngine => 'on', TLSLog => $setup->{log_file}, TLSRequired => 'on', TLSRSACertificateFile => $cert_file, TLSCACertificateFile => $ca_file, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::FTPSSL; my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(2); # For this test, we deliberately omit the `SSL_hostname` SNI configuration my $client_opts = { Encryption => 'E', Port => $port, SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE(), }; if ($ENV{TEST_VERBOSE}) { $client_opts->{Debug} = 1; } my $client = Net::FTPSSL->new('127.0.0.1', %$client_opts); unless ($client) { die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr()); } if ($client->login($setup->{user}, $setup->{passwd})) { die("Login without SNI succeeded unexpectedly"); } my $resp = $client->last_message(); my $expected = '530 Login incorrect.'; $self->assert($resp eq $expected, test_msg("Expected response '$expected', got '$resp'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_forward_noproxyauth_sni_login_failed_proxyforwardto_mismatch { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@sni'; $proxy_config->{ProxyTLSVerifyServer} = 'off'; # Ensure that our ProxyForwardTo regex does not allow the custom SNI port $proxy_config->{ProxyForwardTo} = '^localhost:21$'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 tls:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_proxy.c' => $proxy_config, 'mod_tls.c' => { TLSEngine => 'on', TLSLog => $setup->{log_file}, TLSRequired => 'on', TLSRSACertificateFile => $cert_file, TLSCACertificateFile => $ca_file, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::FTPSSL; my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(2); my $client_opts = { Encryption => 'E', Port => $port, SSL_hostname => "localhost:$vhost_port", SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE(), }; if ($ENV{TEST_VERBOSE}) { $client_opts->{Debug} = 1; } my $client = Net::FTPSSL->new('127.0.0.1', %$client_opts); unless ($client) { die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr()); } if ($client->login($setup->{user}, $setup->{passwd})) { die("Forward proxy SNI login succeeded unexpectedly"); } $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_forward_login_feat_first { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@host'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(3); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 5); my ($resp_code, $resp_msg) = $client->feat(); my $expected = 211; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $client->login("$user\@127.0.0.1:$vhost_port", $passwd); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_list_pasv { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@host'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login("$user\@127.0.0.1:$vhost_port", $passwd); my $conn = $client->list_raw(); unless ($conn) { die("Failed to LIST: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 10); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); ($resp_code, $resp_msg) = $client->quit(); my $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_list_port { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@host'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1); $client->login("$user\@127.0.0.1:$vhost_port", $passwd); my $conn = $client->list_raw(); unless ($conn) { die("Failed to LIST: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 10); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); ($resp_code, $resp_msg) = $client->quit(); my $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_epsv { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@host'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login("$user\@127.0.0.1:$vhost_port", $passwd); my ($resp_code, $resp_msg) = $client->epsv(); my $expected = 229; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = '^Entering Extended Passive Mode \(\|\|\|\d+\|\)'; $self->assert(qr/$expected/, $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_eprt_ipv4 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@host'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login("$setup->{user}\@127.0.0.1:$vhost_port", $setup->{passwd}); my ($resp_code, $resp_msg) = $client->eprt('|1|127.0.0.1|4856|'); my $expected = 200; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "EPRT command successful"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_forward_eprt_ipv6 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@host'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', UseIPv6 => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login("$user\@127.0.0.1:$vhost_port", $passwd); my ($resp_code, $resp_msg) = $client->eprt('|2|::ffff:127.0.0.1|4856|'); my $expected; $expected = 200; $self->assert($expected == $resp_code, test_msg("Expected $expected, got $resp_code")); $expected = "EPRT command successful"; $self->assert($expected eq $resp_msg, test_msg("Expected '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_retr_port { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@host'; my $test_data = "Hello, Proxying World!\n"; my $test_file = File::Spec->rel2abs("$tmpdir/test.txt"); if (open(my $fh, "> $test_file")) { print $fh $test_data; unless (close($fh)) { die("Can't write $test_file: $!"); } } else { die("Can't open $test_file: $!"); } my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1); $client->login("$user\@127.0.0.1:$vhost_port", $passwd); my $conn = $client->retr_raw($test_file); unless ($conn) { die("RETR failed: " . $client->response_code() . " " . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 10); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); $client->quit(); my $expected = length($test_data); my $size = -s $test_file; $self->assert($expected == $size, test_msg("Expected size $expected, got $size")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_stor_pasv { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@host'; my $test_data = "Hello, Proxying World!\n"; my $test_file = File::Spec->rel2abs("$tmpdir/test.txt"); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login("$user\@127.0.0.1:$vhost_port", $passwd); my $conn = $client->stor_raw($test_file); unless ($conn) { die("STOR failed: " . $client->response_code() . " " . $client->response_msg()); } my $buf = $test_data; $conn->write($buf, length($buf), 30); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); $client->quit(); $self->assert(-f $test_file, test_msg("File $test_file does not exist as expected")); my $expected = length($test_data); my $size = -s $test_file; $self->assert($expected == $size, test_msg("Expected size $expected, got $size")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_userwithproxyauth_login { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/vhost.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/vhost.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); # Have separate Auth files for the proxy my $proxy_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $proxy_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $proxy_user = 'proxy-user'; my $proxy_passwd = 'proxy-test'; auth_user_write($proxy_user_file, $proxy_user, $proxy_passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($proxy_group_file, $group, $gid, $proxy_user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'proxyuser,user@host'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $proxy_user_file, AuthGroupFile => $proxy_group_file, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($proxy_user, $proxy_passwd); $client->login("$user\@127.0.0.1:$vhost_port", $passwd); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_userwithproxyauth_login_extra_user { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/vhost.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/vhost.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); # Have separate Auth files for the proxy my $proxy_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $proxy_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $proxy_user = 'proxy-user'; my $proxy_passwd = 'proxy-test'; auth_user_write($proxy_user_file, $proxy_user, $proxy_passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($proxy_group_file, $group, $gid, $proxy_user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'proxyuser,user@host'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $proxy_user_file, AuthGroupFile => $proxy_group_file, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($proxy_user, $proxy_passwd); $client->login("$user\@127.0.0.1:$vhost_port", $passwd); # Due to changes for Bug#4217, a subsequent USER command -- for the # same username -- will succeed. $client->user($user); my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 230; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "User $user logged in"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_userwithproxyauth_login_extra_pass { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/vhost.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/vhost.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); # Have separate Auth files for the proxy my $proxy_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $proxy_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $proxy_user = 'proxy-user'; my $proxy_passwd = 'proxy-test'; auth_user_write($proxy_user_file, $proxy_user, $proxy_passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($proxy_group_file, $group, $gid, $proxy_user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'proxyuser,user@host'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $proxy_user_file, AuthGroupFile => $proxy_group_file, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($proxy_user, $proxy_passwd); $client->login("$user\@127.0.0.1:$vhost_port", $passwd); eval { $client->pass($passwd) }; unless ($@) { die("Extra PASS succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 503; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'You are already logged in'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_userwithproxyauth_login_user_incl_at_symbol { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/vhost.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/vhost.group"); my $user = 'proftpd@proftpd.org'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); # Have separate Auth files for the proxy my $proxy_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $proxy_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $proxy_user = 'proxy-user'; my $proxy_passwd = 'proxy-test'; auth_user_write($proxy_user_file, $proxy_user, $proxy_passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($proxy_group_file, $group, $gid, $proxy_user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'proxyuser,user@host'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $proxy_user_file, AuthGroupFile => $proxy_group_file, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($proxy_user, $proxy_passwd); $client->login("$user\@127.0.0.1:$vhost_port", $passwd); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_userwithproxyauth_login_failed_bad_dst_addr { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); # Have separate Auth files for the proxy my $proxy_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $proxy_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $proxy_user = 'proxy-user'; my $proxy_passwd = 'proxy-test'; auth_user_write($proxy_user_file, $proxy_user, $proxy_passwd, $setup->{uid}, $setup->{gid}, $setup->{home_dir}, '/bin/bash'); auth_group_write($proxy_group_file, $setup->{group}, $setup->{gid}, $proxy_user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'proxyuser,user@host'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $proxy_user_file, AuthGroupFile => $proxy_group_file, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off RootLogin on WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($proxy_user, $proxy_passwd); my $bad_addr = '1.2.3.4:5678'; eval { $client->login("$setup->{user}\@$bad_addr", $setup->{passwd}) }; unless ($@) { die("Destination login succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "Unable to connect to $bad_addr: ((Connection|Operation) timed out|Transport endpoint is not connected)"; $self->assert(qr/$expected/, $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_forward_userwithproxyauth_login_failed_non2xx { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/vhost.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/vhost.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); # Have separate Auth files for the proxy my $proxy_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $proxy_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $proxy_user = 'proxy-user'; my $proxy_passwd = 'proxy-test'; auth_user_write($proxy_user_file, $proxy_user, $proxy_passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($proxy_group_file, $group, $gid, $proxy_user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'proxyuser,user@host'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $proxy_user_file, AuthGroupFile => $proxy_group_file, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none DenyUser $user EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($proxy_user, $proxy_passwd); eval { $client->login("$user\@127.0.0.1:$vhost_port", $passwd) }; unless ($@) { die("Destination login succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected; $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Login incorrect.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_userwithproxyauth_login_failed_limit_login { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/vhost.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/vhost.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); # Have separate Auth files for the proxy my $proxy_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $proxy_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $proxy_user = 'proxy-user'; my $proxy_passwd = 'proxy-test'; auth_user_write($proxy_user_file, $proxy_user, $proxy_passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($proxy_group_file, $group, $gid, $proxy_user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'proxyuser,user@host'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $proxy_user_file, AuthGroupFile => $proxy_group_file, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none DenyAll EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($proxy_user, $proxy_passwd); eval { $client->login("$user\@127.0.0.1:$vhost_port", $passwd) }; unless ($@) { die("Destination login succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected; $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "Unable to connect to 127.0.0.1:$vhost_port: Operation not permitted"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_userwithproxyauth_login_failed_bad_sequence { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/vhost.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/vhost.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); # Have separate Auth files for the proxy my $proxy_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $proxy_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $proxy_user = 'proxy-user'; my $proxy_passwd = 'proxy-test'; auth_user_write($proxy_user_file, $proxy_user, $proxy_passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($proxy_group_file, $group, $gid, $proxy_user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'proxyuser,user@host'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 netio:20 response:20 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $proxy_user_file, AuthGroupFile => $proxy_group_file, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->user($proxy_user); eval { $client->pwd() }; unless ($@) { die("PWD after proxy USER succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected; $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Please login with USER and PASS'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); # Finish frontend authentication $client->pass($proxy_passwd); eval { $client->cwd("/") }; unless ($@) { die("CWD after proxy PASS succeeded unexpectedly"); } $resp_code = $client->response_code(); $resp_msg = $client->response_msg(); $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Access denied'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); eval { $client->list() }; unless ($@) { die("LIST after proxy PASS succeeded unexpectedly"); } $resp_code = $client->response_code(); $resp_msg = $client->response_msg(); $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Access denied'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); eval { $client->pwd() }; unless ($@) { die("PWD after proxy PASS succeeded unexpectedly"); } $resp_code = $client->response_code(); $resp_msg = $client->response_msg(); $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Access denied'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->user("$user\@127.0.0.1:$vhost_port"); eval { $client->pwd() }; unless ($@) { die("PWD after destination USER succeeded unexpectedly"); } $resp_code = $client->response_code(); $resp_msg = $client->response_msg(); $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Please login with USER and PASS'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); # Finish target authentication $client->pass($passwd); ($resp_code, $resp_msg) = $client->pwd(); $expected = 257; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_userwithproxyauth_login_failed_bad_proxy_passwd { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/vhost.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/vhost.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); # Have separate Auth files for the proxy my $proxy_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $proxy_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $proxy_user = 'proxy-user'; my $proxy_passwd = 'proxy-test'; auth_user_write($proxy_user_file, $proxy_user, $proxy_passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($proxy_group_file, $group, $gid, $proxy_user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'proxyuser,user@host'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 response:20 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $proxy_user_file, AuthGroupFile => $proxy_group_file, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); my $bad_passwd = "BadPassword"; eval { $client->login($proxy_user, $bad_passwd) }; unless ($@) { die("Proxy login succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Login incorrect.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); eval { $client->login("$user\@127.0.0.1:$vhost_port", $passwd) }; unless ($@) { die("Second login succeeded unexpectedly"); } $resp_code = $client->response_code(); $resp_msg = $client->response_msg(); $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Login incorrect.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_userwithproxyauth_login_failed_bad_dst_passwd { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/vhost.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/vhost.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); # Have separate Auth files for the proxy my $proxy_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $proxy_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $proxy_user = 'proxy-user'; my $proxy_passwd = 'proxy-test'; auth_user_write($proxy_user_file, $proxy_user, $proxy_passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($proxy_group_file, $group, $gid, $proxy_user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'proxyuser,user@host'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $proxy_user_file, AuthGroupFile => $proxy_group_file, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($proxy_user, $proxy_passwd); my $bad_passwd = "BadPassword"; eval { $client->login("$user\@127.0.0.1:$vhost_port", $bad_passwd) }; unless ($@) { die("Destination login succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Login incorrect.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_userwithproxyauth_bad_sequence_no_dst_login { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/vhost.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/vhost.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); # Have separate Auth files for the proxy my $proxy_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $proxy_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $proxy_user = 'proxy-user'; my $proxy_passwd = 'proxy-test'; auth_user_write($proxy_user_file, $proxy_user, $proxy_passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($proxy_group_file, $group, $gid, $proxy_user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'proxyuser,user@host'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $proxy_user_file, AuthGroupFile => $proxy_group_file, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($proxy_user, $proxy_passwd); my $bad_user = "BadUser"; eval { $client->login("$bad_user\@127.0.0.1:$vhost_port", $passwd) }; unless ($@) { die("Destination login succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Login incorrect.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_proxyuserwithproxyauth_login { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/vhost.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/vhost.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); # Have separate Auth files for the proxy my $proxy_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $proxy_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $proxy_user = 'proxy-user'; my $proxy_passwd = 'proxy-test'; auth_user_write($proxy_user_file, $proxy_user, $proxy_passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($proxy_group_file, $group, $gid, $proxy_user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'proxyuser@host,user'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $proxy_user_file, AuthGroupFile => $proxy_group_file, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login("$proxy_user\@127.0.0.1:$vhost_port", $proxy_passwd); $client->login($user, $passwd); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_proxyuserwithproxyauth_login_extra_user { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/vhost.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/vhost.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); # Have separate Auth files for the proxy my $proxy_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $proxy_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $proxy_user = 'proxy-user'; my $proxy_passwd = 'proxy-test'; auth_user_write($proxy_user_file, $proxy_user, $proxy_passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($proxy_group_file, $group, $gid, $proxy_user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'proxyuser@host,user'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $proxy_user_file, AuthGroupFile => $proxy_group_file, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login("$proxy_user\@127.0.0.1:$vhost_port", $proxy_passwd); $client->login($user, $passwd); # Due to changes for Bug#4217, a subsequent USER command -- for the # same username -- will succeed. $client->user($user); my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 230; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "User $user logged in"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_proxyuserwithproxyauth_login_extra_pass { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/vhost.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/vhost.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); # Have separate Auth files for the proxy my $proxy_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $proxy_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $proxy_user = 'proxy-user'; my $proxy_passwd = 'proxy-test'; auth_user_write($proxy_user_file, $proxy_user, $proxy_passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($proxy_group_file, $group, $gid, $proxy_user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'proxyuser@host,user'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $proxy_user_file, AuthGroupFile => $proxy_group_file, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login("$proxy_user\@127.0.0.1:$vhost_port", $proxy_passwd); $client->login($user, $passwd); eval { $client->pass($passwd) }; unless ($@) { die("Extra PASS succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 503; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'You are already logged in'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_proxyuserwithproxyauth_login_user_incl_at_symbol { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); # Have separate Auth files for the proxy my $proxy_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $proxy_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $proxy_user = 'proxy-user@proftpd.org'; my $proxy_passwd = 'proxy-test'; auth_user_write($proxy_user_file, $proxy_user, $proxy_passwd, $setup->{uid}, $setup->{gid}, $setup->{home_dir}, '/bin/bash'); auth_group_write($proxy_group_file, $setup->{group}, $setup->{gid}, $proxy_user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'proxyuser@host,user'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $proxy_user_file, AuthGroupFile => $proxy_group_file, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login("$proxy_user\@127.0.0.1:$vhost_port", $proxy_passwd); $client->login($setup->{user}, $setup->{passwd}); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_forward_proxyuserwithproxyauth_login_failed_bad_dst_addr { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); # Have separate Auth files for the proxy my $proxy_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $proxy_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $proxy_user = 'proxy-user'; my $proxy_passwd = 'proxy-test'; auth_user_write($proxy_user_file, $proxy_user, $proxy_passwd, $setup->{uid}, $setup->{gid}, $setup->{home_dir}, '/bin/bash'); auth_group_write($proxy_group_file, $setup->{group}, $setup->{gid}, $proxy_user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'proxyuser@host,user'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $proxy_user_file, AuthGroupFile => $proxy_group_file, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off RootLogin on WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); my $bad_addr = '1.2.3.4:5678'; $client->login("$proxy_user\@$bad_addr", $proxy_passwd); eval { $client->login($setup->{user}, $setup->{passwd}) }; unless ($@) { die("Destination login succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "Unable to connect to $bad_addr: ((Connection|Operation) timed out|Transport endpoint is not connected)"; $self->assert(qr/$expected/, $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_forward_proxyuserwithproxyauth_login_failed_non2xx { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/vhost.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/vhost.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); # Have separate Auth files for the proxy my $proxy_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $proxy_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $proxy_user = 'proxy-user'; my $proxy_passwd = 'proxy-test'; auth_user_write($proxy_user_file, $proxy_user, $proxy_passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($proxy_group_file, $group, $gid, $proxy_user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'proxyuser@host,user'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $proxy_user_file, AuthGroupFile => $proxy_group_file, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none DenyUser $user EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login("$proxy_user\@127.0.0.1:$vhost_port", $proxy_passwd); eval { $client->login($user, $passwd) }; unless ($@) { die("Destination login succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected; $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Login incorrect.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_proxyuserwithproxyauth_login_failed_limit_login { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/vhost.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/vhost.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); # Have separate Auth files for the proxy my $proxy_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $proxy_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $proxy_user = 'proxy-user'; my $proxy_passwd = 'proxy-test'; auth_user_write($proxy_user_file, $proxy_user, $proxy_passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($proxy_group_file, $group, $gid, $proxy_user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'proxyuser@host,user'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $proxy_user_file, AuthGroupFile => $proxy_group_file, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none DenyAll EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login("$proxy_user\@127.0.0.1:$vhost_port", $proxy_passwd); eval { $client->login($user, $passwd) }; unless ($@) { die("Destination login succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected; $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "Unable to connect to 127.0.0.1:$vhost_port: Operation not permitted"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_proxyuserwithproxyauth_login_failed_bad_sequence { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/vhost.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/vhost.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); # Have separate Auth files for the proxy my $proxy_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $proxy_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $proxy_user = 'proxy-user'; my $proxy_passwd = 'proxy-test'; auth_user_write($proxy_user_file, $proxy_user, $proxy_passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($proxy_group_file, $group, $gid, $proxy_user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'proxyuser@host,user'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $proxy_user_file, AuthGroupFile => $proxy_group_file, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->user("$proxy_user\@127.0.0.1:$vhost_port"); eval { $client->pwd() }; unless ($@) { die("PWD after proxy USER succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected; $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Please login with USER and PASS'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); # Finish frontend authentication $client->pass($proxy_passwd); eval { $client->cwd("/") }; unless ($@) { die("CWD after proxy PASS succeeded unexpectedly"); } $resp_code = $client->response_code(); $resp_msg = $client->response_msg(); $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Access denied'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); eval { $client->list() }; unless ($@) { die("LIST after proxy PASS succeeded unexpectedly"); } $resp_code = $client->response_code(); $resp_msg = $client->response_msg(); $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Access denied'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); eval { $client->pwd() }; unless ($@) { die("PWD after proxy PASS succeeded unexpectedly"); } $resp_code = $client->response_code(); $resp_msg = $client->response_msg(); $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Access denied'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->user($user); eval { $client->pwd() }; unless ($@) { die("PWD after destination USER succeeded unexpectedly"); } $resp_code = $client->response_code(); $resp_msg = $client->response_msg(); $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Please login with USER and PASS'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); # Finish target authentication $client->pass($passwd); ($resp_code, $resp_msg) = $client->pwd(); $expected = 257; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_proxyuserwithproxyauth_login_failed_bad_proxy_passwd { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/vhost.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/vhost.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); # Have separate Auth files for the proxy my $proxy_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $proxy_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $proxy_user = 'proxy-user'; my $proxy_passwd = 'proxy-test'; auth_user_write($proxy_user_file, $proxy_user, $proxy_passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($proxy_group_file, $group, $gid, $proxy_user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'proxyuser@host,user'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $proxy_user_file, AuthGroupFile => $proxy_group_file, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); my $bad_passwd = "BadPassword"; eval { $client->login("$proxy_user\@127.0.0.1:$vhost_port", $bad_passwd) }; unless ($@) { die("Proxy login succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Login incorrect.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); eval { $client->login($user, $passwd) }; unless ($@) { die("Second login succeeded unexpectedly"); } $resp_code = $client->response_code(); $resp_msg = $client->response_msg(); $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Login incorrect.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_proxyuserwithproxyauth_login_failed_bad_dst_passwd { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/vhost.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/vhost.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); # Have separate Auth files for the proxy my $proxy_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $proxy_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $proxy_user = 'proxy-user'; my $proxy_passwd = 'proxy-test'; auth_user_write($proxy_user_file, $proxy_user, $proxy_passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($proxy_group_file, $group, $gid, $proxy_user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'proxyuser@host,user'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $proxy_user_file, AuthGroupFile => $proxy_group_file, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login("$proxy_user\@127.0.0.1:$vhost_port", $proxy_passwd); my $bad_passwd = "BadPassword"; eval { $client->login($user, $bad_passwd) }; unless ($@) { die("Destination login succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Login incorrect.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_proxyuserwithproxyauth_bad_sequence_no_dst_login { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/vhost.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/vhost.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); # Have separate Auth files for the proxy my $proxy_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $proxy_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $proxy_user = 'proxy-user'; my $proxy_passwd = 'proxy-test'; auth_user_write($proxy_user_file, $proxy_user, $proxy_passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($proxy_group_file, $group, $gid, $proxy_user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'proxyuser@host,user'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $proxy_user_file, AuthGroupFile => $proxy_group_file, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login("$proxy_user\@127.0.0.1:$vhost_port", $proxy_passwd); my $bad_user = "BadUser"; eval { $client->login($bad_user, $passwd) }; unless ($@) { die("Destination login succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Login incorrect.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_config_forward_to { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@host'; $proxy_config->{ProxyForwardTo} = '^google.com [NC]'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); eval { $client->login("$user\@127.0.0.1:$vhost_port", $passwd) }; unless ($@) { die("Login succeeded unexpectedly"); } $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_config_forward_to_negated { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@host'; $proxy_config->{ProxyForwardTo} = "!^127\.0\.0\.1 [NC]"; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); eval { $client->login("$user\@127.0.0.1:$vhost_port", $passwd) }; unless ($@) { die("Login succeeded unexpectedly"); } $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_config_use_direct_data_transfers_port { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@host'; $proxy_config->{ProxyOptions} = 'UseDirectDataTransfers'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1, 1); $client->login("$setup->{user}\@127.0.0.1:$vhost_port", $setup->{passwd}); my $conn = $client->list_raw(); unless ($conn) { die("Failed to LIST: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 10); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_forward_config_use_direct_data_transfers_pasv { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@host'; $proxy_config->{ProxyOptions} = 'UseDirectDataTransfers'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.uri:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login("$setup->{user}\@127.0.0.1:$vhost_port", $setup->{passwd}); my $conn = $client->list_raw(); unless ($conn) { die("Failed to LIST: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 10); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } 1; proftpd-mod_proxy-0.9.5/t/lib/ProFTPD/Tests/Modules/mod_proxy/000077500000000000000000000000001475737016700242235ustar00rootroot00000000000000proftpd-mod_proxy-0.9.5/t/lib/ProFTPD/Tests/Modules/mod_proxy/ban.pm000066400000000000000000000556441475737016700253370ustar00rootroot00000000000000package ProFTPD::Tests::Modules::mod_proxy::ban; use lib qw(t/lib); use base qw(ProFTPD::TestSuite::Child); use strict; use File::Path qw(mkpath); use File::Spec; use IO::Handle; use ProFTPD::TestSuite::FTP; use ProFTPD::TestSuite::Utils qw(:auth :config :running :test :testsuite); $| = 1; my $order = 0; my $TESTS = { proxy_ban_reverse_max_login_attempts => { order => ++$order, test_class => [qw(forking mod_ban reverse)], }, proxy_ban_reverse_login_rate => { order => ++$order, test_class => [qw(forking mod_ban reverse)], }, proxy_ban_forward_noproxyauth_max_login_attempts => { order => ++$order, test_class => [qw(forking forward mod_ban)], }, proxy_ban_forward_userwithproxyauth_max_login_attempts => { order => ++$order, test_class => [qw(forking forward mod_ban)], }, proxy_ban_forward_proxyuserwithproxyauth_max_login_attempts => { order => ++$order, test_class => [qw(forking forward mod_ban)], }, # TODO: # proxy_ban_forward_login_rate }; sub new { return shift()->SUPER::new(@_); } sub list_tests { return testsuite_get_runnable_tests($TESTS); } sub get_forward_proxy_config { my $tmpdir = shift; my $log_file = shift; my $vhost_port = shift; my $table_dir = File::Spec->rel2abs("$tmpdir/var/proxy"); my $config = { ProxyEngine => 'on', ProxyLog => $log_file, ProxyRole => 'forward', ProxyTables => $table_dir, Class => { 'forward-proxy' => { From => '127.0.0.1', ProxyForwardEnabled => 'on', }, }, }; return $config; } sub get_reverse_proxy_config { my $tmpdir = shift; my $log_file = shift; my $vhost_port = shift; my $table_dir = File::Spec->rel2abs("$tmpdir/var/proxy"); my $config = { ProxyEngine => 'on', ProxyLog => $log_file, ProxyReverseServers => "ftp://127.0.0.1:$vhost_port", ProxyRole => 'reverse', ProxyTables => $table_dir, }; return $config; } sub proxy_ban_reverse_max_login_attempts { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $vhost_port2 = $vhost_port - 7; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyTimeoutConnect} = '1sec'; my $ban_tab = File::Spec->rel2abs("$tmpdir/ban.tab"); my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:10 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.reverse:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, SocketBindTight => 'on', MaxLoginAttempts => 2, IfModules => { 'mod_ban.c' => { BanEngine => 'on', BanLog => $setup->{log_file}, # This says to ban a client which exceeds the MaxLoginAttempts # limit once within the last 1 minute will be banned for 10 secs BanOnEvent => 'MaxLoginAttempts 1/00:01:00 00:00:10', BanTable => $ban_tab, }, 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_proxy.c' => $proxy_config, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow time for the server to start up sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); for (my $i = 0; $i < 2; $i++) { eval { $client->login($setup->{user}, 'foo') }; unless ($@) { die("Login succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "Login incorrect."; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); } # Now try again with the correct info; we should be banned. Note # that we have to create a separate connection for this. eval { $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, undef, 0) }; if ($@) { my $conn_ex = ProFTPD::TestSuite::FTP::get_connect_exception(); my $expected = ""; $self->assert($expected eq $conn_ex, test_msg("Expected exception '$expected', got '$conn_ex'")); } else { eval { $client->quit() }; unless ($@) { die("QUIT command succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $expected = 421; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_ban_reverse_login_rate { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $vhost_port2 = $vhost_port - 7; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyTimeoutConnect} = '1sec'; my $ban_tab = File::Spec->rel2abs("$tmpdir/ban.tab"); my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:10 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.reverse:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_ban.c' => { BanEngine => 'on', BanLog => $setup->{log_file}, # This says to ban a client which exceeds the login rate # limit once within the last 1 minute will be banned for 5 secs BanOnEvent => 'LoginRate 2/00:01:00 00:00:05', BanTable => $ban_tab, }, 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_proxy.c' => $proxy_config, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); my $client2 = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); eval { $client2->login($setup->{user}, $setup->{passwd}) }; unless ($@) { die("Second login succeeded unexpectedly"); } my $resp_code = $client2->response_code(); my $resp_msg = $client2->response_msg(); my $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "Login incorrect."; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_ban_forward_noproxyauth_max_login_attempts { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@host'; my $ban_tab = File::Spec->rel2abs("$tmpdir/ban.tab"); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', MaxLoginAttempts => 2, IfModules => { 'mod_ban.c' => { BanEngine => 'on', BanLog => $setup->{log_file}, # This says to ban a client which exceeds the MaxLoginAttempts # limit once within the last 1 minute will be banned for 5 secs BanOnEvent => 'MaxLoginAttempts 1/00:01:00 00:00:05', BanTable => $ban_tab, }, 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); for (my $i = 0; $i < 2; $i++) { eval { $client->login("$setup->{user}\@127.0.0.1:$vhost_port", 'foo') }; unless ($@) { die("Login succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "Login incorrect."; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); } # Now try again with the correct info; we should be banned. Note # that we have to create a separate connection for this. eval { $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, undef, 0) }; unless ($@) { die("Connect succeeded unexpectedly"); } my $conn_ex = ProFTPD::TestSuite::FTP::get_connect_exception(); my $expected = ''; if (length($conn_ex) > 0) { $expected = 'Connection closed'; } $self->assert(qr/$expected/, $conn_ex, test_msg("Expected exception '$expected', got '$conn_ex'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_ban_forward_userwithproxyauth_max_login_attempts { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); # Have separate Auth files for the proxy my $proxy_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $proxy_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $proxy_user = 'proxy-user'; my $proxy_passwd = 'proxy-test'; auth_user_write($proxy_user_file, $proxy_user, $proxy_passwd, $setup->{uid}, $setup->{gid}, $setup->{home_dir}, '/bin/bash'); auth_group_write($proxy_group_file, $setup->{group}, $setup->{gid}, $proxy_user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'proxyuser,user@host'; my $ban_tab = File::Spec->rel2abs("$tmpdir/ban.tab"); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $proxy_user_file, AuthGroupFile => $proxy_group_file, ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', # Whether the login attempt is to the proxy or to the real server, # the same number of login attempts is enforced; it's an overall total. MaxLoginAttempts => 2, IfModules => { 'mod_ban.c' => { BanEngine => 'on', BanLog => $setup->{log_file}, # This says to ban a client which exceeds the MaxLoginAttempts # limit once within the last 1 minute will be banned for 5 secs BanOnEvent => 'MaxLoginAttempts 1/00:01:00 00:00:05', BanTable => $ban_tab, }, 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); for (my $i = 0; $i < 2; $i++) { eval { $client->login($proxy_user, 'foo') }; unless ($@) { die("Login succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "Login incorrect."; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); } # Now try again with the correct info; we should be banned. Note # that we have to create a separate connection for this. eval { $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, undef, 0) }; unless ($@) { die("Connect succeeded unexpectedly"); } my $conn_ex = ProFTPD::TestSuite::FTP::get_connect_exception(); my $expected = ''; if (length($conn_ex) > 0) { $expected = 'Connection closed'; } $self->assert(qr/$expected/, $conn_ex, test_msg("Expected exception '$expected', got '$conn_ex'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_ban_forward_proxyuserwithproxyauth_max_login_attempts { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); # Have separate Auth files for the proxy my $proxy_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $proxy_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $proxy_user = 'proxy-user'; my $proxy_passwd = 'proxy-test'; auth_user_write($proxy_user_file, $proxy_user, $proxy_passwd, $setup->{uid}, $setup->{gid}, $setup->{home_dir}, '/bin/bash'); auth_group_write($proxy_group_file, $setup->{group}, $setup->{gid}, $proxy_user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'proxyuser@host,user'; my $ban_tab = File::Spec->rel2abs("$tmpdir/ban.tab"); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $proxy_user_file, AuthGroupFile => $proxy_group_file, ServerIdent => 'on "Forward Proxy Server"', SocketBindTight => 'on', # Whether the login attempt is to the proxy or to the real server, # the same number of login attempts is enforced; it's an overall total. MaxLoginAttempts => 2, IfModules => { 'mod_ban.c' => { BanEngine => 'on', BanLog => $setup->{log_file}, # This says to ban a client which exceeds the MaxLoginAttempts # limit once within the last 1 minute will be banned for 5 secs BanOnEvent => 'MaxLoginAttempts 1/00:01:00 00:00:05', BanTable => $ban_tab, }, 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); for (my $i = 0; $i < 2; $i++) { eval { $client->login("$proxy_user\@127.0.0.1:$vhost_port", 'foo') }; unless ($@) { die("Login succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "Login incorrect."; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); } # Now try again with the correct info; we should be banned. Note # that we have to create a separate connection for this. eval { $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, undef, 0) }; unless ($@) { die("Connect succeeded unexpectedly"); } my $conn_ex = ProFTPD::TestSuite::FTP::get_connect_exception(); my $expected = ''; if (length($conn_ex) > 0) { $expected = 'Connection closed'; } $self->assert(qr/$expected/, $conn_ex, test_msg("Expected exception '$expected', got '$conn_ex'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } 1; proftpd-mod_proxy-0.9.5/t/lib/ProFTPD/Tests/Modules/mod_proxy/redis.pm000066400000000000000000001275201475737016700256760ustar00rootroot00000000000000package ProFTPD::Tests::Modules::mod_proxy::redis; use lib qw(t/lib); use base qw(ProFTPD::TestSuite::Child); use strict; use Carp; use File::Copy; use File::Path qw(mkpath); use File::Spec; use IO::Handle; use IO::Socket::INET; use Time::HiRes qw(gettimeofday tv_interval usleep); use ProFTPD::TestSuite::FTP; use ProFTPD::TestSuite::Utils qw(:auth :config :running :test :testsuite); $| = 1; my $order = 0; my $TESTS = { proxy_reverse_config_redis_connect_policy_random => { order => ++$order, test_class => [qw(forking mod_redis reverse)], }, proxy_reverse_config_redis_connect_policy_shuffle => { order => ++$order, test_class => [qw(forking mod_redis reverse)], }, proxy_reverse_config_redis_connect_policy_roundrobin => { order => ++$order, test_class => [qw(forking mod_redis reverse)], }, # This is flaky when run in GitHub workflows, but passes when run in Docker # locally. So marking it as flaky. proxy_reverse_config_redis_connect_policy_leastconns => { order => ++$order, test_class => [qw(flaky forking mod_redis reverse)], }, proxy_reverse_config_redis_connect_policy_leastresponsetime => { order => ++$order, test_class => [qw(forking mod_redis reverse)], }, proxy_reverse_config_redis_connect_policy_per_host => { order => ++$order, test_class => [qw(forking mod_ifsession mod_redis reverse)], }, proxy_reverse_config_redis_connect_policy_per_user => { order => ++$order, test_class => [qw(forking mod_redis reverse)], }, proxy_reverse_config_redis_connect_policy_per_user_by_json => { order => ++$order, test_class => [qw(forking mod_redis reverse)], }, proxy_reverse_config_redis_connect_policy_per_group => { order => ++$order, test_class => [qw(forking mod_redis reverse)], }, proxy_reverse_config_redis_connect_policy_per_group_by_json => { order => ++$order, test_class => [qw(forking mod_redis reverse)], }, }; sub new { return shift()->SUPER::new(@_); } sub list_tests { return testsuite_get_runnable_tests($TESTS); } sub config_hash2array { my $hash = shift; my $array = []; foreach my $key (keys(%$hash)) { push(@$array, "$key $hash->{$key}\n"); } return $array; } sub get_redis_config { my $log_file = shift; my $redis_server = '127.0.0.1'; if (defined($ENV{REDIS_HOST})) { $redis_server = $ENV{REDIS_HOST}; } my $config = { RedisEngine => 'on', RedisLog => $log_file, RedisServer => "$redis_server:6379", RedisTimeouts => '2000 500', }; return $config; } sub get_reverse_proxy_config { my $tmpdir = shift; my $log_file = shift; my $vhost_port = shift; my $table_dir = File::Spec->rel2abs("$tmpdir/var/proxy"); my $config = { ProxyEngine => 'on', ProxyLog => $log_file, ProxyReverseServers => "ftp://127.0.0.1:$vhost_port", ProxyRole => 'reverse', ProxyTables => $table_dir, ProxyDatastore => 'Redis 127.0.0.1.', }; return $config; } sub ftp_list { my $self = shift; my $client = shift; my $conn = $client->list_raw(); unless ($conn) { die("Failed to LIST: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 10); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); ($resp_code, $resp_msg) = $client->quit(); my $expected = 221; $self->assert($expected == $resp_code, "Expected response code $expected, got $resp_code"); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, "Expected response message '$expected', got '$resp_msg'"); 1; } sub proxy_reverse_config_redis_connect_policy_random { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyReverseConnectPolicy} = 'Random'; # For now, we cheat and simply repeat the same vhost three times $proxy_config->{ProxyReverseServers} = "ftp://127.0.0.1:$vhost_port ftp://127.0.0.1:$vhost_port ftp://127.0.0.1:$vhost_port"; my $nbackends = 3; my $redis_config = get_redis_config($log_file); my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.reverse:20 proxy.reverse.redis:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_proxy.c' => $proxy_config, 'mod_redis.c' => $redis_config, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { for (my $i = 0; $i < $nbackends+1; $i++) { sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1, 1); $client->login($user, $passwd); ftp_list($self, $client); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_redis_connect_policy_shuffle { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyReverseConnectPolicy} = 'Shuffle'; # For now, we cheat and simply repeat the same vhost three times $proxy_config->{ProxyReverseServers} = "ftp://127.0.0.1:$vhost_port ftp://127.0.0.1:$vhost_port ftp://127.0.0.1:$vhost_port"; my $nbackends = 3; my $timeout_idle = 10; my $redis_config = get_redis_config($log_file); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.reverse:20 proxy.reverse.redis:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_proxy.c' => $proxy_config, 'mod_redis.c' => $redis_config, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { for (my $i = 0; $i < $nbackends+1; $i++) { sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1, 3); $client->login($user, $passwd); ftp_list($self, $client); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_redis_connect_policy_roundrobin { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyReverseConnectPolicy} = 'RoundRobin'; # For now, we cheat and simply repeat the same vhost three times $proxy_config->{ProxyReverseServers} = "ftp://127.0.0.1:$vhost_port ftp://127.0.0.1:$vhost_port ftp://127.0.0.1:$vhost_port"; my $nbackends = 3; my $timeout_idle = 10; my $redis_config = get_redis_config($log_file); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.reverse:20 proxy.reverse.redis:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_proxy.c' => $proxy_config, 'mod_redis.c' => $redis_config, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { for (my $i = 0; $i < $nbackends+1; $i++) { sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1, 1); $client->login($user, $passwd); ftp_list($self, $client); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_redis_connect_policy_leastconns { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyReverseConnectPolicy} = 'LeastConns'; # For now, we cheat and simply repeat the same vhost three times $proxy_config->{ProxyReverseServers} = "ftp://127.0.0.1:$vhost_port ftp://127.0.0.1:$vhost_port ftp://127.0.0.1:$vhost_port"; my $nbackends = 3; my $timeout_idle = 10; my $redis_config = get_redis_config($setup->{log_file}); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.reverse:20 proxy.reverse.redis:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_proxy.c' => $proxy_config, 'mod_redis.c' => $redis_config, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off RootLogin on TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow server to start up sleep(2); for (my $i = 0; $i < $nbackends+1; $i++) { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1, 2); $client->login($setup->{user}, $setup->{passwd}); ftp_list($self, $client); sleep(1); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_config_redis_connect_policy_leastresponsetime { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyReverseConnectPolicy} = 'LeastResponseTime'; # For now, we cheat and simply repeat the same vhost three times $proxy_config->{ProxyReverseServers} = "ftp://127.0.0.1:$vhost_port ftp://127.0.0.1:$vhost_port ftp://127.0.0.1:$vhost_port"; my $nbackends = 3; my $timeout_idle = 10; my $redis_config = get_redis_config($log_file); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.reverse:20 proxy.reverse.redis:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 redis:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_proxy.c' => $proxy_config, 'mod_redis.c' => $redis_config, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { for (my $i = 0; $i < $nbackends+1; $i++) { sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($user, $passwd); ftp_list($self, $client); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_redis_connect_policy_per_host { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $vhost_port2 = $vhost_port - 7; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyTimeoutConnect} = '1sec'; $proxy_config->{ProxyReverseConnectPolicy} = 'PerHost'; $proxy_config->{ProxyReverseServers} = "ftp://127.0.0.1:$vhost_port ftp://127.0.0.1:$vhost_port2"; my $nbackends = 2; my $timeout_idle = 10; my $redis_config = get_redis_config($log_file); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.reverse:20 proxy.reverse.redis:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_proxy.c' => $proxy_config, 'mod_redis.c' => $redis_config, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off Port $vhost_port2 ServerName "Other Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { for (my $i = 0; $i < $nbackends+1; $i++) { sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 0); $client->login($user, $passwd); ftp_list($self, $client); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_redis_connect_policy_per_user { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $vhost_port2 = $vhost_port - 7; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyTimeoutConnect} = '1sec'; $proxy_config->{ProxyReverseConnectPolicy} = 'PerUser'; $proxy_config->{ProxyReverseServers} = "ftp://127.0.0.1:$vhost_port ftp://127.0.0.1:$vhost_port2"; my $nbackends = 2; my $timeout_idle = 10; my $redis_config = get_redis_config($log_file); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.reverse:20 proxy.reverse.redis:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_proxy.c' => $proxy_config, 'mod_redis.c' => $redis_config, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off Port $vhost_port2 ServerName "Other Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { for (my $i = 0; $i < $nbackends+1; $i++) { sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 0); $client->login($user, $passwd); ftp_list($self, $client); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_redis_connect_policy_per_user_by_json { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $vhost_port2 = $vhost_port - 7; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyTimeoutConnect} = '1sec'; $proxy_config->{ProxyReverseConnectPolicy} = 'PerUser'; $proxy_config->{ProxyReverseServers} = "ftp://127.0.0.1:$vhost_port"; my $user_path = File::Spec->rel2abs("$tmpdir/$user-servers.json"); if (open(my $fh, "> $user_path")) { print $fh "[ \"ftp://127.0.0.1:$vhost_port2\" ]\n"; unless (close($fh)) { die("Can't write $user_path: $!"); } } else { die("Can't open $user_path: $!"); } my $uservar_path = File::Spec->rel2abs("$tmpdir/%U-servers.json"); # Since we need multiple ProxyReverseServers directives, convert this # hashref into an arrayref. $proxy_config = config_hash2array($proxy_config); push(@$proxy_config, "ProxyReverseServers file:$uservar_path"); my $nbackends = 1; my $timeout_idle = 10; my $redis_config = get_redis_config($log_file); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.reverse:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_proxy.c' => $proxy_config, 'mod_redis.c' => $redis_config, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off Port $vhost_port2 ServerName "Other Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { for (my $i = 0; $i < $nbackends+1; $i++) { sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 0); $client->login($user, $passwd); ftp_list($self, $client); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_redis_connect_policy_per_group { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $vhost_port2 = $vhost_port - 7; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyTimeoutConnect} = '1sec'; $proxy_config->{ProxyReverseConnectPolicy} = 'PerGroup'; $proxy_config->{ProxyOptions} = 'UseReverseProxyAuth'; $proxy_config->{ProxyReverseServers} = "ftp://127.0.0.1:$vhost_port ftp://127.0.0.1:$vhost_port2"; my $nbackends = 2; my $timeout_idle = 10; my $redis_config = get_redis_config($log_file); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.reverse:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_proxy.c' => $proxy_config, 'mod_redis.c' => $redis_config, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off Port $vhost_port2 ServerName "Other Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { for (my $i = 0; $i < $nbackends+1; $i++) { sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 0); $client->login($user, $passwd); ftp_list($self, $client); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_redis_connect_policy_per_group_by_json { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $vhost_port2 = $vhost_port - 7; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyTimeoutConnect} = '1sec'; $proxy_config->{ProxyReverseConnectPolicy} = 'PerGroup'; $proxy_config->{ProxyOptions} = 'UseReverseProxyAuth'; $proxy_config->{ProxyReverseServers} = "ftp://127.0.0.1:$vhost_port"; my $group_path = File::Spec->rel2abs("$tmpdir/$group-servers.json"); if (open(my $fh, "> $group_path")) { print $fh "[ \"ftp://127.0.0.1:$vhost_port2\" ]\n"; unless (close($fh)) { die("Can't write $group_path: $!"); } } else { die("Can't open $group_path: $!"); } my $groupvar_path = File::Spec->rel2abs("$tmpdir/%g-servers.json"); # Since we need multiple ProxyReverseServers directives, convert this # hashref into an arrayref. $proxy_config = config_hash2array($proxy_config); push(@$proxy_config, "ProxyReverseServers file:$groupvar_path"); my $nbackends = 1; my $timeout_idle = 10; my $redis_config = get_redis_config($log_file); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.reverse:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_proxy.c' => $proxy_config, 'mod_redis.c' => $redis_config, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off Port $vhost_port2 ServerName "Other Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { for (my $i = 0; $i < $nbackends+1; $i++) { sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 0); $client->login($user, $passwd); ftp_list($self, $client); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } 1; proftpd-mod_proxy-0.9.5/t/lib/ProFTPD/Tests/Modules/mod_proxy/reverse/000077500000000000000000000000001475737016700256765ustar00rootroot00000000000000proftpd-mod_proxy-0.9.5/t/lib/ProFTPD/Tests/Modules/mod_proxy/reverse/ipv6.pm000066400000000000000000002043671475737016700271340ustar00rootroot00000000000000package ProFTPD::Tests::Modules::mod_proxy::reverse::ipv6; use lib qw(t/lib); use base qw(ProFTPD::TestSuite::Child); use strict; use Carp; use File::Copy; use File::Path qw(mkpath); use File::Spec; use IO::Handle; use IO::Socket::INET; use IO::Socket::INET6; use Net::Address::IP::Local; use ProFTPD::TestSuite::FTP; use ProFTPD::TestSuite::Utils qw(:auth :config :running :test :testsuite); $| = 1; my $order = 0; my $TESTS = { proxy_reverse_ipv6_list_pasv => { order => ++$order, test_class => [qw(feature_ipv6 forking reverse)], }, proxy_reverse_ipv6_list_port => { order => ++$order, test_class => [qw(feature_ipv6 forking reverse)], }, proxy_reverse_ipv6_epsv => { order => ++$order, test_class => [qw(feature_ipv6 forking reverse)], }, proxy_reverse_ipv6_eprt_ipv4 => { order => ++$order, test_class => [qw(feature_ipv6 forking reverse)], }, proxy_reverse_ipv6_eprt_ipv6 => { order => ++$order, test_class => [qw(feature_ipv6 forking reverse)], }, proxy_reverse_ipv4mappedipv6_eprt_ipv4_backend_issue158 => { order => ++$order, test_class => [qw(bug feature_ipv6 forking reverse)], }, proxy_reverse_ipv4mappedipv6_epsv_ipv4_backend_issue158 => { order => ++$order, test_class => [qw(bug feature_ipv6 forking reverse)], }, proxy_reverse_ipv4mappedipv6_active_list_ipv4_backend_issue158 => { order => ++$order, test_class => [qw(bug feature_ipv6 forking reverse)], }, proxy_reverse_ipv4mappedipv6_port_list_ipv4_backend_issue158 => { order => ++$order, test_class => [qw(bug feature_ipv6 forking reverse)], }, proxy_reverse_ipv6only_eprt_ipv4_backend_issue158 => { order => ++$order, test_class => [qw(bug feature_ipv6 forking reverse)], }, proxy_reverse_ipv6only_epsv_ipv4_backend_issue158 => { order => ++$order, test_class => [qw(bug feature_ipv6 forking reverse)], }, proxy_reverse_ipv6only_active_list_ipv4_backend_issue158 => { order => ++$order, test_class => [qw(bug feature_ipv6 forking reverse)], }, proxy_reverse_ipv6only_port_list_ipv4_backend_issue158 => { order => ++$order, test_class => [qw(bug feature_ipv6 forking reverse)], }, proxy_reverse_ipv4_active_list_ipv6only_backend_issue158 => { order => ++$order, test_class => [qw(bug feature_ipv6 forking reverse)], }, proxy_reverse_ipv4_port_list_ipv6only_backend_issue158 => { order => ++$order, test_class => [qw(bug feature_ipv6 forking reverse)], }, proxy_reverse_ipv4_passive_list_ipv6only_backend_issue158 => { order => ++$order, test_class => [qw(bug feature_ipv6 forking reverse)], }, }; sub new { return shift()->SUPER::new(@_); } sub list_tests { return testsuite_get_runnable_tests($TESTS); } sub get_reverse_proxy_config_ipv4 { my $tmpdir = shift; my $log_file = shift; my $vhost_port = shift; my $table_dir = File::Spec->rel2abs("$tmpdir/var/proxy"); my $config = { ProxyEngine => 'on', ProxyLog => $log_file, ProxyReverseServers => "ftp://127.0.0.1:$vhost_port", ProxyRole => 'reverse', ProxyTables => $table_dir, }; return $config; } sub get_reverse_proxy_config_ipv6 { my $tmpdir = shift; my $log_file = shift; my $vhost_port = shift; my $table_dir = File::Spec->rel2abs("$tmpdir/var/proxy"); my $config = { ProxyEngine => 'on', ProxyLog => $log_file, ProxyReverseServers => "ftp://\[::1\]:$vhost_port", ProxyRole => 'reverse', ProxyTables => $table_dir, ProxySourceAddress => '::1', }; return $config; } sub proxy_reverse_ipv6_list_pasv { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config_ipv6($tmpdir, $log_file, $vhost_port); my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.xfer:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', TimeoutIdle => $timeout_idle, UseIPv6 => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($user, $passwd); my $conn = $client->list_raw(); unless ($conn) { die("Failed to LIST: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 10); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); ($resp_code, $resp_msg) = $client->quit(); my $expected = 221; $self->assert($expected == $resp_code, "Expected response code $expected, got $resp_code"); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, "Expected response message '$expected', got '$resp_msg'"); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_ipv6_list_port { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config_ipv6($tmpdir, $log_file, $vhost_port); my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.xfer:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', TimeoutIdle => $timeout_idle, UseIPv6 => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1, 1); $client->login($user, $passwd); my $conn = $client->list_raw(); unless ($conn) { die("Failed to LIST: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 10); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); ($resp_code, $resp_msg) = $client->quit(); my $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_ipv6_epsv { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config_ipv6($tmpdir, $log_file, $vhost_port); my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.xfer:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', TimeoutIdle => $timeout_idle, UseIPv6 => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($user, $passwd); my ($resp_code, $resp_msg) = $client->epsv(); my $expected = 229; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = '^Entering Extended Passive Mode \(\|\|\|\d+\|\)'; $self->assert(qr/$expected/, $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_ipv6_eprt_ipv4 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config_ipv6($tmpdir, $log_file, $vhost_port); my $timeout_idle = 10; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.xfer:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', TimeoutIdle => $timeout_idle, UseIPv6 => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($user, $passwd); my ($resp_code, $resp_msg) = $client->eprt('|1|127.0.0.1|4856|'); my $expected; $expected = 200; $self->assert($expected == $resp_code, test_msg("Expected $expected, got $resp_code")); $expected = "EPRT command successful"; $self->assert($expected eq $resp_msg, test_msg("Expected '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_ipv6_eprt_ipv6 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config_ipv6($tmpdir, $setup->{log_file}, $vhost_port); my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.xfer:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, UseIPv6 => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); $client->login($setup->{user}, $setup->{passwd}); my ($resp_code, $resp_msg) = $client->eprt('|2|::ffff:127.0.0.1|4856|'); my $expected = 200; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "EPRT command successful"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_ipv4mappedipv6_eprt_ipv4_backend_issue158 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config_ipv4($tmpdir, $setup->{log_file}, $vhost_port); my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:30 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.xfer:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', DefaultAddress => '::ffff:127.0.0.1', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, UseIPv6 => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('::ffff:127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); my ($resp_code, $resp_msg) = $client->eprt('|2|::ffff:127.0.0.1|4856|'); my $expected = 200; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "EPRT command successful"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); if (defined($ex)) { test_cleanup($setup->{log_file}, $ex); die($ex); } eval { if (open(my $fh, "< $setup->{log_file}")) { my $ok = 0; while (my $line = <$fh>) { chomp($line); if ($ENV{TEST_VERBOSE}) { print STDERR "# $line\n"; } if ($line =~ /proxied command 'EPRT \|1\|127\.0\.0\.1\|/) { $ok = 1; last; } } close($fh); $self->assert($ok, test_msg("Did not see expected backend log message")); } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_ipv4mappedipv6_epsv_ipv4_backend_issue158 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config_ipv4($tmpdir, $setup->{log_file}, $vhost_port); my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:30 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.xfer:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', DefaultAddress => '::ffff:127.0.0.1', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, UseIPv6 => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('::ffff:127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); my ($resp_code, $resp_msg) = $client->epsv(); my $expected = 229; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Entering Extended Passive Mode'; $self->assert(qr/$expected/, $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); if (defined($ex)) { test_cleanup($setup->{log_file}, $ex); die($ex); } eval { if (open(my $fh, "< $setup->{log_file}")) { my $ok = 0; while (my $line = <$fh>) { chomp($line); if ($ENV{TEST_VERBOSE}) { print STDERR "# $line\n"; } if ($line =~ /proxied EPSV command/) { $ok = 1; last; } } close($fh); $self->assert($ok, test_msg("Did not see expected proxy TraceLog message")); } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_ipv4mappedipv6_active_list_ipv4_backend_issue158 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config_ipv4($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyDataTransferPolicy} = 'active'; my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 directory:0 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:30 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.xfer:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', DefaultAddress => '::ffff:127.0.0.1', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, UseIPv6 => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); # We'll use passive transfers from the client to the frontend, but # use ProxyDataTransferPolicy to force an active transfer between # proxy and backend. my $client = ProFTPD::TestSuite::FTP->new('::ffff:127.0.0.1', $port, 0); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->list_raw(); unless ($conn) { die("LIST failed: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 25); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $client->quit(); $self->assert_transfer_ok($resp_code, $resp_msg); # We have to be careful of the fact that readdir returns directory # entries in an unordered fashion. my $res = {}; my $lines = [split(/\n/, $buf)]; foreach my $line (@$lines) { if ($line =~ /^\S+\s+\d+\s+\S+\s+\S+\s+.*?\s+(\S+)$/) { $res->{$1} = 1; } } my $expected = { 'proxy.conf' => 1, 'proxy.group' => 1, 'proxy.passwd' => 1, 'proxy.pid' => 1, 'proxy.scoreboard' => 1, 'proxy.scoreboard.lck' => 1, 'var' => 1, }; my $ok = 1; my $mismatch; foreach my $name (keys(%$res)) { unless (defined($expected->{$name})) { $mismatch = $name; $ok = 0; last; } } unless ($ok) { die("Unexpected name '$mismatch' appeared in LIST data") } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_ipv4mappedipv6_port_list_ipv4_backend_issue158 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config_ipv4($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyDataTransferPolicy} = 'port'; my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 directory:0 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:30 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.xfer:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', DefaultAddress => '::ffff:127.0.0.1', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, UseIPv6 => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); # We'll use passive transfers from the client to the frontend, but # use ProxyDataTransferPolicy to force an active transfer between # proxy and backend. my $client = ProFTPD::TestSuite::FTP->new('::ffff:127.0.0.1', $port, 0); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->list_raw(); unless ($conn) { die("LIST failed: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 25); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $client->quit(); $self->assert_transfer_ok($resp_code, $resp_msg); # We have to be careful of the fact that readdir returns directory # entries in an unordered fashion. my $res = {}; my $lines = [split(/\n/, $buf)]; foreach my $line (@$lines) { if ($line =~ /^\S+\s+\d+\s+\S+\s+\S+\s+.*?\s+(\S+)$/) { $res->{$1} = 1; } } my $expected = { 'proxy.conf' => 1, 'proxy.group' => 1, 'proxy.passwd' => 1, 'proxy.pid' => 1, 'proxy.scoreboard' => 1, 'proxy.scoreboard.lck' => 1, 'var' => 1, }; my $ok = 1; my $mismatch; foreach my $name (keys(%$res)) { unless (defined($expected->{$name})) { $mismatch = $name; $ok = 0; last; } } unless ($ok) { die("Unexpected name '$mismatch' appeared in LIST data") } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_ipv6only_eprt_ipv4_backend_issue158 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config_ipv4($tmpdir, $setup->{log_file}, $vhost_port); # Without this IPv4 ProxySourceAddress, the backend connections (ctrl/data) # fail. $proxy_config->{ProxySourceAddress} = '127.0.0.1'; my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:30 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.xfer:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', DefaultAddress => '::1', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, UseIPv6 => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('::1', $port); $client->login($setup->{user}, $setup->{passwd}); my ($resp_code, $resp_msg) = $client->eprt('|2|::1|4856|'); my $expected = 200; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "EPRT command successful"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); if (defined($ex)) { test_cleanup($setup->{log_file}, $ex); die($ex); } eval { if (open(my $fh, "< $setup->{log_file}")) { my $ok = 0; while (my $line = <$fh>) { chomp($line); if ($ENV{TEST_VERBOSE}) { print STDERR "# $line\n"; } if ($line =~ /proxied command 'EPRT \|1\|127\.0\.0\.1\|/) { $ok = 1; last; } } close($fh); $self->assert($ok, test_msg("Did not see expected backend log message")); } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_ipv6only_epsv_ipv4_backend_issue158 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config_ipv4($tmpdir, $setup->{log_file}, $vhost_port); # Without this IPv4 ProxySourceAddress, the backend connections (ctrl/data) # fail. $proxy_config->{ProxySourceAddress} = '127.0.0.1'; my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:30 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.xfer:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', DefaultAddress => '::1', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, UseIPv6 => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('::1', $port); $client->login($setup->{user}, $setup->{passwd}); my ($resp_code, $resp_msg) = $client->epsv(); my $expected = 229; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Entering Extended Passive Mode'; $self->assert(qr/$expected/, $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); if (defined($ex)) { test_cleanup($setup->{log_file}, $ex); die($ex); } eval { if (open(my $fh, "< $setup->{log_file}")) { my $ok = 0; while (my $line = <$fh>) { chomp($line); if ($ENV{TEST_VERBOSE}) { print STDERR "# $line\n"; } if ($line =~ /proxied EPSV command/) { $ok = 1; last; } } close($fh); $self->assert($ok, test_msg("Did not see expected proxy TraceLog message")); } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_ipv6only_active_list_ipv4_backend_issue158 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config_ipv4($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyDataTransferPolicy} = 'active'; # Without this IPv4 ProxySourceAddress, the backend connections (ctrl/data) # fail. $proxy_config->{ProxySourceAddress} = '127.0.0.1'; my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 directory:0 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:30 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.xfer:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', DefaultAddress => '::1', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, UseIPv6 => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); # We'll use passive transfers from the client to the frontend, but # use ProxyDataTransferPolicy to force an active transfer between # proxy and backend. my $client = ProFTPD::TestSuite::FTP->new('::1', $port, 0); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->list_raw(); unless ($conn) { die("LIST failed: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 25); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $client->quit(); $self->assert_transfer_ok($resp_code, $resp_msg); # We have to be careful of the fact that readdir returns directory # entries in an unordered fashion. my $res = {}; my $lines = [split(/\n/, $buf)]; foreach my $line (@$lines) { if ($line =~ /^\S+\s+\d+\s+\S+\s+\S+\s+.*?\s+(\S+)$/) { $res->{$1} = 1; } } my $expected = { 'proxy.conf' => 1, 'proxy.group' => 1, 'proxy.passwd' => 1, 'proxy.pid' => 1, 'proxy.scoreboard' => 1, 'proxy.scoreboard.lck' => 1, 'var' => 1, }; my $ok = 1; my $mismatch; foreach my $name (keys(%$res)) { unless (defined($expected->{$name})) { $mismatch = $name; $ok = 0; last; } } unless ($ok) { die("Unexpected name '$mismatch' appeared in LIST data") } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_ipv6only_port_list_ipv4_backend_issue158 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config_ipv4($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyDataTransferPolicy} = 'port'; # Without this IPv4 ProxySourceAddress, the backend connections (ctrl/data) # fail. $proxy_config->{ProxySourceAddress} = '127.0.0.1'; my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 directory:0 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:30 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.xfer:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', DefaultAddress => '::1', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, UseIPv6 => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); # We'll use passive transfers from the client to the frontend, but # use ProxyDataTransferPolicy to force an active transfer between # proxy and backend. my $client = ProFTPD::TestSuite::FTP->new('::1', $port, 0); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->list_raw(); unless ($conn) { die("LIST failed: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 25); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $client->quit(); $self->assert_transfer_ok($resp_code, $resp_msg); # We have to be careful of the fact that readdir returns directory # entries in an unordered fashion. my $res = {}; my $lines = [split(/\n/, $buf)]; foreach my $line (@$lines) { if ($line =~ /^\S+\s+\d+\s+\S+\s+\S+\s+.*?\s+(\S+)$/) { $res->{$1} = 1; } } my $expected = { 'proxy.conf' => 1, 'proxy.group' => 1, 'proxy.passwd' => 1, 'proxy.pid' => 1, 'proxy.scoreboard' => 1, 'proxy.scoreboard.lck' => 1, 'var' => 1, }; my $ok = 1; my $mismatch; foreach my $name (keys(%$res)) { unless (defined($expected->{$name})) { $mismatch = $name; $ok = 0; last; } } unless ($ok) { die("Unexpected name '$mismatch' appeared in LIST data") } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_ipv4_active_list_ipv6only_backend_issue158 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config_ipv6($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyDataTransferPolicy} = 'active'; my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 directory:0 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:30 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.xfer:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', DefaultAddress => '127.0.0.1', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, UseIPv6 => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); # We'll use passive transfers from the client to the frontend, but # use ProxyDataTransferPolicy to force an active transfer between # proxy and backend. my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->list_raw(); unless ($conn) { die("LIST failed: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 25); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $client->quit(); $self->assert_transfer_ok($resp_code, $resp_msg); # We have to be careful of the fact that readdir returns directory # entries in an unordered fashion. my $res = {}; my $lines = [split(/\n/, $buf)]; foreach my $line (@$lines) { if ($line =~ /^\S+\s+\d+\s+\S+\s+\S+\s+.*?\s+(\S+)$/) { $res->{$1} = 1; } } my $expected = { 'proxy.conf' => 1, 'proxy.group' => 1, 'proxy.passwd' => 1, 'proxy.pid' => 1, 'proxy.scoreboard' => 1, 'proxy.scoreboard.lck' => 1, 'var' => 1, }; my $ok = 1; my $mismatch; foreach my $name (keys(%$res)) { unless (defined($expected->{$name})) { $mismatch = $name; $ok = 0; last; } } unless ($ok) { die("Unexpected name '$mismatch' appeared in LIST data") } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_ipv4_port_list_ipv6only_backend_issue158 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config_ipv6($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyDataTransferPolicy} = 'port'; my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 directory:0 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:30 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.xfer:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', DefaultAddress => '127.0.0.1', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, UseIPv6 => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); # We'll use passive transfers from the client to the frontend, but # use ProxyDataTransferPolicy to force an active transfer between # proxy and backend. my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->list_raw(); unless ($conn) { die("LIST failed: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 25); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $client->quit(); $self->assert_transfer_ok($resp_code, $resp_msg); # We have to be careful of the fact that readdir returns directory # entries in an unordered fashion. my $res = {}; my $lines = [split(/\n/, $buf)]; foreach my $line (@$lines) { if ($line =~ /^\S+\s+\d+\s+\S+\s+\S+\s+.*?\s+(\S+)$/) { $res->{$1} = 1; } } my $expected = { 'proxy.conf' => 1, 'proxy.group' => 1, 'proxy.passwd' => 1, 'proxy.pid' => 1, 'proxy.scoreboard' => 1, 'proxy.scoreboard.lck' => 1, 'var' => 1, }; my $ok = 1; my $mismatch; foreach my $name (keys(%$res)) { unless (defined($expected->{$name})) { $mismatch = $name; $ok = 0; last; } } unless ($ok) { die("Unexpected name '$mismatch' appeared in LIST data") } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_ipv4_passive_list_ipv6only_backend_issue158 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config_ipv6($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyDataTransferPolicy} = 'passive'; my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 directory:0 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:30 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.xfer:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', DefaultAddress => '127.0.0.1', SocketBindTight => 'on', TimeoutIdle => $timeout_idle, UseIPv6 => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); # We'll use active transfers from the client to the frontend, but # use ProxyDataTransferPolicy to force a passive transfer between # proxy and backend. my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->list_raw(); unless ($conn) { die("LIST failed: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 25); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $client->quit(); $self->assert_transfer_ok($resp_code, $resp_msg); # We have to be careful of the fact that readdir returns directory # entries in an unordered fashion. my $res = {}; my $lines = [split(/\n/, $buf)]; foreach my $line (@$lines) { if ($line =~ /^\S+\s+\d+\s+\S+\s+\S+\s+.*?\s+(\S+)$/) { $res->{$1} = 1; } } my $expected = { 'proxy.conf' => 1, 'proxy.group' => 1, 'proxy.passwd' => 1, 'proxy.pid' => 1, 'proxy.scoreboard' => 1, 'proxy.scoreboard.lck' => 1, 'var' => 1, }; my $ok = 1; my $mismatch; foreach my $name (keys(%$res)) { unless (defined($expected->{$name})) { $mismatch = $name; $ok = 0; last; } } unless ($ok) { die("Unexpected name '$mismatch' appeared in LIST data") } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } 1; proftpd-mod_proxy-0.9.5/t/lib/ProFTPD/Tests/Modules/mod_proxy/sql.pm000066400000000000000000001564051475737016700253730ustar00rootroot00000000000000package ProFTPD::Tests::Modules::mod_proxy::sql; use lib qw(t/lib); use base qw(ProFTPD::TestSuite::Child); use strict; use File::Path qw(mkpath); use File::Spec; use IO::Handle; use ProFTPD::TestSuite::FTP; use ProFTPD::TestSuite::Utils qw(:auth :config :running :test :testsuite); $| = 1; my $order = 0; my $TESTS = { proxy_sql_reverse_config_connect_policy_per_user_by_sql => { order => ++$order, test_class => [qw(forking mod_sql_sqlite reverse)], }, proxy_sql_reverse_config_connect_policy_per_user_by_sql_bad_url => { order => ++$order, test_class => [qw(forking mod_sql_sqlite reverse)], }, proxy_sql_reverse_config_connect_policy_per_user_by_sql_username_override => { order => ++$order, test_class => [qw(forking mod_sql_sqlite reverse)], }, proxy_sql_reverse_config_connect_policy_per_user_by_sql_use_reverse_proxy_auth => { order => ++$order, test_class => [qw(forking mod_sql_sqlite reverse)], }, proxy_sql_reverse_config_redis_connect_policy_per_user_by_sql => { order => ++$order, test_class => [qw(forking mod_redis mod_sql_sqlite reverse)], }, proxy_sql_reverse_config_connect_policy_per_group_by_sql => { order => ++$order, test_class => [qw(forking mod_sql_sqlite reverse)], }, proxy_sql_sqllog_forward_proxied_address_note_issue175 => { order => ++$order, test_class => [qw(forking forward mod_sql_sqlite)], }, proxy_sql_sqllog_forward_proxied_file_xfer_issue175 => { order => ++$order, test_class => [qw(forking forward mod_sql_sqlite)], }, proxy_sql_sqllog_reverse_proxied_address_note_issue175 => { order => ++$order, test_class => [qw(forking mod_sql_sqlite reverse)], }, proxy_sql_sqllog_reverse_proxied_file_xfer_issue175 => { order => ++$order, test_class => [qw(forking mod_sql_sqlite reverse)], }, }; sub new { return shift()->SUPER::new(@_); } sub list_tests { return testsuite_get_runnable_tests($TESTS); } sub build_db { my $cmd = shift; my $db_script = shift; my $check_exit_status = shift; $check_exit_status = 0 unless defined $check_exit_status; if ($ENV{TEST_VERBOSE}) { print STDERR "Executing sqlite3: $cmd\n"; } my @output = `$cmd`; my $exit_status = $?; if ($ENV{TEST_VERBOSE}) { print STDERR "Output: ", join('', @output), "\n"; } if ($check_exit_status) { if ($? != 0) { croak("'$cmd' failed"); } } unlink($db_script); return 1; } sub config_hash2array { my $hash = shift; my $array = []; foreach my $key (keys(%$hash)) { push(@$array, "$key $hash->{$key}\n"); } return $array; } sub get_redis_config { my $log_file = shift; my $redis_server = '127.0.0.1'; if (defined($ENV{REDIS_HOST})) { $redis_server = $ENV{REDIS_HOST}; } my $config = { RedisEngine => 'on', RedisLog => $log_file, RedisServer => "$redis_server:6379", RedisTimeouts => '2000 500', }; return $config; } sub get_forward_proxy_config { my $tmpdir = shift; my $log_file = shift; my $vhost_port = shift; my $table_dir = File::Spec->rel2abs("$tmpdir/var/proxy"); my $config = { ProxyEngine => 'on', ProxyLog => $log_file, ProxyRole => 'forward', ProxyTables => $table_dir, ProxyTimeoutConnect => '1sec', Class => { 'forward-proxy' => { From => '127.0.0.1', ProxyForwardEnabled => 'on', }, }, }; return $config; } sub get_reverse_proxy_config { my $tmpdir = shift; my $log_file = shift; my $vhost_port = shift; my $table_dir = File::Spec->rel2abs("$tmpdir/var/proxy"); my $config = { ProxyEngine => 'on', ProxyLog => $log_file, ProxyReverseServers => "ftp://127.0.0.1:$vhost_port", ProxyRole => 'reverse', ProxyTables => $table_dir, }; return $config; } sub ftp_list { my $self = shift; my $client = shift; my $do_quit = shift; $do_quit = 1 unless defined($do_quit); my $conn = $client->list_raw(); unless ($conn) { die("Failed to LIST: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 10); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); if ($do_quit) { ($resp_code, $resp_msg) = $client->quit(); my $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); } 1; } sub proxy_sql_reverse_config_connect_policy_per_user_by_sql { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $vhost_port2 = $vhost_port - 7; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyTimeoutConnect} = '1sec'; $proxy_config->{ProxyReverseConnectPolicy} = 'PerUser'; $proxy_config->{ProxyReverseServers} = "ftp://127.0.0.1:$vhost_port"; # Since we need multiple ProxyReverseServers directives, convert this # hashref into an arrayref. $proxy_config = config_hash2array($proxy_config); push(@$proxy_config, "ProxyReverseServers sql:/get-user-servers"); my $nbackends = 1; my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db"); # Build up the sqlite3 command to create tables and populate them my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql"); if (open(my $fh, "> $db_script")) { print $fh <{user}', 'ftp://127.0.0.1:$vhost_port2'); EOS unless (close($fh)) { die("Can't write $db_script: $!"); } } else { die("Can't open $db_script: $!"); } my $cmd = "sqlite3 $db_file < $db_script"; build_db($cmd, $db_script); # Make sure that, if we're running as root, the database file has # the permissions/privs set for use by proftpd if ($< == 0) { unless (chmod(0666, $db_file)) { die("Can't set perms on $db_file to 0666: $!"); } } my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.reverse:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, SocketBindTight => 'on', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_proxy.c' => $proxy_config, 'mod_sql.c' => { SQLEngine => 'log', SQLBackend => 'sqlite3', SQLConnectInfo => $db_file, SQLLogFile => $setup->{log_file}, SQLNamedQuery => 'get-user-servers SELECT "url FROM proxy_user_servers WHERE user_name = \'%{0}\'"', } }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off Port $vhost_port2 ServerName "Other Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { for (my $i = 0; $i < $nbackends+1; $i++) { sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 0); $client->login($setup->{user}, $setup->{passwd}); ftp_list($self, $client); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_sql_reverse_config_connect_policy_per_user_by_sql_bad_url { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $vhost_port2 = $vhost_port - 7; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyTimeoutConnect} = '1sec'; $proxy_config->{ProxyReverseConnectPolicy} = 'PerUser'; $proxy_config->{ProxyReverseServers} = "ftp://127.0.0.1:$vhost_port"; # Since we need multiple ProxyReverseServers directives, convert this # hashref into an arrayref. $proxy_config = config_hash2array($proxy_config); push(@$proxy_config, "ProxyReverseServers sql:/get-user-servers"); my $nbackends = 1; my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db"); # Build up the sqlite3 command to create tables and populate them my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql"); if (open(my $fh, "> $db_script")) { print $fh <{user}', 'ftp://foo.bar.com'); EOS unless (close($fh)) { die("Can't write $db_script: $!"); } } else { die("Can't open $db_script: $!"); } my $cmd = "sqlite3 $db_file < $db_script"; build_db($cmd, $db_script); # Make sure that, if we're running as root, the database file has # the permissions/privs set for use by proftpd if ($< == 0) { unless (chmod(0666, $db_file)) { die("Can't set perms on $db_file to 0666: $!"); } } my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.reverse:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, SocketBindTight => 'on', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_proxy.c' => $proxy_config, 'mod_sql.c' => { SQLEngine => 'log', SQLBackend => 'sqlite3', SQLConnectInfo => $db_file, SQLLogFile => $setup->{log_file}, SQLNamedQuery => 'get-user-servers SELECT "url FROM proxy_user_servers WHERE user_name = \'%{0}\'"', } }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off Port $vhost_port2 ServerName "Other Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { for (my $i = 0; $i < $nbackends+1; $i++) { sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 0); $client->login($setup->{user}, $setup->{passwd}); ftp_list($self, $client); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_sql_reverse_config_connect_policy_per_user_by_sql_username_override { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $vhost_port2 = $vhost_port - 7; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyTimeoutConnect} = '1sec'; $proxy_config->{ProxyReverseConnectPolicy} = 'PerUser'; delete($proxy_config->{ProxyReverseServers}); # Since we need multiple ProxyReverseServers directives, convert this # hashref into an arrayref. $proxy_config = config_hash2array($proxy_config); push(@$proxy_config, "ProxyReverseServers sql:/get-user-servers\n"); my $nbackends = 1; my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db"); # Build up the sqlite3 command to create tables and populate them my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql"); if (open(my $fh, "> $db_script")) { print $fh <{user}', 'ftp://$setup->{user}:\@127.0.0.1:$vhost_port2'); EOS unless (close($fh)) { die("Can't write $db_script: $!"); } } else { die("Can't open $db_script: $!"); } my $cmd = "sqlite3 $db_file < $db_script"; build_db($cmd, $db_script); # Make sure that, if we're running as root, the database file has # the permissions/privs set for use by proftpd if ($< == 0) { unless (chmod(0666, $db_file)) { die("Can't set perms on $db_file to 0666: $!"); } } my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.reverse:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, SocketBindTight => 'on', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_proxy.c' => $proxy_config, 'mod_sql.c' => { SQLEngine => 'log', SQLBackend => 'sqlite3', SQLConnectInfo => $db_file, SQLLogFile => $setup->{log_file}, SQLNamedQuery => 'get-user-servers SELECT "url FROM proxy_user_servers WHERE user_name = \'%{0}\'"', } }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off Port $vhost_port2 ServerName "Other Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { for (my $i = 0; $i < $nbackends+1; $i++) { sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 0); $client->login($setup->{user}, $setup->{passwd}); ftp_list($self, $client); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_sql_reverse_config_connect_policy_per_user_by_sql_use_reverse_proxy_auth { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $vhost_port2 = $vhost_port - 7; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyOptions} = 'UseReverseProxyAuth'; $proxy_config->{ProxyTimeoutConnect} = '1sec'; $proxy_config->{ProxyReverseConnectPolicy} = 'PerUser'; $proxy_config->{ProxyReverseServers} = "ftp://127.0.0.1:$vhost_port"; # Since we need multiple ProxyReverseServers directives, convert this # hashref into an arrayref. $proxy_config = config_hash2array($proxy_config); push(@$proxy_config, "ProxyReverseServers sql:/get-user-servers"); my $nbackends = 1; my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db"); # Build up the sqlite3 command to create tables and populate them my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql"); if (open(my $fh, "> $db_script")) { print $fh <{user}', 'ftp://127.0.0.1:$vhost_port2'); EOS unless (close($fh)) { die("Can't write $db_script: $!"); } } else { die("Can't open $db_script: $!"); } my $cmd = "sqlite3 $db_file < $db_script"; build_db($cmd, $db_script); # Make sure that, if we're running as root, the database file has # the permissions/privs set for use by proftpd if ($< == 0) { unless (chmod(0666, $db_file)) { die("Can't set perms on $db_file to 0666: $!"); } } my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.reverse:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, SocketBindTight => 'on', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_proxy.c' => $proxy_config, 'mod_sql.c' => { SQLEngine => 'log', SQLBackend => 'sqlite3', SQLConnectInfo => $db_file, SQLLogFile => $setup->{log_file}, SQLNamedQuery => 'get-user-servers SELECT "url FROM proxy_user_servers WHERE user_name = \'%{0}\'"', } }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off Port $vhost_port2 ServerName "Other Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { my $clients = []; for (my $i = 0; $i < $nbackends+1; $i++) { sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 0); $client->login($setup->{user}, $setup->{passwd}); ftp_list($self, $client, 0); push(@$clients, $client); } sleep(3); foreach my $client (@$clients) { $client->quit(); } $clients = undef; }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 5) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_sql_reverse_config_redis_connect_policy_per_user_by_sql { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $vhost_port2 = $vhost_port - 7; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyTimeoutConnect} = '1sec'; $proxy_config->{ProxyReverseConnectPolicy} = 'PerUser'; $proxy_config->{ProxyReverseServers} = "ftp://127.0.0.1:$vhost_port"; # Since we need multiple ProxyReverseServers directives, convert this # hashref into an arrayref. $proxy_config = config_hash2array($proxy_config); push(@$proxy_config, "ProxyReverseServers sql:/get-user-servers"); my $nbackends = 1; my $redis_server = '127.0.0.1'; if (defined($ENV{REDIS_HOST})) { $redis_server = $ENV{REDIS_HOST}; } # No, the trailing '.' is NOT a typo; it is part of the prefix. push(@$proxy_config, "ProxyDatastore Redis $redis_server."); my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db"); # Build up the sqlite3 command to create tables and populate them my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql"); if (open(my $fh, "> $db_script")) { print $fh <{user}', 'ftp://127.0.0.1:$vhost_port2'); EOS unless (close($fh)) { die("Can't write $db_script: $!"); } } else { die("Can't open $db_script: $!"); } my $cmd = "sqlite3 $db_file < $db_script"; build_db($cmd, $db_script); # Make sure that, if we're running as root, the database file has # the permissions/privs set for use by proftpd if ($< == 0) { unless (chmod(0666, $db_file)) { die("Can't set perms on $db_file to 0666: $!"); } } my $redis_config = get_redis_config($setup->{log_file}); my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.reverse:20 proxy.reverse.db:20 proxy.reverse.redis:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 redis:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, SocketBindTight => 'on', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_proxy.c' => $proxy_config, 'mod_redis.c' => $redis_config, 'mod_sql.c' => { SQLEngine => 'log', SQLBackend => 'sqlite3', SQLConnectInfo => $db_file, SQLLogFile => $setup->{log_file}, SQLNamedQuery => 'get-user-servers SELECT "url FROM proxy_user_servers WHERE user_name = \'%{0}\'"', } }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off # Make sure the client logs into the other virtual server by # denying the login to the test user here, too. Deny $setup->{user} Port $vhost_port2 ServerName "Other Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { for (my $i = 0; $i < $nbackends+1; $i++) { # Allow for server startup sleep(3); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 2); $client->login($setup->{user}, $setup->{passwd}); ftp_list($self, $client, 0); $client->quit(); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_sql_reverse_config_connect_policy_per_group_by_sql { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $vhost_port2 = $vhost_port - 7; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyTimeoutConnect} = '1sec'; $proxy_config->{ProxyReverseConnectPolicy} = 'PerGroup'; $proxy_config->{ProxyOptions} = 'UseReverseProxyAuth'; $proxy_config->{ProxyReverseServers} = "ftp://127.0.0.1:$vhost_port"; # Since we need multiple ProxyReverseServers directives, convert this # hashref into an arrayref. $proxy_config = config_hash2array($proxy_config); push(@$proxy_config, "ProxyReverseServers sql:/get-group-servers"); my $nbackends = 1; my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db"); # Build up the sqlite3 command to create tables and populate them my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql"); if (open(my $fh, "> $db_script")) { print $fh <{group}', 'ftp://127.0.0.1:$vhost_port2'); EOS unless (close($fh)) { die("Can't write $db_script: $!"); } } else { die("Can't open $db_script: $!"); } my $cmd = "sqlite3 $db_file < $db_script"; build_db($cmd, $db_script); # Make sure that, if we're running as root, the database file has # the permissions/privs set for use by proftpd if ($< == 0) { unless (chmod(0666, $db_file)) { die("Can't set perms on $db_file to 0666: $!"); } } my $timeout_idle = 10; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.reverse:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, SocketBindTight => 'on', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_proxy.c' => $proxy_config, 'mod_sql.c' => { SQLEngine => 'log', SQLBackend => 'sqlite3', SQLConnectInfo => $db_file, SQLLogFile => $setup->{log_file}, SQLNamedQuery => 'get-group-servers SELECT "url FROM proxy_group_servers WHERE group_name = \'%{0}\'"', } }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off Port $vhost_port2 ServerName "Other Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(2); for (my $i = 0; $i < $nbackends+1; $i++) { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 0); $client->login($setup->{user}, $setup->{passwd}); ftp_list($self, $client); sleep(2); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub get_sessions { my $db_file = shift; my $where = shift; my $sql = "SELECT user, protocol, frontend_ipaddr, local_ipaddr, backend_ipaddr, backend_port, timestamp FROM proxy_sessions"; if ($where) { $sql .= " WHERE $where"; } my $cmd = "sqlite3 $db_file \"$sql\""; if ($ENV{TEST_VERBOSE}) { print STDERR "Executing sqlite3: $cmd\n"; } my $res = join('', `$cmd`); chomp($res); # The default sqlite3 delimiter is '|' return split(/\|/, $res); } sub get_transfers { my $db_file = shift; my $where = shift; my $sql = "SELECT user, protocol, frontend_ipaddr, local_ipaddr, backend_ipaddr, backend_port, file, timestamp FROM proxy_transfers"; if ($where) { $sql .= " WHERE $where"; } my $cmd = "sqlite3 $db_file \"$sql\""; if ($ENV{TEST_VERBOSE}) { print STDERR "Executing sqlite3: $cmd\n"; } my $res = join('', `$cmd`); chomp($res); # The default sqlite3 delimiter is '|' return split(/\|/, $res); } sub proxy_sql_sqllog_forward_proxied_address_note_issue175 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $vhost_port2 = $vhost_port - 7; my $proxy_config = get_forward_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'proxyuser@host,user'; my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db"); # Build up the sqlite3 command to create tables and populate them my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql"); if (open(my $fh, "> $db_script")) { print $fh < $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 auth:0 event:0 jot:20 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 sql:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, SocketBindTight => 'on', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_proxy.c' => $proxy_config, 'mod_sql.c' => { SQLEngine => 'log', SQLBackend => 'sqlite3', # We provide dummy username/password values, in order to specify # the policy. In case of chroots, we need to explicitly use # the PERCONNECTION policy. SQLConnectInfo => "$db_file foo bar PERCONNECTION", SQLLogFile => $setup->{log_file}, SQLNamedQuery => 'session_start FREEFORM "INSERT INTO proxy_sessions (user, protocol, frontend_ipaddr, local_ipaddr, backend_ipaddr, backend_port, timestamp) VALUES (\'%u\', \'%{protocol}\', \'%a\', \'%L\', \'%{note:mod_proxy.backend-ip}\', %{note:mod_proxy.backend-port}, \'%{iso8601}\')"', SQLLog => 'PASS session_start', } }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 0); $client->login("$setup->{user}\@127.0.0.1:$vhost_port", $setup->{passwd}); $client->login($setup->{user}, $setup->{passwd}); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex) if $ex; eval { my ($login, $protocol, $frontend_ip, $local_ip, $backend_ip, $backend_port, $timestamp) = get_sessions($db_file, "user = \'$setup->{user}\'"); my $expected = $setup->{user}; $self->assert($expected eq $login, "Expected '$expected', got '$login'"); $expected = 'ftp'; $self->assert($expected eq $protocol, "Expected '$expected', got '$protocol'"); $expected = '127.0.0.1'; $self->assert($expected eq $frontend_ip, "Expected frontend IP '$expected', got '$frontend_ip'"); $self->assert($expected eq $local_ip, "Expected local IP '$expected', got '$local_ip'"); $self->assert($expected eq $backend_ip, "Expected backend IP '$expected', got '$backend_ip'"); $expected = $vhost_port; $self->assert($expected == $backend_port, "Expected backend port $expected, got $backend_port"); }; if ($@) { $ex = $@; } test_cleanup($setup->{log_file}, $ex); } sub proxy_sql_sqllog_forward_proxied_file_xfer_issue175 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $vhost_port2 = $vhost_port - 7; my $proxy_config = get_forward_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'proxyuser@host,user'; my $test_file = File::Spec->rel2abs("$tmpdir/test.dat"); if (open(my $fh, "> $test_file")) { print $fh "Hello, World!\n"; unless (close($fh)) { die("Can't write $test_file: $!"); } } else { die("Can't open $test_file: $!"); } my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db"); # Build up the sqlite3 command to create tables and populate them my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql"); if (open(my $fh, "> $db_script")) { print $fh < $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 auth:0 event:0 jot:20 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 sql:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, SocketBindTight => 'on', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_proxy.c' => $proxy_config, 'mod_sql.c' => { SQLEngine => 'log', SQLBackend => 'sqlite3', # We provide dummy username/password values, in order to specify # the policy. In case of chroots, we need to explicitly use # the PERCONNECTION policy. SQLConnectInfo => "$db_file foo bar PERCONNECTION", SQLLogFile => $setup->{log_file}, SQLNamedQuery => 'file_xfer FREEFORM "INSERT INTO proxy_transfers (user, protocol, frontend_ipaddr, local_ipaddr, backend_ipaddr, backend_port, file, timestamp) VALUES (\'%u\', \'%{protocol}\', \'%a\', \'%L\', \'%{note:mod_proxy.backend-ip}\', %{note:mod_proxy.backend-port}, \'%F\', \'%{iso8601}\')"', SQLLog => 'RETR,STOR file_xfer', } }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 0); $client->login("$setup->{user}\@127.0.0.1:$vhost_port", $setup->{passwd}); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->retr_raw('test.dat'); unless ($conn) { die("RETR failed: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf = ''; $conn->read($buf, 1024, 10); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex) if $ex; eval { my ($login, $protocol, $frontend_ip, $local_ip, $backend_ip, $backend_port, $path, $timestamp) = get_transfers($db_file, "user = \'$setup->{user}\'"); my $expected = $setup->{user}; $self->assert($expected eq $login, "Expected '$expected', got '$login'"); $expected = 'ftp'; $self->assert($expected eq $protocol, "Expected '$expected', got '$protocol'"); $expected = '127.0.0.1'; $self->assert($expected eq $frontend_ip, "Expected frontend IP '$expected', got '$frontend_ip'"); $self->assert($expected eq $local_ip, "Expected local IP '$expected', got '$local_ip'"); $self->assert($expected eq $backend_ip, "Expected backend IP '$expected', got '$backend_ip'"); $expected = $vhost_port; $self->assert($expected == $backend_port, "Expected backend port $expected, got $backend_port"); $expected = 'test.dat'; $self->assert($expected eq $path, "Expected transfer path '$expected', got '$path'"); }; if ($@) { $ex = $@; } test_cleanup($setup->{log_file}, $ex); } sub proxy_sql_sqllog_reverse_proxied_address_note_issue175 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $vhost_port2 = $vhost_port - 7; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyTimeoutConnect} = '1sec'; $proxy_config->{ProxyReverseConnectPolicy} = 'RoundRobin'; my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db"); # Build up the sqlite3 command to create tables and populate them my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql"); if (open(my $fh, "> $db_script")) { print $fh < $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 auth:0 event:0 jot:20 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.reverse:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 sql:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, SocketBindTight => 'on', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_proxy.c' => $proxy_config, 'mod_sql.c' => { SQLEngine => 'log', SQLBackend => 'sqlite3', # We provide dummy username/password values, in order to specify # the policy. In case of chroots, we need to explicitly use # the PERCONNECTION policy. SQLConnectInfo => "$db_file foo bar PERCONNECTION", SQLLogFile => $setup->{log_file}, SQLNamedQuery => 'session_start FREEFORM "INSERT INTO proxy_sessions (user, protocol, frontend_ipaddr, local_ipaddr, backend_ipaddr, backend_port, timestamp) VALUES (\'%u\', \'%{protocol}\', \'%a\', \'%L\', \'%{note:mod_proxy.backend-ip}\', %{note:mod_proxy.backend-port}, \'%{iso8601}\')"', SQLLog => 'PASS session_start', } }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 0); $client->login($setup->{user}, $setup->{passwd}); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex) if $ex; eval { my ($login, $protocol, $frontend_ip, $local_ip, $backend_ip, $backend_port, $timestamp) = get_sessions($db_file, "user = \'$setup->{user}\'"); my $expected = $setup->{user}; $self->assert($expected eq $login, "Expected '$expected', got '$login'"); $expected = 'ftp'; $self->assert($expected eq $protocol, "Expected '$expected', got '$protocol'"); $expected = '127.0.0.1'; $self->assert($expected eq $frontend_ip, "Expected frontend IP '$expected', got '$frontend_ip'"); $self->assert($expected eq $local_ip, "Expected local IP '$expected', got '$local_ip'"); $self->assert($expected eq $backend_ip, "Expected backend IP '$expected', got '$backend_ip'"); $expected = $vhost_port; $self->assert($expected == $backend_port, "Expected backend port $expected, got $backend_port"); }; if ($@) { $ex = $@; } test_cleanup($setup->{log_file}, $ex); } sub proxy_sql_sqllog_reverse_proxied_file_xfer_issue175 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $vhost_port2 = $vhost_port - 7; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyReverseConnectPolicy} = 'Shuffle'; my $test_file = File::Spec->rel2abs("$tmpdir/test.dat"); if (open(my $fh, "> $test_file")) { print $fh "Hello, World!\n"; unless (close($fh)) { die("Can't write $test_file: $!"); } } else { die("Can't open $test_file: $!"); } my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db"); # Build up the sqlite3 command to create tables and populate them my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql"); if (open(my $fh, "> $db_script")) { print $fh < $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 auth:0 event:0 jot:20 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.reverse:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 sql:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, SocketBindTight => 'on', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_proxy.c' => $proxy_config, 'mod_sql.c' => { SQLEngine => 'log', SQLBackend => 'sqlite3', # We provide dummy username/password values, in order to specify # the policy. In case of chroots, we need to explicitly use # the PERCONNECTION policy. SQLConnectInfo => "$db_file foo bar PERCONNECTION", SQLLogFile => $setup->{log_file}, SQLNamedQuery => 'file_xfer FREEFORM "INSERT INTO proxy_transfers (user, protocol, frontend_ipaddr, local_ipaddr, backend_ipaddr, backend_port, file, timestamp) VALUES (\'%u\', \'%{protocol}\', \'%a\', \'%L\', \'%{note:mod_proxy.backend-ip}\', %{note:mod_proxy.backend-port}, \'%F\', \'%{iso8601}\')"', SQLLog => 'RETR,STOR file_xfer', } }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TimeoutIdle $timeout_idle TransferLog none WtmpLog off EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 0); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->retr_raw('test.dat'); unless ($conn) { die("RETR failed: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf = ''; $conn->read($buf, 1024, 10); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex) if $ex; eval { my ($login, $protocol, $frontend_ip, $local_ip, $backend_ip, $backend_port, $path, $timestamp) = get_transfers($db_file, "user = \'$setup->{user}\'"); my $expected = $setup->{user}; $self->assert($expected eq $login, "Expected '$expected', got '$login'"); $expected = 'ftp'; $self->assert($expected eq $protocol, "Expected '$expected', got '$protocol'"); $expected = '127.0.0.1'; $self->assert($expected eq $frontend_ip, "Expected frontend IP '$expected', got '$frontend_ip'"); $self->assert($expected eq $local_ip, "Expected local IP '$expected', got '$local_ip'"); $self->assert($expected eq $backend_ip, "Expected backend IP '$expected', got '$backend_ip'"); $expected = $vhost_port; $self->assert($expected == $backend_port, "Expected backend port $expected, got $backend_port"); $expected = 'test.dat'; $self->assert($expected eq $path, "Expected transfer path '$expected', got '$path'"); }; if ($@) { $ex = $@; } test_cleanup($setup->{log_file}, $ex); } 1; proftpd-mod_proxy-0.9.5/t/lib/ProFTPD/Tests/Modules/mod_proxy/ssh.pm000066400000000000000000010321021475737016700253550ustar00rootroot00000000000000package ProFTPD::Tests::Modules::mod_proxy::ssh; use lib qw(t/lib); use base qw(ProFTPD::TestSuite::Child); use strict; use Carp; use Digest::MD5; use File::Copy; use File::Path qw(mkpath); use File::Spec; use IO::Handle; use IO::Socket::INET; use IPC::Open3; use POSIX qw(:fcntl_h); use Time::HiRes qw(gettimeofday tv_interval usleep); use ProFTPD::TestSuite::FTP; use ProFTPD::TestSuite::Utils qw(:auth :config :running :test :testsuite); $| = 1; my $order = 0; my $TESTS = { proxy_reverse_backend_ssh_connect_bad_version_format => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_connect_timeoutlogin => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_kex_dh_group1_sha1 => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_kex_dh_group14_sha1 => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_kex_dh_group14_sha256 => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_kex_dh_group16_sha512 => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_kex_dh_group18_sha512 => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_kex_dh_gex_sha1 => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_kex_dh_gex_sha256 => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_kex_ecdh_sha2_nistp256 => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_kex_ecdh_sha2_nistp384 => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_kex_ecdh_sha2_nistp521 => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_kex_curve25519_sha256 => { order => ++$order, test_class => [qw(feature_sodium forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_kex_curve448_sha512 => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_kex_rsa1024_sha1 => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_hostkey_rsa => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_hostkey_rsa_sha256 => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_hostkey_rsa_sha512 => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_hostkey_dss => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_hostkey_ecdsa256 => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_hostkey_ecdsa384 => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_hostkey_ecdsa521 => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_hostkey_ed25519 => { order => ++$order, test_class => [qw(feature_sodium forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_cipher_aes256_gcm => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_cipher_aes256_ctr => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_cipher_aes256_cbc => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_cipher_aes192_ctr => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_cipher_aes192_cbc => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_cipher_aes128_gcm => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_cipher_aes128_ctr => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_cipher_aes128_cbc => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_cipher_blowfish_ctr => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_cipher_blowfish_cbc => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_cipher_cast128_cbc => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_cipher_arcfour256 => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_cipher_arcfour128 => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_cipher_3des_ctr => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_cipher_3des_cbc => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_cipher_none => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_mac_hmac_sha1 => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_mac_hmac_sha1_etm_openssh => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_mac_hmac_sha1_96 => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_mac_hmac_sha1_96_etm_openssh => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_mac_hmac_md5 => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_mac_hmac_md5_etm_openssh => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_mac_hmac_md5_96 => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_mac_hmac_md5_96_etm_openssh => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_mac_hmac_ripemd160 => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_mac_hmac_sha256 => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_mac_hmac_sha256_etm_openssh => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_mac_hmac_sha512 => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_mac_hmac_sha512_etm_openssh => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_mac_umac64_openssh => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_mac_umac64_etm_openssh => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_mac_umac128_openssh => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_mac_umac128_etm_openssh => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_mac_none => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_compress_none => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_compress_zlib => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_compress_zlib_openssh => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_auth_none => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_auth_hostbased => { order => ++$order, test_class => [qw(flaky forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_auth_hostbased_failed => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_auth_hostbased_passphraseprovider => { order => ++$order, test_class => [qw(flaky forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_auth_hostbased_openssh_rsa => { order => ++$order, test_class => [qw(feature_sodium flaky forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_auth_hostbased_rewrite_user => { order => ++$order, test_class => [qw(flaky forking mod_rewrite mod_sftp reverse)], }, proxy_reverse_backend_ssh_auth_kbdint => { order => ++$order, test_class => [qw(forking mod_auth_otp mod_sftp reverse)], }, proxy_reverse_backend_ssh_auth_kbdint_failed => { order => ++$order, test_class => [qw(forking mod_auth_otp mod_sftp reverse)], }, proxy_reverse_backend_ssh_auth_kbdint_rewrite_user => { order => ++$order, test_class => [qw(forking mod_auth_otp mod_rewrite mod_sftp reverse)], }, proxy_reverse_backend_ssh_auth_password => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_auth_password_failed => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_auth_password_with_banner => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_auth_password_twice => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_auth_password_rewrite_user => { order => ++$order, test_class => [qw(forking mod_rewrite mod_sftp reverse)], }, proxy_reverse_backend_ssh_auth_publickey => { order => ++$order, test_class => [qw(flaky forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_auth_publickey_failed => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_auth_publickey_rewrite_user => { order => ++$order, test_class => [qw(flaky forking mod_rewrite mod_sftp reverse)], }, proxy_reverse_backend_ssh_auth_publickey_useproxyauth => { order => ++$order, test_class => [qw(flaky forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_auth_publickey_useproxyauth_peruser => { order => ++$order, test_class => [qw(flaky forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_auth_chain_password_kbdint => { order => ++$order, test_class => [qw(forking mod_auth_otp mod_sftp reverse)], }, proxy_reverse_backend_ssh_auth_chain_publickey_kbdint => { order => ++$order, test_class => [qw(forking mod_auth_otp mod_sftp reverse)], }, proxy_reverse_backend_ssh_verify_server_off => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_verify_server_on => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_sftp_without_auth => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_sftp_stat => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_sftp_upload => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_sftp_download => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_sftp_readdir => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_scp_upload => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_scp_download => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_server_rekey_kex_dh_group1_sha1 => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, # See: https://github.com/proftpd/proftpd/issues/323 proxy_reverse_backend_ssh_server_rekey_kex_dh_group1_sha1_zlib_openssh => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_server_rekey_kex_dh_group14_sha1 => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_server_rekey_kex_dh_group14_sha256 => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_server_rekey_kex_dh_group16_sha512 => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_server_rekey_kex_dh_group18_sha512 => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_server_rekey_kex_dh_gex_sha1 => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_server_rekey_kex_dh_gex_sha256 => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_server_rekey_kex_ecdh_sha2_nistp256 => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_server_rekey_kex_ecdh_sha2_nistp384 => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_server_rekey_kex_ecdh_sha2_nistp521 => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_server_rekey_kex_rsa1024_sha1 => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_server_rekey_kex_curve25519_sha256 => { order => ++$order, test_class => [qw(feature_sodium forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_ext_client_rekey => { order => ++$order, test_class => [qw(flaky forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_sighup => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_extlog => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_connect_policy_per_host => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_connect_policy_per_user => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_connect_policy_per_user_by_json => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_connect_policy_per_group_password_auth => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, proxy_reverse_backend_ssh_connect_policy_per_group_publickey_auth => { order => ++$order, test_class => [qw(forking mod_sftp reverse)], }, # XXX TODO? # proxy_reverse_backend_ssh_sftp_xferlog # proxy_reverse_backend_ssh_scp_xferlog }; sub new { return shift()->SUPER::new(@_); } sub list_tests { # Check for the required Perl modules: # # Net-SSH2 # Net-SSH2-SFTP my $required = [qw( Net::SSH2 Net::SSH2::SFTP )]; foreach my $req (@$required) { eval "use $req"; if ($@) { print STDERR "\nWARNING:\n + Module '$req' not found, skipping all tests\n"; if ($ENV{TEST_VERBOSE}) { print STDERR "Unable to load $req: $@\n"; } return qw(testsuite_empty_test); } } my @tests = testsuite_get_runnable_tests($TESTS); # These tests need to be skipped due to lack of support when using newer # OpenSSL versions, i.e. 3.x or later. my $skipped_tests = { proxy_reverse_backend_ssh_cipher_3des_ctr => 1, proxy_reverse_backend_ssh_cipher_arcfour128 => 1, proxy_reverse_backend_ssh_cipher_arcfour256 => 1, proxy_reverse_backend_ssh_cipher_blowfish_ctr => 1, }; foreach my $key (keys(%$skipped_tests)) { my $ntests = scalar(@tests); for (my $i = 0; $i < $ntests; $i++) { if ($tests[$i] eq $key) { splice(@tests, $i, 1); last; } } } return @tests; } sub set_up { my $self = shift; $self->SUPER::set_up(@_); # Make sure that mod_sftp does not complain about permissions on the hostkey # files. my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $openssh_rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_openssh_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key"); my $ecdsa256_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_ecdsa256_key"); my $ecdsa384_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_ecdsa384_key"); my $ecdsa521_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_ecdsa521_key"); my $ed25519_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_openssh_ed25519_key"); my $passphrase_rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/passphrase_host_rsa_key"); unless (chmod(0400, $rsa_host_key, $openssh_rsa_host_key, $dsa_host_key, $ecdsa256_host_key, $ecdsa384_host_key, $ecdsa521_host_key, $ed25519_host_key, $passphrase_rsa_host_key)) { die("Can't set perms on mod_sftp hostkeys: $!"); } } sub config_hash2array { my $hash = shift; my $array = []; foreach my $key (keys(%$hash)) { push(@$array, "$key $hash->{$key}\n"); } return $array; } sub get_reverse_proxy_config { my $tmpdir = shift; my $log_file = shift; my $vhost_port = shift; my $table_dir = File::Spec->rel2abs("$tmpdir/var/proxy"); my $config = { ProxyEngine => 'on', ProxyLog => $log_file, ProxyReverseServers => "sftp://127.0.0.1:$vhost_port", ProxyRole => 'reverse', ProxyTables => $table_dir, ProxySFTPVerifyServer => 'off', }; return $config; } sub build_db { my $cmd = shift; my $db_script = shift; my $db_file = shift; my $check_exit_status = shift; $check_exit_status = 0 unless defined($check_exit_status); if ($ENV{TEST_VERBOSE}) { print STDERR "Executing sqlite3: $cmd\n"; } my (@output, $exit_status); @output = `$cmd`; $exit_status = $?; if ($ENV{TEST_VERBOSE}) { print STDERR "Output: ", join('', @output), "\n"; } if ($check_exit_status) { if ($? != 0) { croak("'$cmd' failed"); } } # Make sure that, if we're running as root, the database file has # the permissions/privs set for use by proftpd if ($< == 0) { unless (chmod(0666, $db_file)) { croak("Can't set perms on $db_file to 0666: $!"); } } unlink($db_script); return 1; } sub get_sftp_bin { my $sftp = 'sftp'; # Example: # $ export PROXY_TEST_SFTP_BIN=/Users/tj/local/openssh-7.9p1/bin/sftp if (defined($ENV{PROXY_TEST_SFTP_BIN})) { $sftp = $ENV{PROXY_TEST_SFTP_BIN}; } return $sftp; } sub ssh_auth_with_algos { my $self = shift; my $cipher_algo = shift; my $digest_algo = shift; my $kex_algo = shift; my $proxy_sftp_opts = shift; my $ssh_host_key = shift; $ssh_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key") unless $ssh_host_key; my $comp_algo = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxySFTPCiphers} = $cipher_algo if defined($cipher_algo); $proxy_config->{ProxySFTPCompression} = $comp_algo if defined($comp_algo); $proxy_config->{ProxySFTPDigests} = $digest_algo if defined($digest_algo); $proxy_config->{ProxySFTPKeyExchanges} = $kex_algo if defined($kex_algo); # Some options do not (yet?) apply to mod_proxy. my $sftp_opts = $proxy_sftp_opts; if ($proxy_sftp_opts =~ /AllowInsecureLogin/) { $proxy_sftp_opts =~ s/AllowInsecureLogin//ig; } if ($proxy_sftp_opts =~ /NoHostkeyRotation/) { $proxy_sftp_opts =~ s/NoHostkeyRotation//ig; } if ($proxy_sftp_opts =~ /^\s*$/) { $proxy_sftp_opts = undef; } $proxy_config->{ProxySFTPOptions} = $proxy_sftp_opts if defined($proxy_sftp_opts); if ($sftp_opts =~ /^\s*$/) { $sftp_opts = undef; } my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.cipher:20 proxy.ssh.disconnect:20 proxy.ssh.mac:20 proxy.ssh.packet:20 proxy.ssh.kex:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $ssh_host_key SFTPCiphers $cipher_algo SFTPCompression delayed SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo EOC print $fh "SFTPOptions $sftp_opts\n" if defined($sftp_opts); print $fh < EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(3); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } if ($ENV{TEST_VERBOSE}) { print STDERR "Disconnecting SSH...\n"; } $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub ssh_rekey_with_algos { my $self = shift; my $cipher_algo = shift; my $digest_algo = shift; my $kex_algo = shift; my $proxy_sftp_opts = shift; my $ssh_host_key = shift; $ssh_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key") unless $ssh_host_key; my $comp_algo = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxySFTPCiphers} = $cipher_algo if defined($cipher_algo); $proxy_config->{ProxySFTPCompression} = $comp_algo if defined($comp_algo); $proxy_config->{ProxySFTPDigests} = $digest_algo if defined($digest_algo); $proxy_config->{ProxySFTPKeyExchanges} = $kex_algo if defined($kex_algo); if ($proxy_sftp_opts =~ /^\s*$/) { $proxy_sftp_opts = undef; } $proxy_config->{ProxySFTPOptions} = $proxy_sftp_opts if defined($proxy_sftp_opts); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $test_file = File::Spec->rel2abs("$tmpdir/test.dat"); if (open(my $fh, "> $test_file")) { # Make a file that's larger than the maximum SSH2 packet size, forcing # the sftp code to loop properly until the entire large file is sent. print $fh "ABCDefgh" x 262144; unless (close($fh)) { die("Can't write $test_file: $!"); } } else { die("Can't open $test_file: $!"); } # Calculate the MD5 checksum of this file, for comparison with the # downloaded file. my $ctx = Digest::MD5->new(); my $expected_md5; if (open(my $fh, "< $test_file")) { binmode($fh); $ctx->addfile($fh); $expected_md5 = $ctx->hexdigest(); close($fh); } else { die("Can't read $test_file: $!"); } my $test_file2 = File::Spec->rel2abs("$tmpdir/test2.dat"); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.cipher:20 proxy.ssh.disconnect:20 proxy.ssh.mac:20 proxy.ssh.packet:20 proxy.ssh.kex:30 proxy.ssh.keys:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c TimeoutIdle 10 AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $ssh_host_key SFTPCiphers $cipher_algo SFTPCompression delayed SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo SFTPRekey required 5 1 2 EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { my $test_wfh; unless (open($test_wfh, "> $test_file2")) { die("Can't read $test_file2: $!"); } binmode($test_wfh); # Give the server a chance to start up sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_ts = [gettimeofday()]; if ($ENV{TEST_VERBOSE}) { print STDERR "Opening SFTP channel...\n"; } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_elapsed = tv_interval($sftp_start_ts); if ($ENV{TEST_VERBOSE}) { print STDERR "Sending SFTP OPEN request ($sftp_start_elapsed since SFTP channel opened)...\n"; } my $test_rfh = $sftp->open('test.dat', O_RDONLY); unless ($test_rfh) { my ($err_code, $err_name) = $sftp->error(); die("Can't open test.dat: [$err_name] ($err_code)"); } my $buf; my $bufsz = 8192; my $res = $test_rfh->read($buf, $bufsz); while ($res) { print $test_wfh $buf; $res = $test_rfh->read($buf, $bufsz); } unless (close($test_wfh)) { die("Can't write $test_file2: $!"); } # To issue the FXP_CLOSE, we have to explicitly destroy the filehandle $test_rfh = undef; if ($ENV{TEST_VERBOSE}) { print STDERR "Closing SFTP channel...\n"; } # To close the SFTP channel, we have to explicitly destroy the object $sftp = undef; if ($ENV{TEST_VERBOSE}) { print STDERR "Disconnecting SSH...\n"; } $ssh2->disconnect(); unless (-f $test_file2) { die("$test_file2 file does not exist as expected"); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { # Calculate the MD5 checksum of the uploaded file, for comparison with the # file that was uploaded. $ctx->reset(); my $md5; if (open(my $fh, "< $test_file2")) { binmode($fh); $ctx->addfile($fh); $md5 = $ctx->hexdigest(); close($fh); } else { die("Can't read $test_file2: $!"); } $self->assert($expected_md5 eq $md5, test_msg("Expected '$expected_md5', got '$md5'")); }; if ($@) { $ex = $@; } test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_connect_bad_version_format { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxySFTPCiphers} = $cipher_algo; $proxy_config->{ProxySFTPDigests} = $digest_algo; $proxy_config->{ProxySFTPKeyExchanges} = $kex_algo; my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none # We deliberately do NOT configure mod_sftp in this destination/target # backend vhost, in order to force a non-SSH banner for the proxied # connection. EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(1); my $ssh2 = Net::SSH2->new(); if ($ssh2->connect('127.0.0.1', $port)) { die("Connected to SSH2 server unexpectedly"); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 15) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $setup->{log_file}")) { my $ok = 0; while (my $line = <$fh>) { chomp($line); if ($ENV{TEST_VERBOSE}) { print STDERR "# $line\n"; } if ($line =~ /Bad protocol version/) { $ok = 1; last; } } close($fh); $self->assert($ok, test_msg("Did not see expected ProxyLog message about bad protocol version")); } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_connect_timeoutlogin { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxySFTPCiphers} = $cipher_algo; $proxy_config->{ProxySFTPDigests} = $digest_algo; $proxy_config->{ProxySFTPKeyExchanges} = $kex_algo; my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $timeout_login = 2; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', TimeoutLogin => $timeout_login, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c TimeoutLogin 15 AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key SFTPCiphers $cipher_algo SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } # Trigger TimeoutLogin by waiting longer than that to authenticate. if ($ENV{TEST_VERBOSE}) { print STDERR "# Waiting longer than TimeoutLogin ($timeout_login) secs\n"; } sleep($timeout_login + 1); if ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { die("Login succeed to SSH2 server unexpectedly"); } $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $setup->{log_file}")) { my $ok = 0; while (my $line = <$fh>) { chomp($line); if ($ENV{TEST_VERBOSE}) { print STDERR "# $line\n"; } if ($line =~ /Login timeout exceeded/) { $ok = 1; last; } } close($fh); # Note that this log message is logged at the INFO level, which is only # visible here when TEST_VERBOSE is enabled. if ($ENV{TEST_VERBOSE}) { $self->assert($ok, test_msg("Did not see expected ProxyLog message about TimeoutLogin")); } } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_kex_dh_group1_sha1 { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group1-sha1'; my $proxy_sftp_opts = 'AllowWeakDH'; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_kex_dh_group14_sha1 { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_kex_dh_group14_sha256 { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha256'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_kex_dh_group16_sha512 { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group16-sha512'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_kex_dh_group18_sha512 { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group18-sha512'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_kex_dh_gex_sha1 { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group-exchange-sha1'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_kex_dh_gex_sha256 { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group-exchange-sha256'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_kex_ecdh_sha2_nistp256 { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'ecdh-sha2-nistp256'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_kex_ecdh_sha2_nistp384 { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'ecdh-sha2-nistp384'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_kex_ecdh_sha2_nistp521 { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'ecdh-sha2-nistp521'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_kex_curve25519_sha256 { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'curve25519-sha256'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_kex_curve448_sha512 { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'curve448-sha512'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_kex_rsa1024_sha1 { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'rsa1024-sha1'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_hostkey_rsa { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group-exchange-sha1'; my $proxy_sftp_opts = ''; my $ssh_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); # TODO: Technically, we end up using 'rsa-sha2-512', since that is what # mod_sftp will offer; the hostkey algorithms are currently not configurable. ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts, $ssh_host_key); } sub proxy_reverse_backend_ssh_hostkey_rsa_sha256 { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group-exchange-sha1'; my $proxy_sftp_opts = ''; my $ssh_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); # TODO: Technically, we end up using 'rsa-sha2-512', since that is what # mod_sftp will offer; the hostkey algorithms are currently not configurable. ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts, $ssh_host_key); } sub proxy_reverse_backend_ssh_hostkey_rsa_sha512 { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group-exchange-sha1'; my $proxy_sftp_opts = ''; my $ssh_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); # TODO: Technically, we end up using 'rsa-sha2-512', since that is what # mod_sftp will offer; the hostkey algorithms are currently not configurable. ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts, $ssh_host_key); } sub proxy_reverse_backend_ssh_hostkey_dss { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group-exchange-sha1'; my $proxy_sftp_opts = ''; my $ssh_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key"); ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts, $ssh_host_key); } sub proxy_reverse_backend_ssh_hostkey_ecdsa256 { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = ''; my $ssh_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_ecdsa256_key"); ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts, $ssh_host_key); } sub proxy_reverse_backend_ssh_hostkey_ecdsa384 { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group-exchange-sha1'; my $proxy_sftp_opts = ''; my $ssh_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_ecdsa384_key"); ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts, $ssh_host_key); } sub proxy_reverse_backend_ssh_hostkey_ecdsa521 { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group-exchange-sha1'; my $proxy_sftp_opts = ''; my $ssh_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_ecdsa521_key"); ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts, $ssh_host_key); } sub proxy_reverse_backend_ssh_hostkey_ed25519 { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group-exchange-sha1'; my $proxy_sftp_opts = ''; my $ssh_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_openssh_ed25519_key"); ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts, $ssh_host_key); } sub proxy_reverse_backend_ssh_cipher_aes256_gcm { my $self = shift; my $cipher_algo = 'aes256-gcm@openssh.com'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_cipher_aes256_ctr { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_cipher_aes256_cbc { my $self = shift; my $cipher_algo = 'aes256-cbc'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_cipher_aes192_ctr { my $self = shift; my $cipher_algo = 'aes192-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_cipher_aes192_cbc { my $self = shift; my $cipher_algo = 'aes192-cbc'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_cipher_aes128_gcm { my $self = shift; my $cipher_algo = 'aes128-gcm@openssh.com'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_cipher_aes128_ctr { my $self = shift; my $cipher_algo = 'aes128-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_cipher_aes128_cbc { my $self = shift; my $cipher_algo = 'aes128-cbc'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_cipher_blowfish_ctr { my $self = shift; my $cipher_algo = 'blowfish-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_cipher_blowfish_cbc { my $self = shift; my $cipher_algo = 'blowfish-cbc'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_cipher_cast128_cbc { my $self = shift; my $cipher_algo = 'cast128-cbc'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_cipher_arcfour256 { my $self = shift; my $cipher_algo = 'arcfour256'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_cipher_arcfour128 { my $self = shift; my $cipher_algo = 'arcfour128'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_cipher_3des_ctr { my $self = shift; my $cipher_algo = '3des-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_cipher_3des_cbc { my $self = shift; my $cipher_algo = '3des-cbc'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_cipher_none { my $self = shift; # Note that in order to use the 'none' algorithms for _e.g. password # authentication, mod_sftp requires the AllowInsecureLogin option. my $cipher_algo = 'none'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = 'AllowInsecureLogin'; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_mac_hmac_sha1 { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_mac_hmac_sha1_etm_openssh { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1-etm@openssh.com'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_mac_hmac_sha1_96 { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1-96'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_mac_hmac_sha1_96_etm_openssh { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1-96-etm@openssh.com'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_mac_hmac_md5 { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-md5'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_mac_hmac_md5_etm_openssh { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-md5-etm@openssh.com'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_mac_hmac_md5_96 { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-md5-96'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_mac_hmac_md5_96_etm_openssh { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-md5-96-etm@openssh.com'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_mac_hmac_ripemd160 { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-ripemd160'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_mac_hmac_sha256 { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha2-256'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_mac_hmac_sha256_etm_openssh { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha2-256-etm@openssh.com'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_mac_hmac_sha512 { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha2-512'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_mac_hmac_sha512_etm_openssh { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha2-512-etm@openssh.com'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_mac_umac64_openssh { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'umac-64@openssh.com'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_mac_umac64_etm_openssh { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'umac-64-etm@openssh.com'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_mac_umac128_openssh { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'umac-128@openssh.com'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_mac_umac128_etm_openssh { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'umac-128@openssh.com'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_mac_none { my $self = shift; my $cipher_algo = 'aes256-ctr'; # Note that in order to use the 'none' algorithms for _e.g. password # authentication, mod_sftp requires the AllowInsecureLogin option. my $digest_algo = 'none'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = 'AllowInsecureLogin'; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_compress_none { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $comp_algo = 'off'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts, undef, $comp_algo); } sub proxy_reverse_backend_ssh_compress_zlib { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $comp_algo = 'on'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts, undef, $comp_algo); } sub proxy_reverse_backend_ssh_compress_zlib_openssh { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $comp_algo = 'delayed'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = ''; ssh_auth_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts, undef, $comp_algo); } sub proxy_reverse_backend_ssh_auth_none { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxySFTPCiphers} = $cipher_algo; $proxy_config->{ProxySFTPDigests} = $digest_algo; $proxy_config->{ProxySFTPKeyExchanges} = $kex_algo; my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key SFTPCiphers $cipher_algo SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } my $auth_methods = $ssh2->auth_list($setup->{user}); unless ($auth_methods) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't list SSH2 server auth methods: [$err_name] ($err_code) $err_str"); } if ($ENV{TEST_VERBOSE}) { print STDERR "# authentication methods: $auth_methods\n"; } $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_auth_hostbased { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxySFTPCiphers} = $cipher_algo; $proxy_config->{ProxySFTPDigests} = $digest_algo; $proxy_config->{ProxySFTPKeyExchanges} = $kex_algo; my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); # For hostbased authentication my $rsa_priv_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/test_rsa_key"); my $rsa_pub_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/test_rsa_key.pub"); my $rsa_rfc4716_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/authorized_rsa_keys"); my $authorized_keys = File::Spec->rel2abs("$tmpdir/.authorized_keys"); unless (copy($rsa_rfc4716_key, $authorized_keys)) { die("Can't copy $rsa_rfc4716_key to $authorized_keys: $!"); } unless (chmod(0400, $rsa_priv_key)) { die("Can't set perms on $rsa_priv_key: $!"); } $proxy_config->{ProxySFTPHostKey} = $rsa_priv_key; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key SFTPCiphers $cipher_algo SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo SFTPAuthorizedHostKeys file:~/.authorized_keys EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_hostbased($setup->{user}, $rsa_pub_key, $rsa_priv_key, '127.0.0.1', $setup->{user})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_ts = [gettimeofday()]; if ($ENV{TEST_VERBOSE}) { print STDERR "Opening SFTP channel...\n"; } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_elapsed = tv_interval($sftp_start_ts); if ($ENV{TEST_VERBOSE}) { print STDERR "Sending SFTP STAT request ($sftp_start_elapsed since SFTP channel opened)...\n"; } my $path = 'proxy.conf'; unless ($sftp->stat($path, 1)) { my ($err_code, $err_name) = $sftp->error(); die("STAT $path failed: [$err_name] ($err_code)"); } if ($ENV{TEST_VERBOSE}) { print STDERR "Closing SFTP channel...\n"; } # To close the SFTP channel, we have to explicitly destroy the object $sftp = undef; if ($ENV{TEST_VERBOSE}) { print STDERR "Disconnecting SSH...\n"; } $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_auth_hostbased_failed { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxySFTPCiphers} = $cipher_algo; $proxy_config->{ProxySFTPDigests} = $digest_algo; $proxy_config->{ProxySFTPKeyExchanges} = $kex_algo; my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); # For hostbased authentication my $rsa_priv_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/test_rsa_key"); my $rsa_pub_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/test_rsa_key.pub"); my $rsa_rfc4716_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/authorized_rsa_keys"); my $authorized_keys = File::Spec->rel2abs("$tmpdir/.authorized_keys"); unless (copy($rsa_rfc4716_key, $authorized_keys)) { die("Can't copy $rsa_rfc4716_key to $authorized_keys: $!"); } my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key SFTPCiphers $cipher_algo SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo SFTPAuthorizedHostKeys file:~/.authorized_keys EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } # We expect this to fail because we have not configured any # ProxySFTPHostKeys. if ($ssh2->auth_hostbased($setup->{user}, $rsa_pub_key, $rsa_priv_key, '127.0.0.1', $setup->{user})) { die("Hostbased login succeeded unexpectedly"); } $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_auth_hostbased_passphraseprovider { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxySFTPCiphers} = $cipher_algo; $proxy_config->{ProxySFTPDigests} = $digest_algo; $proxy_config->{ProxySFTPKeyExchanges} = $kex_algo; my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); # For hostbased authentication my $rsa_priv_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/test_rsa_key"); my $rsa_pub_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/test_rsa_key.pub"); my $passphrase_rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/passphrase_host_rsa_key"); my $passphrase_provider = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/sftp-get-passphrase.pl"); $proxy_config->{ProxySFTPHostKey} = $passphrase_rsa_host_key; $proxy_config->{ProxySFTPPassPhraseProvider} = $passphrase_provider; my $rsa_rfc4716_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/authorized_passphrase_rsa_keys"); my $authorized_keys = File::Spec->rel2abs("$tmpdir/.authorized_keys"); unless (copy($rsa_rfc4716_key, $authorized_keys)) { die("Can't copy $rsa_rfc4716_key to $authorized_keys: $!"); } my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key SFTPCiphers $cipher_algo SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo SFTPAuthorizedHostKeys file:~/.authorized_keys EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_hostbased($setup->{user}, $rsa_pub_key, $rsa_priv_key, '127.0.0.1', $setup->{user})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_ts = [gettimeofday()]; if ($ENV{TEST_VERBOSE}) { print STDERR "Opening SFTP channel...\n"; } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_elapsed = tv_interval($sftp_start_ts); if ($ENV{TEST_VERBOSE}) { print STDERR "Sending SFTP STAT request ($sftp_start_elapsed since SFTP channel opened)...\n"; } my $path = 'proxy.conf'; unless ($sftp->stat($path, 1)) { my ($err_code, $err_name) = $sftp->error(); die("STAT $path failed: [$err_name] ($err_code)"); } if ($ENV{TEST_VERBOSE}) { print STDERR "Closing SFTP channel...\n"; } # To close the SFTP channel, we have to explicitly destroy the object $sftp = undef; if ($ENV{TEST_VERBOSE}) { print STDERR "Disconnecting SSH...\n"; } $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_auth_hostbased_openssh_rsa { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxySFTPCiphers} = $cipher_algo; $proxy_config->{ProxySFTPDigests} = $digest_algo; $proxy_config->{ProxySFTPKeyExchanges} = $kex_algo; my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); # For hostbased authentication my $rsa_priv_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/test_rsa_key"); my $rsa_pub_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/test_rsa_key.pub"); my $openssh_rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_openssh_rsa_key"); $proxy_config->{ProxySFTPHostKey} = $openssh_rsa_host_key; # This "authorized_passphrase_rsa_keys" file contains the RFC4716 public # key for this OpenSSH-encoded private key, too. my $rsa_rfc4716_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/authorized_passphrase_rsa_keys"); my $authorized_keys = File::Spec->rel2abs("$tmpdir/.authorized_keys"); unless (copy($rsa_rfc4716_key, $authorized_keys)) { die("Can't copy $rsa_rfc4716_key to $authorized_keys: $!"); } my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key SFTPCiphers $cipher_algo SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo SFTPAuthorizedHostKeys file:~/.authorized_keys EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_hostbased($setup->{user}, $rsa_pub_key, $rsa_priv_key, '127.0.0.1', $setup->{user})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_ts = [gettimeofday()]; if ($ENV{TEST_VERBOSE}) { print STDERR "Opening SFTP channel...\n"; } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_elapsed = tv_interval($sftp_start_ts); if ($ENV{TEST_VERBOSE}) { print STDERR "Sending SFTP STAT request ($sftp_start_elapsed since SFTP channel opened)...\n"; } my $path = 'proxy.conf'; unless ($sftp->stat($path, 1)) { my ($err_code, $err_name) = $sftp->error(); die("STAT $path failed: [$err_name] ($err_code)"); } if ($ENV{TEST_VERBOSE}) { print STDERR "Closing SFTP channel...\n"; } # To close the SFTP channel, we have to explicitly destroy the object $sftp = undef; if ($ENV{TEST_VERBOSE}) { print STDERR "Disconnecting SSH...\n"; } $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_auth_hostbased_rewrite_user { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxySFTPCiphers} = $cipher_algo; $proxy_config->{ProxySFTPDigests} = $digest_algo; $proxy_config->{ProxySFTPKeyExchanges} = $kex_algo; my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); # For hostbased authentication my $rsa_priv_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/test_rsa_key"); my $rsa_pub_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/test_rsa_key.pub"); my $rsa_rfc4716_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/authorized_rsa_keys"); my $authorized_keys = File::Spec->rel2abs("$tmpdir/.authorized_keys"); unless (copy($rsa_rfc4716_key, $authorized_keys)) { die("Can't copy $rsa_rfc4716_key to $authorized_keys: $!"); } unless (chmod(0400, $rsa_priv_key)) { die("Can't set perms on $rsa_priv_key: $!"); } $proxy_config->{ProxySFTPHostKey} = $rsa_priv_key; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_rewrite.c' => [ 'RewriteEngine on', "RewriteLog $setup->{log_file}", 'RewriteMap lowercase int:tolower', 'RewriteCondition %m USER', 'RewriteRule ^(.*)$ ${lowercase:$1}', ], 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key SFTPCiphers $cipher_algo SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo SFTPAuthorizedHostKeys file:~/.authorized_keys EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_hostbased(uc($setup->{user}), $rsa_pub_key, $rsa_priv_key, '127.0.0.1', $setup->{user})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_ts = [gettimeofday()]; if ($ENV{TEST_VERBOSE}) { print STDERR "Opening SFTP channel...\n"; } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_elapsed = tv_interval($sftp_start_ts); if ($ENV{TEST_VERBOSE}) { print STDERR "Sending SFTP STAT request ($sftp_start_elapsed since SFTP channel opened)...\n"; } my $path = 'proxy.conf'; unless ($sftp->stat($path, 1)) { my ($err_code, $err_name) = $sftp->error(); die("STAT $path failed: [$err_name] ($err_code)"); } if ($ENV{TEST_VERBOSE}) { print STDERR "Closing SFTP channel...\n"; } # To close the SFTP channel, we have to explicitly destroy the object $sftp = undef; if ($ENV{TEST_VERBOSE}) { print STDERR "Disconnecting SSH...\n"; } $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_auth_publickey { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxySFTPCiphers} = $cipher_algo; $proxy_config->{ProxySFTPDigests} = $digest_algo; $proxy_config->{ProxySFTPKeyExchanges} = $kex_algo; my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); # For publickey authentication my $rsa_priv_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/test_rsa_key"); my $rsa_pub_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/test_rsa_key.pub"); # For hostbased authentication my $rsa_rfc4716_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/authorized_rsa_keys"); my $authorized_keys = File::Spec->rel2abs("$tmpdir/.authorized_keys"); unless (copy($rsa_rfc4716_key, $authorized_keys)) { die("Can't copy $rsa_rfc4716_key to $authorized_keys: $!"); } unless (chmod(0400, $rsa_priv_key)) { die("Can't set perms on $rsa_priv_key: $!"); } $proxy_config->{ProxySFTPHostKey} = $rsa_priv_key; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key SFTPCiphers $cipher_algo SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo SFTPAuthorizedHostKeys file:~/.authorized_keys EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_publickey($setup->{user}, $rsa_pub_key, $rsa_priv_key)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_ts = [gettimeofday()]; if ($ENV{TEST_VERBOSE}) { print STDERR "Opening SFTP channel...\n"; } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_elapsed = tv_interval($sftp_start_ts); if ($ENV{TEST_VERBOSE}) { print STDERR "Sending SFTP STAT request ($sftp_start_elapsed since SFTP channel opened)...\n"; } my $path = 'proxy.conf'; unless ($sftp->stat($path, 1)) { my ($err_code, $err_name) = $sftp->error(); die("STAT $path failed: [$err_name] ($err_code)"); } if ($ENV{TEST_VERBOSE}) { print STDERR "Closing SFTP channel...\n"; } # To close the SFTP channel, we have to explicitly destroy the object $sftp = undef; if ($ENV{TEST_VERBOSE}) { print STDERR "Disconnecting SSH...\n"; } $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_auth_publickey_failed { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxySFTPCiphers} = $cipher_algo; $proxy_config->{ProxySFTPDigests} = $digest_algo; $proxy_config->{ProxySFTPKeyExchanges} = $kex_algo; my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); # For publickey authentication my $rsa_priv_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/test_rsa_key"); my $rsa_pub_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/test_rsa_key.pub"); # For hostbased authentication my $rsa_rfc4716_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/authorized_rsa_keys"); my $authorized_keys = File::Spec->rel2abs("$tmpdir/.authorized_keys"); unless (copy($rsa_rfc4716_key, $authorized_keys)) { die("Can't copy $rsa_rfc4716_key to $authorized_keys: $!"); } my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key SFTPCiphers $cipher_algo SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo SFTPAuthorizedHostKeys file:~/.authorized_keys EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } if ($ssh2->auth_publickey($setup->{user}, $rsa_pub_key, $rsa_priv_key)) { die("Publickey authentication succeeded unexpectedly"); } $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_auth_publickey_rewrite_user { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxySFTPCiphers} = $cipher_algo; $proxy_config->{ProxySFTPDigests} = $digest_algo; $proxy_config->{ProxySFTPKeyExchanges} = $kex_algo; my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); # For publickey authentication my $rsa_priv_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/test_rsa_key"); my $rsa_pub_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/test_rsa_key.pub"); # For hostbased authentication my $rsa_rfc4716_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/authorized_rsa_keys"); my $authorized_keys = File::Spec->rel2abs("$tmpdir/.authorized_keys"); unless (copy($rsa_rfc4716_key, $authorized_keys)) { die("Can't copy $rsa_rfc4716_key to $authorized_keys: $!"); } unless (chmod(0400, $rsa_priv_key)) { die("Can't set perms on $rsa_priv_key: $!"); } $proxy_config->{ProxySFTPHostKey} = $rsa_priv_key; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_rewrite.c' => [ 'RewriteEngine on', "RewriteLog $setup->{log_file}", 'RewriteMap lowercase int:tolower', 'RewriteCondition %m USER', 'RewriteRule ^(.*)$ ${lowercase:$1}', ], 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key SFTPCiphers $cipher_algo SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo SFTPAuthorizedHostKeys file:~/.authorized_keys EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_publickey(uc($setup->{user}), $rsa_pub_key, $rsa_priv_key)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_ts = [gettimeofday()]; if ($ENV{TEST_VERBOSE}) { print STDERR "Opening SFTP channel...\n"; } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_elapsed = tv_interval($sftp_start_ts); if ($ENV{TEST_VERBOSE}) { print STDERR "Sending SFTP STAT request ($sftp_start_elapsed since SFTP channel opened)...\n"; } my $path = 'proxy.conf'; unless ($sftp->stat($path, 1)) { my ($err_code, $err_name) = $sftp->error(); die("STAT $path failed: [$err_name] ($err_code)"); } if ($ENV{TEST_VERBOSE}) { print STDERR "Closing SFTP channel...\n"; } # To close the SFTP channel, we have to explicitly destroy the object $sftp = undef; if ($ENV{TEST_VERBOSE}) { print STDERR "Disconnecting SSH...\n"; } $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_auth_publickey_useproxyauth { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyOptions} = 'UseReverseProxyAuth'; $proxy_config->{ProxySFTPCiphers} = $cipher_algo; $proxy_config->{ProxySFTPDigests} = $digest_algo; $proxy_config->{ProxySFTPKeyExchanges} = $kex_algo; my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); # For publickey authentication my $rsa_priv_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/test_rsa_key"); my $rsa_pub_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/test_rsa_key.pub"); # For hostbased authentication my $rsa_rfc4716_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/authorized_rsa_keys"); my $authorized_keys = File::Spec->rel2abs("$tmpdir/.authorized_keys"); unless (copy($rsa_rfc4716_key, $authorized_keys)) { die("Can't copy $rsa_rfc4716_key to $authorized_keys: $!"); } unless (chmod(0400, $rsa_priv_key)) { die("Can't set perms on $rsa_priv_key: $!"); } $proxy_config->{ProxySFTPHostKey} = $rsa_priv_key; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'event:20 proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", 'SFTPAuthorizedUserKeys file:~/.authorized_keys', ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key SFTPCiphers $cipher_algo SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo SFTPAuthorizedHostKeys file:~/.authorized_keys EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_publickey($setup->{user}, $rsa_pub_key, $rsa_priv_key)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_ts = [gettimeofday()]; if ($ENV{TEST_VERBOSE}) { print STDERR "Opening SFTP channel...\n"; } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_elapsed = tv_interval($sftp_start_ts); if ($ENV{TEST_VERBOSE}) { print STDERR "Sending SFTP STAT request ($sftp_start_elapsed since SFTP channel opened)...\n"; } my $path = 'proxy.conf'; unless ($sftp->stat($path, 1)) { my ($err_code, $err_name) = $sftp->error(); die("STAT $path failed: [$err_name] ($err_code)"); } if ($ENV{TEST_VERBOSE}) { print STDERR "Closing SFTP channel...\n"; } # To close the SFTP channel, we have to explicitly destroy the object $sftp = undef; if ($ENV{TEST_VERBOSE}) { print STDERR "Disconnecting SSH...\n"; } $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_auth_publickey_useproxyauth_peruser { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyReverseConnectPolicy} = 'PerUser'; $proxy_config->{ProxyOptions} = 'UseReverseProxyAuth'; $proxy_config->{ProxySFTPCiphers} = $cipher_algo; $proxy_config->{ProxySFTPDigests} = $digest_algo; $proxy_config->{ProxySFTPKeyExchanges} = $kex_algo; my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); # For publickey authentication my $rsa_priv_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/test_rsa_key"); my $rsa_pub_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/test_rsa_key.pub"); # For hostbased authentication my $rsa_rfc4716_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/authorized_rsa_keys"); my $authorized_keys = File::Spec->rel2abs("$tmpdir/.authorized_keys"); unless (copy($rsa_rfc4716_key, $authorized_keys)) { die("Can't copy $rsa_rfc4716_key to $authorized_keys: $!"); } unless (chmod(0400, $rsa_priv_key)) { die("Can't set perms on $rsa_priv_key: $!"); } $proxy_config->{ProxySFTPHostKey} = $rsa_priv_key; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'event:20 proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", 'SFTPAuthorizedUserKeys file:~/.authorized_keys', ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key SFTPCiphers $cipher_algo SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo SFTPAuthorizedHostKeys file:~/.authorized_keys EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_publickey($setup->{user}, $rsa_pub_key, $rsa_priv_key)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_ts = [gettimeofday()]; if ($ENV{TEST_VERBOSE}) { print STDERR "Opening SFTP channel...\n"; } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_elapsed = tv_interval($sftp_start_ts); if ($ENV{TEST_VERBOSE}) { print STDERR "Sending SFTP STAT request ($sftp_start_elapsed since SFTP channel opened)...\n"; } my $path = 'proxy.conf'; unless ($sftp->stat($path, 1)) { my ($err_code, $err_name) = $sftp->error(); die("STAT $path failed: [$err_name] ($err_code)"); } if ($ENV{TEST_VERBOSE}) { print STDERR "Closing SFTP channel...\n"; } # To close the SFTP channel, we have to explicitly destroy the object $sftp = undef; if ($ENV{TEST_VERBOSE}) { print STDERR "Disconnecting SSH...\n"; } $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_auth_password { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxySFTPCiphers} = $cipher_algo; $proxy_config->{ProxySFTPDigests} = $digest_algo; $proxy_config->{ProxySFTPKeyExchanges} = $kex_algo; my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key SFTPCiphers $cipher_algo SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_ts = [gettimeofday()]; if ($ENV{TEST_VERBOSE}) { print STDERR "Opening SFTP channel...\n"; } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_elapsed = tv_interval($sftp_start_ts); if ($ENV{TEST_VERBOSE}) { print STDERR "Sending SFTP STAT request ($sftp_start_elapsed since SFTP channel opened)...\n"; } my $path = 'proxy.conf'; unless ($sftp->stat($path, 1)) { my ($err_code, $err_name) = $sftp->error(); die("STAT $path failed: [$err_name] ($err_code)"); } if ($ENV{TEST_VERBOSE}) { print STDERR "Closing SFTP channel...\n"; } # To close the SFTP channel, we have to explicitly destroy the object $sftp = undef; if ($ENV{TEST_VERBOSE}) { print STDERR "Disconnecting SSH...\n"; } $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_auth_password_failed { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxySFTPCiphers} = $cipher_algo; $proxy_config->{ProxySFTPDigests} = $digest_algo; $proxy_config->{ProxySFTPKeyExchanges} = $kex_algo; my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key SFTPCiphers $cipher_algo SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } if ($ssh2->auth_password($setup->{user}, 'BadPassword')) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Successfully authenticated to SSH2 server unexpectedly"); } unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_ts = [gettimeofday()]; if ($ENV{TEST_VERBOSE}) { print STDERR "Opening SFTP channel...\n"; } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_elapsed = tv_interval($sftp_start_ts); if ($ENV{TEST_VERBOSE}) { print STDERR "Sending SFTP STAT request ($sftp_start_elapsed since SFTP channel opened)...\n"; } my $path = 'proxy.conf'; unless ($sftp->stat($path, 1)) { my ($err_code, $err_name) = $sftp->error(); die("STAT $path failed: [$err_name] ($err_code)"); } if ($ENV{TEST_VERBOSE}) { print STDERR "Closing SFTP channel...\n"; } # To close the SFTP channel, we have to explicitly destroy the object $sftp = undef; if ($ENV{TEST_VERBOSE}) { print STDERR "Disconnecting SSH...\n"; } $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_auth_password_with_banner { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $banner_file = File::Spec->rel2abs("$tmpdir/banner.txt"); if (open(my $fh, "> $banner_file")) { print $fh <{log_file}, $vhost_port); $proxy_config->{ProxySFTPCiphers} = $cipher_algo; $proxy_config->{ProxySFTPDigests} = $digest_algo; $proxy_config->{ProxySFTPKeyExchanges} = $kex_algo; my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key SFTPCiphers $cipher_algo SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo SFTPDisplayBanner $banner_file EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } if ($ssh2->auth_password($setup->{user}, 'BadPassword')) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Successfully authenticated to SSH2 server unexpectedly"); } unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_ts = [gettimeofday()]; if ($ENV{TEST_VERBOSE}) { print STDERR "Opening SFTP channel...\n"; } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_elapsed = tv_interval($sftp_start_ts); if ($ENV{TEST_VERBOSE}) { print STDERR "Sending SFTP STAT request ($sftp_start_elapsed since SFTP channel opened)...\n"; } my $path = 'proxy.conf'; unless ($sftp->stat($path, 1)) { my ($err_code, $err_name) = $sftp->error(); die("STAT $path failed: [$err_name] ($err_code)"); } if ($ENV{TEST_VERBOSE}) { print STDERR "Closing SFTP channel...\n"; } # To close the SFTP channel, we have to explicitly destroy the object $sftp = undef; if ($ENV{TEST_VERBOSE}) { print STDERR "Disconnecting SSH...\n"; } $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_auth_password_twice { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxySFTPCiphers} = $cipher_algo; $proxy_config->{ProxySFTPDigests} = $digest_algo; $proxy_config->{ProxySFTPKeyExchanges} = $kex_algo; my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key SFTPCiphers $cipher_algo SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } # Now, authenticate again, to make sure mod_proxy handles this case # properly. # # Since mod_proxy should be ignoring this additional USERAUTH_REQUEST, # we need to time out the libssh2 request before the testcase times out. my $auth_timed_out = 0; eval { local %SIG; $SIG{ALRM} = sub { $auth_timed_out = 1; }; alarm(1); if ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { alarm(0); die("Second login succeeded unexpectedly"); } # Clear the pending alarm alarm(0); }; $self->assert($auth_timed_out, test_msg("Expected timeout out auth request")); my $sftp_start_ts = [gettimeofday()]; if ($ENV{TEST_VERBOSE}) { print STDERR "Opening SFTP channel...\n"; } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_elapsed = tv_interval($sftp_start_ts); if ($ENV{TEST_VERBOSE}) { print STDERR "Sending SFTP STAT request ($sftp_start_elapsed since SFTP channel opened)...\n"; } my $path = 'proxy.conf'; unless ($sftp->stat($path, 1)) { my ($err_code, $err_name) = $sftp->error(); die("STAT $path failed: [$err_name] ($err_code)"); } if ($ENV{TEST_VERBOSE}) { print STDERR "Closing SFTP channel...\n"; } # To close the SFTP channel, we have to explicitly destroy the object $sftp = undef; if ($ENV{TEST_VERBOSE}) { print STDERR "Disconnecting SSH...\n"; } $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_auth_password_rewrite_user { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxySFTPCiphers} = $cipher_algo; $proxy_config->{ProxySFTPDigests} = $digest_algo; $proxy_config->{ProxySFTPKeyExchanges} = $kex_algo; my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_rewrite.c' => [ 'RewriteEngine on', "RewriteLog $setup->{log_file}", 'RewriteMap lowercase int:tolower', 'RewriteCondition %m USER', 'RewriteRule ^(.*)$ ${lowercase:$1}', ], 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key SFTPCiphers $cipher_algo SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password(uc($setup->{user}), $setup->{passwd})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_ts = [gettimeofday()]; if ($ENV{TEST_VERBOSE}) { print STDERR "Opening SFTP channel...\n"; } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_elapsed = tv_interval($sftp_start_ts); if ($ENV{TEST_VERBOSE}) { print STDERR "Sending SFTP STAT request ($sftp_start_elapsed since SFTP channel opened)...\n"; } my $path = 'proxy.conf'; unless ($sftp->stat($path, 1)) { my ($err_code, $err_name) = $sftp->error(); die("STAT $path failed: [$err_name] ($err_code)"); } if ($ENV{TEST_VERBOSE}) { print STDERR "Closing SFTP channel...\n"; } # To close the SFTP channel, we have to explicitly destroy the object $sftp = undef; if ($ENV{TEST_VERBOSE}) { print STDERR "Disconnecting SSH...\n"; } $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_auth_kbdint { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxySFTPCiphers} = $cipher_algo; $proxy_config->{ProxySFTPDigests} = $digest_algo; $proxy_config->{ProxySFTPKeyExchanges} = $kex_algo; my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); # For keyboard-interactive authentication (via OTP) my $db_file = File::Spec->rel2abs("$tmpdir/auth_otp.db"); # Build up sqlite3 command to create HOTP tables my $db_script = File::Spec->rel2abs("$tmpdir/hotp.sql"); # mod_auth_otp wants this secret to be base32-encoded, for interoperability # with Google Authenticator. require MIME::Base32; my $secret = 'Sup3rS3Cr3t'; my $base32_secret = MIME::Base32::encode_base32($secret); my $counter = 777; my $bad_secret = 'B@d1YK3pts3kr3T!'; if (open(my $fh, "> $db_script")) { print $fh <{user}', '$base32_secret', $counter); EOS unless (close($fh)) { die("Can't write $db_script: $!"); } } else { die("Can't open $db_script: $!"); } my $cmd = "sqlite3 $db_file < $db_script"; build_db($cmd, $db_script, $db_file); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'auth:20 auth_otp:20 proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 sql:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_otp.c mod_auth_file.c AllowOverride off WtmpLog off TransferLog none AuthOTPEngine on AuthOTPLog $setup->{log_file} AuthOTPAlgorithm hotp # Assumes default table names, column names AuthOTPTable sql:/get-user-hotp/update-user-hotp DelayEngine off SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key SFTPCiphers $cipher_algo SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo # Configure mod_sftp to only use the keyboard-interactive method. # NOTE: How to handle this when both mod_auth_otp AND mod_sftp_pam # are used/loaded? SFTPAuthMethods keyboard-interactive SQLEngine log SQLBackend sqlite3 SQLConnectInfo $db_file SQLLogFile $setup->{log_file} SQLNamedQuery get-user-hotp SELECT "secret, counter FROM auth_otp WHERE user = '%{0}'" SQLNamedQuery update-user-hotp UPDATE "counter = %{1} WHERE user = '%{0}'" auth_otp EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Authen::OATH; require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } # Calculate HOTP my $oath = Authen::OATH->new(); my $hotp = $oath->hotp($secret, $counter); if ($ENV{TEST_VERBOSE}) { print STDERR "# Generated HOTP $hotp for counter ", $counter, "\n"; } unless ($ssh2->auth_keyboard($setup->{user}, $hotp)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_ts = [gettimeofday()]; if ($ENV{TEST_VERBOSE}) { print STDERR "Opening SFTP channel...\n"; } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_elapsed = tv_interval($sftp_start_ts); if ($ENV{TEST_VERBOSE}) { print STDERR "Sending SFTP STAT request ($sftp_start_elapsed since SFTP channel opened)...\n"; } my $path = 'proxy.conf'; unless ($sftp->stat($path, 1)) { my ($err_code, $err_name) = $sftp->error(); die("STAT $path failed: [$err_name] ($err_code)"); } if ($ENV{TEST_VERBOSE}) { print STDERR "Closing SFTP channel...\n"; } # To close the SFTP channel, we have to explicitly destroy the object $sftp = undef; if ($ENV{TEST_VERBOSE}) { print STDERR "Disconnecting SSH...\n"; } $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_auth_kbdint_failed { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxySFTPCiphers} = $cipher_algo; $proxy_config->{ProxySFTPDigests} = $digest_algo; $proxy_config->{ProxySFTPKeyExchanges} = $kex_algo; my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'auth:20 auth_otp:20 proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 sql:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none AuthOTPEngine on AuthOTPLog $setup->{log_file} AuthOTPAlgorithm hotp # Assumes default table names, column names AuthOTPTable sql:/get-user-hotp/update-user-hotp DelayEngine off SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key SFTPCiphers $cipher_algo SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo # Configure mod_sftp to only use the keyboard-interactive method. # NOTE: How to handle this when both mod_auth_otp AND mod_sftp_pam # are used/loaded? SFTPAuthMethods keyboard-interactive EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Authen::OATH; require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } # Calculate HOTP my $oath = Authen::OATH->new(); my $counter = 1; my $hotp = $oath->hotp('foobar', $counter); if ($ENV{TEST_VERBOSE}) { print STDERR "# Generated HOTP $hotp for counter ", $counter, "\n"; } if ($ssh2->auth_keyboard($setup->{user}, $hotp)) { die("keyboard-interactive login succeeded unexpectedly"); } $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_auth_kbdint_rewrite_user { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxySFTPCiphers} = $cipher_algo; $proxy_config->{ProxySFTPDigests} = $digest_algo; $proxy_config->{ProxySFTPKeyExchanges} = $kex_algo; my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); # For keyboard-interactive authentication (via OTP) my $db_file = File::Spec->rel2abs("$tmpdir/auth_otp.db"); # Build up sqlite3 command to create HOTP tables my $db_script = File::Spec->rel2abs("$tmpdir/hotp.sql"); # mod_auth_otp wants this secret to be base32-encoded, for interoperability # with Google Authenticator. require MIME::Base32; my $secret = 'Sup3rS3Cr3t'; my $base32_secret = MIME::Base32::encode_base32($secret); my $counter = 777; my $bad_secret = 'B@d1YK3pts3kr3T!'; if (open(my $fh, "> $db_script")) { print $fh <{user}', '$base32_secret', $counter); EOS unless (close($fh)) { die("Can't write $db_script: $!"); } } else { die("Can't open $db_script: $!"); } my $cmd = "sqlite3 $db_file < $db_script"; build_db($cmd, $db_script, $db_file); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'auth:20 auth_otp:20 proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 sql:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_rewrite.c' => [ 'RewriteEngine on', "RewriteLog $setup->{log_file}", 'RewriteMap lowercase int:tolower', 'RewriteCondition %m USER', 'RewriteRule ^(.*)$ ${lowercase:$1}', ], 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_otp.c mod_auth_file.c AllowOverride off WtmpLog off TransferLog none AuthOTPEngine on AuthOTPLog $setup->{log_file} AuthOTPAlgorithm hotp # Assumes default table names, column names AuthOTPTable sql:/get-user-hotp/update-user-hotp DelayEngine off SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key SFTPCiphers $cipher_algo SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo # Configure mod_sftp to only use the keyboard-interactive method. # NOTE: How to handle this when both mod_auth_otp AND mod_sftp_pam # are used/loaded? SFTPAuthMethods keyboard-interactive SQLEngine log SQLBackend sqlite3 SQLConnectInfo $db_file SQLLogFile $setup->{log_file} SQLNamedQuery get-user-hotp SELECT "secret, counter FROM auth_otp WHERE user = '%{0}'" SQLNamedQuery update-user-hotp UPDATE "counter = %{1} WHERE user = '%{0}'" auth_otp EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Authen::OATH; require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } # Calculate HOTP my $oath = Authen::OATH->new(); my $hotp = $oath->hotp($secret, $counter); if ($ENV{TEST_VERBOSE}) { print STDERR "# Generated HOTP $hotp for counter ", $counter, "\n"; } unless ($ssh2->auth_keyboard(uc($setup->{user}), $hotp)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_ts = [gettimeofday()]; if ($ENV{TEST_VERBOSE}) { print STDERR "Opening SFTP channel...\n"; } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_elapsed = tv_interval($sftp_start_ts); if ($ENV{TEST_VERBOSE}) { print STDERR "Sending SFTP STAT request ($sftp_start_elapsed since SFTP channel opened)...\n"; } my $path = 'proxy.conf'; unless ($sftp->stat($path, 1)) { my ($err_code, $err_name) = $sftp->error(); die("STAT $path failed: [$err_name] ($err_code)"); } if ($ENV{TEST_VERBOSE}) { print STDERR "Closing SFTP channel...\n"; } # To close the SFTP channel, we have to explicitly destroy the object $sftp = undef; if ($ENV{TEST_VERBOSE}) { print STDERR "Disconnecting SSH...\n"; } $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_auth_chain_password_kbdint { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxySFTPCiphers} = $cipher_algo; $proxy_config->{ProxySFTPDigests} = $digest_algo; $proxy_config->{ProxySFTPKeyExchanges} = $kex_algo; my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); # For keyboard-interactive authentication (via OTP) my $db_file = File::Spec->rel2abs("$tmpdir/auth_otp.db"); # Build up sqlite3 command to create HOTP tables my $db_script = File::Spec->rel2abs("$tmpdir/hotp.sql"); # mod_auth_otp wants this secret to be base32-encoded, for interoperability # with Google Authenticator. require MIME::Base32; my $secret = 'Sup3rS3Cr3t'; my $base32_secret = MIME::Base32::encode_base32($secret); my $counter = 777; my $bad_secret = 'B@d1YK3pts3kr3T!'; if (open(my $fh, "> $db_script")) { print $fh <{user}', '$base32_secret', $counter); EOS unless (close($fh)) { die("Can't write $db_script: $!"); } } else { die("Can't open $db_script: $!"); } my $cmd = "sqlite3 $db_file < $db_script"; build_db($cmd, $db_script, $db_file); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'auth:20 auth_otp:20 proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 sql:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_otp.c mod_auth_file.c AllowOverride off WtmpLog off TransferLog none AuthOTPEngine on AuthOTPLog $setup->{log_file} AuthOTPAlgorithm hotp # Assumes default table names, column names AuthOTPTable sql:/get-user-hotp/update-user-hotp DelayEngine off SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key SFTPCiphers $cipher_algo SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo # Configure mod_sftp to require both password and keyboard-interactive # methods. SFTPAuthMethods password+keyboard-interactive SQLEngine log SQLBackend sqlite3 SQLConnectInfo $db_file SQLLogFile $setup->{log_file} SQLNamedQuery get-user-hotp SELECT "secret, counter FROM auth_otp WHERE user = '%{0}'" SQLNamedQuery update-user-hotp UPDATE "counter = %{1} WHERE user = '%{0}'" auth_otp EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Authen::OATH; require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } if ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { die("Login succeeded unexpectedly with just password authentication"); } # Calculate HOTP my $oath = Authen::OATH->new(); my $hotp = $oath->hotp($secret, $counter); if ($ENV{TEST_VERBOSE}) { print STDERR "# Generated HOTP $hotp for counter ", $counter, "\n"; } unless ($ssh2->auth_keyboard($setup->{user}, $hotp)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_ts = [gettimeofday()]; if ($ENV{TEST_VERBOSE}) { print STDERR "Opening SFTP channel...\n"; } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_elapsed = tv_interval($sftp_start_ts); if ($ENV{TEST_VERBOSE}) { print STDERR "Sending SFTP STAT request ($sftp_start_elapsed since SFTP channel opened)...\n"; } my $path = 'proxy.conf'; unless ($sftp->stat($path, 1)) { my ($err_code, $err_name) = $sftp->error(); die("STAT $path failed: [$err_name] ($err_code)"); } if ($ENV{TEST_VERBOSE}) { print STDERR "Closing SFTP channel...\n"; } # To close the SFTP channel, we have to explicitly destroy the object $sftp = undef; if ($ENV{TEST_VERBOSE}) { print STDERR "Disconnecting SSH...\n"; } $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_auth_chain_publickey_kbdint { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxySFTPCiphers} = $cipher_algo; $proxy_config->{ProxySFTPDigests} = $digest_algo; $proxy_config->{ProxySFTPKeyExchanges} = $kex_algo; my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); # For publickey authentication my $rsa_priv_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/test_rsa_key"); my $rsa_pub_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/test_rsa_key.pub"); # For hostbased authentication my $rsa_rfc4716_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/authorized_rsa_keys"); my $authorized_keys = File::Spec->rel2abs("$tmpdir/.authorized_keys"); unless (copy($rsa_rfc4716_key, $authorized_keys)) { die("Can't copy $rsa_rfc4716_key to $authorized_keys: $!"); } unless (chmod(0400, $rsa_priv_key)) { die("Can't set perms on $rsa_priv_key: $!"); } $proxy_config->{ProxySFTPHostKey} = $rsa_priv_key; # For keyboard-interactive authentication (via OTP) my $db_file = File::Spec->rel2abs("$tmpdir/auth_otp.db"); # Build up sqlite3 command to create HOTP tables my $db_script = File::Spec->rel2abs("$tmpdir/hotp.sql"); # mod_auth_otp wants this secret to be base32-encoded, for interoperability # with Google Authenticator. require MIME::Base32; my $secret = 'Sup3rS3Cr3t'; my $base32_secret = MIME::Base32::encode_base32($secret); my $counter = 777; my $bad_secret = 'B@d1YK3pts3kr3T!'; if (open(my $fh, "> $db_script")) { print $fh <{user}', '$base32_secret', $counter); EOS unless (close($fh)) { die("Can't write $db_script: $!"); } } else { die("Can't open $db_script: $!"); } my $cmd = "sqlite3 $db_file < $db_script"; build_db($cmd, $db_script, $db_file); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'auth:20 auth_otp:20 proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 sql:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_otp.c mod_auth_file.c AllowOverride off WtmpLog off TransferLog none AuthOTPEngine on AuthOTPLog $setup->{log_file} AuthOTPAlgorithm hotp # Assumes default table names, column names AuthOTPTable sql:/get-user-hotp/update-user-hotp DelayEngine off SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key SFTPCiphers $cipher_algo SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo SFTPAuthorizedHostKeys file:~/.authorized_keys # Configure mod_sftp to require both hostbased and keyboard-interactive # methods. SFTPAuthMethods hostbased+keyboard-interactive SQLEngine log SQLBackend sqlite3 SQLConnectInfo $db_file SQLLogFile $setup->{log_file} SQLNamedQuery get-user-hotp SELECT "secret, counter FROM auth_otp WHERE user = '%{0}'" SQLNamedQuery update-user-hotp UPDATE "counter = %{1} WHERE user = '%{0}'" auth_otp EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Authen::OATH; require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } if ($ssh2->auth_publickey($setup->{user}, $rsa_pub_key, $rsa_priv_key)) { die("Login succeeded unexpectedly with just publickey authentication"); } # Calculate HOTP my $oath = Authen::OATH->new(); my $hotp = $oath->hotp($secret, $counter); if ($ENV{TEST_VERBOSE}) { print STDERR "# Generated HOTP $hotp for counter ", $counter, "\n"; } unless ($ssh2->auth_keyboard($setup->{user}, $hotp)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_ts = [gettimeofday()]; if ($ENV{TEST_VERBOSE}) { print STDERR "Opening SFTP channel...\n"; } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_elapsed = tv_interval($sftp_start_ts); if ($ENV{TEST_VERBOSE}) { print STDERR "Sending SFTP STAT request ($sftp_start_elapsed since SFTP channel opened)...\n"; } my $path = 'proxy.conf'; unless ($sftp->stat($path, 1)) { my ($err_code, $err_name) = $sftp->error(); die("STAT $path failed: [$err_name] ($err_code)"); } if ($ENV{TEST_VERBOSE}) { print STDERR "Closing SFTP channel...\n"; } # To close the SFTP channel, we have to explicitly destroy the object $sftp = undef; if ($ENV{TEST_VERBOSE}) { print STDERR "Disconnecting SSH...\n"; } $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_verify_server_off { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxySFTPCiphers} = $cipher_algo; $proxy_config->{ProxySFTPDigests} = $digest_algo; $proxy_config->{ProxySFTPKeyExchanges} = $kex_algo; $proxy_config->{ProxySFTPVerifyServer} = 'off'; my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'proxy:20 proxy.db:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.db:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key SFTPCiphers $cipher_algo SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } $ssh2->disconnect(); # We now connect again, to check the just-stored hostkey. $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $setup->{log_file}")) { my $no_existing = 0; my $hostkey_matches = 0; while (my $line = <$fh>) { chomp($line); if ($ENV{TEST_VERBOSE}) { print STDERR "# $line\n"; } if ($line =~ /no existing hostkey stored for vhost ID/) { $no_existing = 1; } if ($line =~ /stored hostkey matches current hostkey for vhost/) { $hostkey_matches = 1; last; } } close($fh); $self->assert($no_existing, test_msg("Did not see expected TraceLog messages about no existing stored hostkey")); $self->assert($hostkey_matches, test_msg("Did not see expected TraceLog messages about stored hostkey matching")); } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_verify_server_on { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxySFTPCiphers} = $cipher_algo; $proxy_config->{ProxySFTPDigests} = $digest_algo; $proxy_config->{ProxySFTPKeyExchanges} = $kex_algo; $proxy_config->{ProxySFTPVerifyServer} = 'on'; my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); # We force a hostkey mismatch by manually creating the expected 'proxy-ssh.db' # SQLite database, populated with a hostkey entry that will not match. # # NOTE: Try to keep the schema_version here in sync with the code! my $table_dir = $proxy_config->{ProxyTables}; mkpath($table_dir); my $db_file = File::Spec->rel2abs("$table_dir/proxy-ssh.db"); if ($ENV{TEST_VERBOSE}) { print STDERR "# Fiddling with SQLite database file $db_file\n"; } my $db_script = File::Spec->rel2abs("$tmpdir/proxy-ssh.sql"); if (open(my $fh, "> $db_script")) { print $fh <{ProxyReverseServers}', 'ssh-ed25519', X'4E6F74415265616C4B6579'); EOS unless (close($fh)) { die("Can't write $db_script: $!"); } } else { die("Can't open $db_script: $!"); } my $cmd = "sqlite3 -batch -echo $db_file < $db_script"; build_db($cmd, $db_script, $db_file); unlink($db_script); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'proxy:20 proxy.db:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.db:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key SFTPCiphers $cipher_algo SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $setup->{log_file}")) { my $have_existing = 0; my $hostkey_mismatch = 0; while (my $line = <$fh>) { chomp($line); if ($ENV{TEST_VERBOSE}) { print STDERR "# $line\n"; } if ($line =~ /found stored .*? for vhost ID/) { $have_existing = 1; } if ($line =~ /stored hostkey does not match current hostkey .*? ProxySFTPVerifyServer is enabled/) { $hostkey_mismatch = 1; last; } } close($fh); $self->assert($have_existing, test_msg("Did not see expected TraceLog messages about existing stored hostkey")); $self->assert($hostkey_mismatch, test_msg("Did not see expected TraceLog messages about stored hostkey mismatch")); } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_sftp_without_auth { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp = $ssh2->sftp(); if ($sftp) { die("Started SFTP channel unexpectedly"); } my ($err_code, $err_name, $err_str) = $ssh2->error(); my $expected; # The expected error messages depend on the version of libssh2 being # used. $self->assert($err_name eq 'LIBSSH2_ERROR_INVAL' or $err_name eq 'LIBSSH2_ERROR_CHANNEL_FAILURE', test_msg("Expected 'LIBSSH2_ERROR_INVAL' or 'LIBSSH2_ERROR_CHANNEL_FAILURE', got '$err_name'")); $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_sftp_stat { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_ts = [gettimeofday()]; if ($ENV{TEST_VERBOSE}) { print STDERR "Opening SFTP channel...\n"; } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_elapsed = tv_interval($sftp_start_ts); if ($ENV{TEST_VERBOSE}) { print STDERR "Sending SFTP STAT request ($sftp_start_elapsed since SFTP channel opened)...\n"; } my $path = 'proxy.conf'; unless ($sftp->stat($path, 1)) { my ($err_code, $err_name) = $sftp->error(); die("STAT $path failed: [$err_name] ($err_code)"); } if ($ENV{TEST_VERBOSE}) { print STDERR "Closing SFTP channel...\n"; } # To close the SFTP channel, we have to explicitly destroy the object $sftp = undef; if ($ENV{TEST_VERBOSE}) { print STDERR "Disconnecting SSH...\n"; } $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_sftp_upload { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $test_file = File::Spec->rel2abs("$tmpdir/test.dat"); if (open(my $fh, "> $test_file")) { # Make a file that's larger than the maximum SSH2 packet size, forcing # the sftp code to loop properly until the entire large file is sent. print $fh "ABCDefgh" x 16384; unless (close($fh)) { die("Can't write $test_file: $!"); } } else { die("Can't open $test_file: $!"); } # Calculate the MD5 checksum of this file, for comparison with the # downloaded file. my $ctx = Digest::MD5->new(); my $expected_md5; if (open(my $fh, "< $test_file")) { binmode($fh); $ctx->addfile($fh); $expected_md5 = $ctx->hexdigest(); close($fh); } else { die("Can't read $test_file: $!"); } my $test_file2 = File::Spec->rel2abs("$tmpdir/test2.dat"); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { my $test_rfh; unless (open($test_rfh, "< $test_file")) { die("Can't read $test_file: $!"); } # Give the server a chance to start up sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_ts = [gettimeofday()]; if ($ENV{TEST_VERBOSE}) { print STDERR "Opening SFTP channel...\n"; } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_elapsed = tv_interval($sftp_start_ts); if ($ENV{TEST_VERBOSE}) { print STDERR "Sending SFTP OPEN request ($sftp_start_elapsed since SFTP channel opened)...\n"; } my $test_wfh = $sftp->open('test2.dat', O_WRONLY|O_CREAT|O_TRUNC, 0644); unless ($test_wfh) { my ($err_code, $err_name) = $sftp->error(); die("Can't open test2.dat: [$err_name] ($err_code)"); } my $buf; my $bufsz = 8192; while (read($test_rfh, $buf, $bufsz)) { print $test_wfh $buf; } close($test_rfh); # To issue the FXP_CLOSE, we have to explicitly destroy the filehandle $test_wfh = undef; if ($ENV{TEST_VERBOSE}) { print STDERR "Closing SFTP channel...\n"; } # To close the SFTP channel, we have to explicitly destroy the object $sftp = undef; if ($ENV{TEST_VERBOSE}) { print STDERR "Disconnecting SSH...\n"; } $ssh2->disconnect(); unless (-f $test_file2) { die("$test_file2 file does not exist as expected"); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { # Calculate the MD5 checksum of the uploaded file, for comparison with the # file that was uploaded. $ctx->reset(); my $md5; if (open(my $fh, "< $test_file2")) { binmode($fh); $ctx->addfile($fh); $md5 = $ctx->hexdigest(); close($fh); } else { die("Can't read $test_file2: $!"); } $self->assert($expected_md5 eq $md5, test_msg("Expected '$expected_md5', got '$md5'")); }; if ($@) { $ex = $@; } test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_sftp_download { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $test_file = File::Spec->rel2abs("$tmpdir/test.dat"); if (open(my $fh, "> $test_file")) { # Make a file that's larger than the maximum SSH2 packet size, forcing # the sftp code to loop properly until the entire large file is sent. print $fh "ABCDefgh" x 16384; unless (close($fh)) { die("Can't write $test_file: $!"); } } else { die("Can't open $test_file: $!"); } # Calculate the MD5 checksum of this file, for comparison with the # downloaded file. my $ctx = Digest::MD5->new(); my $expected_md5; if (open(my $fh, "< $test_file")) { binmode($fh); $ctx->addfile($fh); $expected_md5 = $ctx->hexdigest(); close($fh); } else { die("Can't read $test_file: $!"); } my $test_file2 = File::Spec->rel2abs("$tmpdir/test2.dat"); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { my $test_wfh; unless (open($test_wfh, "> $test_file2")) { die("Can't read $test_file2: $!"); } binmode($test_wfh); # Give the server a chance to start up sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_ts = [gettimeofday()]; if ($ENV{TEST_VERBOSE}) { print STDERR "Opening SFTP channel...\n"; } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_elapsed = tv_interval($sftp_start_ts); if ($ENV{TEST_VERBOSE}) { print STDERR "Sending SFTP OPEN request ($sftp_start_elapsed since SFTP channel opened)...\n"; } my $test_rfh = $sftp->open('test.dat', O_RDONLY); unless ($test_rfh) { my ($err_code, $err_name) = $sftp->error(); die("Can't open test.dat: [$err_name] ($err_code)"); } my $buf; my $bufsz = 8192; my $res = $test_rfh->read($buf, $bufsz); while ($res) { print $test_wfh $buf; $res = $test_rfh->read($buf, $bufsz); } unless (close($test_wfh)) { die("Can't write $test_file2: $!"); } # To issue the FXP_CLOSE, we have to explicitly destroy the filehandle $test_rfh = undef; if ($ENV{TEST_VERBOSE}) { print STDERR "Closing SFTP channel...\n"; } # To close the SFTP channel, we have to explicitly destroy the object $sftp = undef; if ($ENV{TEST_VERBOSE}) { print STDERR "Disconnecting SSH...\n"; } $ssh2->disconnect(); unless (-f $test_file2) { die("$test_file2 file does not exist as expected"); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { # Calculate the MD5 checksum of the uploaded file, for comparison with the # file that was uploaded. $ctx->reset(); my $md5; if (open(my $fh, "< $test_file2")) { binmode($fh); $ctx->addfile($fh); $md5 = $ctx->hexdigest(); close($fh); } else { die("Can't read $test_file2: $!"); } $self->assert($expected_md5 eq $md5, test_msg("Expected '$expected_md5', got '$md5'")); }; if ($@) { $ex = $@; } test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_sftp_readdir { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_ts = [gettimeofday()]; if ($ENV{TEST_VERBOSE}) { print STDERR "Opening SFTP channel...\n"; } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_elapsed = tv_interval($sftp_start_ts); if ($ENV{TEST_VERBOSE}) { print STDERR "Sending SFTP OPENDIR request ($sftp_start_elapsed since SFTP channel opened)...\n"; } my $dirh = $sftp->opendir('.'); unless ($dirh) { my ($err_code, $err_name) = $sftp->error(); die("Can't open directory '.': [$err_name] ($err_code)"); } my $res = {}; my $file = $dirh->read(); while ($file) { $res->{$file->{name}} = $file; $file = $dirh->read(); } # To issue the FXP_CLOSE, we have to explicitly destroy the dirhandle $dirh = undef; if ($ENV{TEST_VERBOSE}) { print STDERR "Closing SFTP channel...\n"; } # To close the SFTP channel, we have to explicitly destroy the object $sftp = undef; if ($ENV{TEST_VERBOSE}) { print STDERR "Disconnecting SSH...\n"; } $ssh2->disconnect(); my $expected = { '.' => 1, '..' => 1, 'proxy.conf' => 1, 'proxy.group' => 1, 'proxy.passwd' => 1, 'proxy.pid' => 1, 'proxy.scoreboard' => 1, 'proxy.scoreboard.lck' => 1, 'var' => 1, }; my $ok = 1; my $mismatch; my $seen = []; foreach my $name (keys(%$res)) { push(@$seen, $name); unless (defined($expected->{$name})) { $mismatch = $name; $ok = 0; last; } } unless ($ok) { die("Unexpected name '$mismatch' appeared in READDIR data") } # Now remove from $expected all of the paths we saw; if there are # any entries remaining in $expected, something went wrong. foreach my $name (@$seen) { delete($expected->{$name}); } my $remaining = scalar(keys(%$expected)); $self->assert(0 == $remaining, test_msg("Expected 0, got $remaining")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_scp_upload { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $test_file = File::Spec->rel2abs("$tmpdir/test.dat"); if (open(my $fh, "> $test_file")) { # Make a file that's larger than the maximum SSH2 packet size, forcing # the sftp code to loop properly until the entire large file is sent. print $fh "ABCDefgh" x 16384; unless (close($fh)) { die("Can't write $test_file: $!"); } } else { die("Can't open $test_file: $!"); } # Calculate the MD5 checksum of this file, for comparison with the # downloaded file. my $ctx = Digest::MD5->new(); my $expected_md5; if (open(my $fh, "< $test_file")) { binmode($fh); $ctx->addfile($fh); $expected_md5 = $ctx->hexdigest(); close($fh); } else { die("Can't read $test_file: $!"); } my $test_file2 = File::Spec->rel2abs("$tmpdir/test2.dat"); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 ssh2:20 sftp:20 scp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $res = $ssh2->scp_put($test_file, 'test2.dat'); unless ($res) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't upload 'test.dat' to server: [$err_name] ($err_code) $err_str"); } $ssh2->disconnect(); unless (-f $test_file2) { die("$test_file2 file does not exist as expected"); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { # Calculate the MD5 checksum of the uploaded file, for comparison with the # file that was uploaded. $ctx->reset(); my $md5; if (open(my $fh, "< $test_file2")) { binmode($fh); $ctx->addfile($fh); $md5 = $ctx->hexdigest(); close($fh); } else { die("Can't read $test_file2: $!"); } $self->assert($expected_md5 eq $md5, test_msg("Expected '$expected_md5', got '$md5'")); }; if ($@) { $ex = $@; } test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_scp_download { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $test_file = File::Spec->rel2abs("$tmpdir/test.dat"); if (open(my $fh, "> $test_file")) { # Make a file that's larger than the maximum SSH2 packet size, forcing # the sftp code to loop properly until the entire large file is sent. print $fh "ABCDefgh" x 16384; unless (close($fh)) { die("Can't write $test_file: $!"); } } else { die("Can't open $test_file: $!"); } # Calculate the MD5 checksum of this file, for comparison with the # downloaded file. my $ctx = Digest::MD5->new(); my $expected_md5; if (open(my $fh, "< $test_file")) { binmode($fh); $ctx->addfile($fh); $expected_md5 = $ctx->hexdigest(); close($fh); } else { die("Can't read $test_file: $!"); } my $test_file2 = File::Spec->rel2abs("$tmpdir/test2.dat"); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'response:25 proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 ssh2:20 sftp:20 scp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $res = $ssh2->scp_get('test.dat', $test_file2); unless ($res) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't download 'test.dat' from server: [$err_name] ($err_code) $err_str"); } $ssh2->disconnect(); unless (-f $test_file2) { die("$test_file2 file does not exist as expected"); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { # Calculate the MD5 checksum of the uploaded file, for comparison with the # file that was uploaded. $ctx->reset(); my $md5; if (open(my $fh, "< $test_file2")) { binmode($fh); $ctx->addfile($fh); $md5 = $ctx->hexdigest(); close($fh); } else { die("Can't read $test_file2: $!"); } $self->assert($expected_md5 eq $md5, test_msg("Expected '$expected_md5', got '$md5'")); }; if ($@) { $ex = $@; } test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_server_rekey_kex_dh_group1_sha1 { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group1-sha1'; my $proxy_sftp_opts = 'AllowWeakDH'; ssh_rekey_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_server_rekey_kex_dh_group1_sha1_zlib_openssh { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $comp_algo = 'delayed'; my $kex_algo = 'diffie-hellman-group1-sha1'; my $proxy_sftp_opts = 'AllowWeakDH'; ssh_rekey_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts, undef, $comp_algo); } sub proxy_reverse_backend_ssh_server_rekey_kex_dh_group14_sha1 { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_sftp_opts = ''; ssh_rekey_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_server_rekey_kex_dh_group14_sha256 { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha256'; my $proxy_sftp_opts = ''; ssh_rekey_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_server_rekey_kex_dh_group16_sha512 { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group16-sha512'; my $proxy_sftp_opts = ''; ssh_rekey_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_server_rekey_kex_dh_group18_sha512 { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group18-sha512'; my $proxy_sftp_opts = ''; ssh_rekey_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_server_rekey_kex_dh_gex_sha1 { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group-exchange-sha1'; my $proxy_sftp_opts = ''; ssh_rekey_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_server_rekey_kex_dh_gex_sha256 { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group-exchange-sha256'; my $proxy_sftp_opts = ''; ssh_rekey_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_server_rekey_kex_ecdh_sha2_nistp256 { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'ecdh-sha2-nistp256'; my $proxy_sftp_opts = ''; ssh_rekey_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_server_rekey_kex_ecdh_sha2_nistp384 { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'ecdh-sha2-nistp384'; my $proxy_sftp_opts = ''; ssh_rekey_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_server_rekey_kex_ecdh_sha2_nistp521 { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'ecdh-sha2-nistp521'; my $proxy_sftp_opts = ''; ssh_rekey_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_server_rekey_kex_rsa1024_sha1 { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'rsa1024-sha1'; my $proxy_sftp_opts = ''; ssh_rekey_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_server_rekey_kex_curve25519_sha256 { my $self = shift; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'curve25519-sha256'; my $proxy_sftp_opts = ''; ssh_rekey_with_algos($self, $cipher_algo, $digest_algo, $kex_algo, $proxy_sftp_opts); } sub proxy_reverse_backend_ssh_ext_client_rekey { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxySFTPCiphers} = $cipher_algo; $proxy_config->{ProxySFTPDigests} = $digest_algo; $proxy_config->{ProxySFTPKeyExchanges} = $kex_algo; my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); # For publickey authentication my $rsa_priv_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/test_rsa_key"); # For hostbased authentication my $rsa_rfc4716_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/authorized_rsa_keys"); my $authorized_keys = File::Spec->rel2abs("$tmpdir/.authorized_keys"); unless (copy($rsa_rfc4716_key, $authorized_keys)) { die("Can't copy $rsa_rfc4716_key to $authorized_keys: $!"); } unless (chmod(0400, $rsa_priv_key)) { die("Can't set perms on $rsa_priv_key: $!"); } $proxy_config->{ProxySFTPHostKey} = $rsa_priv_key; my $src_file = File::Spec->rel2abs("$tmpdir/test.dat"); if (open(my $fh, "> $src_file")) { # Make a file that's larger than the maximum SSH2 packet size, forcing # the sftp code to loop properly until the entire large file is sent. print $fh "ABCDefgh" x 262144; unless (close($fh)) { die("Can't write $src_file: $!"); } # Make sure that, if we're running as root, that the test file has # permissions/privs set for the account we create if ($< == 0) { unless (chown($setup->{uid}, $setup->{gid}, $src_file)) { die("Can't set owner of $src_file to $setup->{uid}/$setup->{gid}: $!"); } } } else { die("Can't open $src_file: $!"); } # Calculate the MD5 checksum of this file, for comparison with the downloaded # file. my $ctx = Digest::MD5->new(); my $expected_md5; if (open(my $fh, "< $src_file")) { binmode($fh); $ctx->addfile($fh); $expected_md5 = $ctx->hexdigest(); close($fh); } else { die("Can't read $src_file: $!"); } my $dst_file = File::Spec->rel2abs("$tmpdir/test2.dat"); my $batch_file = File::Spec->rel2abs("$tmpdir/sftp-batch.txt"); if (open(my $fh, "> $batch_file")) { print $fh "get -P $src_file $dst_file\n"; unless (close($fh)) { die("Can't write $batch_file: $!"); } } else { die("Can't open $batch_file: $!"); } my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key SFTPCiphers $cipher_algo SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo SFTPAuthorizedHostKeys file:~/.authorized_keys EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $sftp = get_sftp_bin(); my @cmd = ( $sftp, '-oBatchMode=yes', '-oCheckHostIP=no', '-oRekeyLimit=1M', "-oPort=$port", "-oIdentityFile=$rsa_priv_key", '-oPubkeyAuthentication=yes', '-oStrictHostKeyChecking=no', '-oUserKnownHostsFile=/dev/null', '-vvv', '-b', "$batch_file", "$setup->{user}\@127.0.0.1", ); my $sftp_rh = IO::Handle->new(); my $sftp_wh = IO::Handle->new(); my $sftp_eh = IO::Handle->new(); $sftp_wh->autoflush(1); local $SIG{CHLD} = 'DEFAULT'; if ($ENV{TEST_VERBOSE}) { print STDERR "Executing: ", join(' ', @cmd), "\n"; } my $sftp_pid = open3($sftp_wh, $sftp_rh, $sftp_eh, @cmd); waitpid($sftp_pid, 0); my $exit_status = $?; # Restore the perms on the priv key unless (chmod(0644, $rsa_priv_key)) { die("Can't set perms on $rsa_priv_key to 0644: $!"); } my $res; my $errstr = ''; if ($exit_status >> 8 == 0) { $errstr = join('', <$sftp_eh>); $res = 0; } else { if ($ENV{TEST_VERBOSE}) { $errstr = join('', <$sftp_eh>); print STDERR "Stderr: $errstr\n"; } $res = 1; } unless ($res == 0) { die("Can't download $src_file from server: $errstr"); } unless (-f $dst_file) { die("File '$dst_file' does not exist as expected"); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { $ctx->reset(); my $md5; if (open(my $fh, "< $dst_file")) { binmode($fh); $ctx->addfile($fh); $md5 = $ctx->hexdigest(); close($fh); } else { die("Can't read $dst_file: $!"); } $self->assert($expected_md5 eq $md5, test_msg("Expected MD5 '$expected_md5', got '$md5'")); }; if ($@) { $ex = $@; } test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_sighup { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxySFTPCiphers} = $cipher_algo; $proxy_config->{ProxySFTPDigests} = $digest_algo; $proxy_config->{ProxySFTPKeyExchanges} = $kex_algo; my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key SFTPCiphers $cipher_algo SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # First, start the server. server_start($setup->{config_file}); # Give it a second to start up, then send the SIGHUP signal sleep(2); server_restart($setup->{pid_file}); # Give it another second to start up again sleep(2); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_ts = [gettimeofday()]; if ($ENV{TEST_VERBOSE}) { print STDERR "Opening SFTP channel...\n"; } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_elapsed = tv_interval($sftp_start_ts); if ($ENV{TEST_VERBOSE}) { print STDERR "Sending SFTP STAT request ($sftp_start_elapsed since SFTP channel opened)...\n"; } my $path = 'proxy.conf'; unless ($sftp->stat($path, 1)) { my ($err_code, $err_name) = $sftp->error(); die("STAT $path failed: [$err_name] ($err_code)"); } if ($ENV{TEST_VERBOSE}) { print STDERR "Closing SFTP channel...\n"; } # To close the SFTP channel, we have to explicitly destroy the object $sftp = undef; if ($ENV{TEST_VERBOSE}) { print STDERR "Disconnecting SSH...\n"; } $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_extlog { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $extlog_file = File::Spec->rel2abs("$tmpdir/ext.log"); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', LogFormat => 'fmt "%m %J"', ExtendedLog => "$extlog_file ALL fmt", IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_ts = [gettimeofday()]; if ($ENV{TEST_VERBOSE}) { print STDERR "Opening SFTP channel...\n"; } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_elapsed = tv_interval($sftp_start_ts); if ($ENV{TEST_VERBOSE}) { print STDERR "Sending SFTP OPENDIR request ($sftp_start_elapsed since SFTP channel opened)...\n"; } my $dirh = $sftp->opendir('.'); unless ($dirh) { my ($err_code, $err_name) = $sftp->error(); die("Can't open directory '.': [$err_name] ($err_code)"); } my $res = {}; my $file = $dirh->read(); while ($file) { $res->{$file->{name}} = $file; $file = $dirh->read(); } # To issue the FXP_CLOSE, we have to explicitly destroy the dirhandle $dirh = undef; if ($ENV{TEST_VERBOSE}) { print STDERR "Closing SFTP channel...\n"; } # To close the SFTP channel, we have to explicitly destroy the object $sftp = undef; if ($ENV{TEST_VERBOSE}) { print STDERR "Disconnecting SSH...\n"; } $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); if ($ex) { test_cleanup($setup->{log_file}, $ex); die($ex); } eval { if (open(my $fh, "< $extlog_file")) { my $saw_kexinit = 0; my $saw_newkeys = 0; my $saw_service_request = 0; my $saw_service_accept = 0; my $saw_userauth_request = 0; my $saw_userauth_success = 0; my $saw_user = 0; my $saw_pass = 0; my $saw_channel_success = 0; my $saw_channel_data = 0; my $saw_channel_eof = 0; while (my $line = <$fh>) { chomp($line); if ($ENV{TEST_VERBOSE}) { print STDERR "# $line\n"; } if ($line =~ /^KEXINIT/) { $saw_kexinit = 1; next; } if ($line =~ /^NEWKEYS/) { $saw_newkeys = 1; next; } if ($line =~ /^SERVICE_REQUEST/) { $saw_service_request = 1; next; } if ($line =~ /^SERVICE_ACCEPT/) { $saw_service_accept = 1; next; } if ($line =~ /^USERAUTH_REQUEST/) { $saw_userauth_request = 1; next; } if ($line =~ /^USERAUTH_SUCCESS/) { $saw_userauth_success = 1; next; } if ($line =~ /^USER/) { $saw_user = 1; next; } if ($line =~ /^PASS/) { $saw_pass = 1; next; } if ($line =~ /^CHANNEL_SUCCESS/) { $saw_channel_success = 1; next; } if ($line =~ /^CHANNEL_DATA/) { $saw_channel_data = 1; next; } if ($line =~ /^CHANNEL_EOF/) { $saw_channel_eof = 1; last; } } close($fh); $self->assert($saw_kexinit, test_msg("Did not see expected ExtendedLog KEXINIT message")); $self->assert($saw_newkeys, test_msg("Did not see expected ExtendedLog NEWKEYS message")); $self->assert($saw_userauth_request, test_msg("Did not see expected ExtendedLog USERAUTH_REQUEST message")); $self->assert($saw_userauth_success, test_msg("Did not see expected ExtendedLog USERAUTH_SUCCESS message")); $self->assert($saw_user, test_msg("Did not see expected ExtendedLog USER message")); $self->assert($saw_pass, test_msg("Did not see expected ExtendedLog PASS message")); $self->assert($saw_channel_success, test_msg("Did not see expected ExtendedLog CHANNEL_SUCCESS message")); $self->assert($saw_channel_data, test_msg("Did not see expected ExtendedLog CHANNEL_DATA message")); $self->assert($saw_channel_eof, test_msg("Did not see expected ExtendedLog CHANNEL_EOF message")); } else { die("Can't read $extlog_file: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_connect_policy_per_host { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $vhost_port2 = $vhost_port - 7; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyReverseConnectPolicy} = 'PerHost'; $proxy_config->{ProxySFTPCiphers} = $cipher_algo; $proxy_config->{ProxySFTPDigests} = $digest_algo; $proxy_config->{ProxySFTPKeyExchanges} = $kex_algo; $proxy_config->{ProxyReverseServers} = "sftp://127.0.0.1:$vhost_port sftp://127.0.0.1:$vhost_port2", my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key SFTPCiphers $cipher_algo SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo Port $vhost_port2 ServerName "Other Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key SFTPCiphers $cipher_algo SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); for (my $i = 0; $i < 3; $i++) { my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_ts = [gettimeofday()]; if ($ENV{TEST_VERBOSE}) { print STDERR "Opening SFTP channel...\n"; } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_elapsed = tv_interval($sftp_start_ts); if ($ENV{TEST_VERBOSE}) { print STDERR "Sending SFTP STAT request ($sftp_start_elapsed since SFTP channel opened)...\n"; } my $path = 'proxy.conf'; unless ($sftp->stat($path, 1)) { my ($err_code, $err_name) = $sftp->error(); die("STAT $path failed: [$err_name] ($err_code)"); } if ($ENV{TEST_VERBOSE}) { print STDERR "Closing SFTP channel...\n"; } # To close the SFTP channel, we have to explicitly destroy the object $sftp = undef; if ($ENV{TEST_VERBOSE}) { print STDERR "Disconnecting SSH...\n"; } $ssh2->disconnect(); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_connect_policy_per_user { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $vhost_port2 = $vhost_port - 7; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyReverseConnectPolicy} = 'PerUser'; $proxy_config->{ProxySFTPCiphers} = $cipher_algo; $proxy_config->{ProxySFTPDigests} = $digest_algo; $proxy_config->{ProxySFTPKeyExchanges} = $kex_algo; $proxy_config->{ProxyReverseServers} = "sftp://127.0.0.1:$vhost_port sftp://127.0.0.1:$vhost_port2", my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); # For hostbased authentication my $rsa_priv_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/test_rsa_key"); my $rsa_pub_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/test_rsa_key.pub"); my $rsa_rfc4716_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/authorized_rsa_keys"); my $authorized_keys = File::Spec->rel2abs("$tmpdir/.authorized_keys"); unless (copy($rsa_rfc4716_key, $authorized_keys)) { die("Can't copy $rsa_rfc4716_key to $authorized_keys: $!"); } unless (chmod(0400, $rsa_priv_key)) { die("Can't set perms on $rsa_priv_key: $!"); } $proxy_config->{ProxySFTPHostKey} = $rsa_priv_key; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'event:20 proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key SFTPCiphers $cipher_algo SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo SFTPAuthorizedHostKeys file:~/.authorized_keys Port $vhost_port2 ServerName "Other Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key SFTPCiphers $cipher_algo SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo SFTPAuthorizedHostKeys file:~/.authorized_keys EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(5); for (my $i = 0; $i < 3; $i++) { my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_ts = [gettimeofday()]; if ($ENV{TEST_VERBOSE}) { print STDERR "Opening SFTP channel...\n"; } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_elapsed = tv_interval($sftp_start_ts); if ($ENV{TEST_VERBOSE}) { print STDERR "Sending SFTP STAT request ($sftp_start_elapsed since SFTP channel opened)...\n"; } my $path = 'proxy.conf'; unless ($sftp->stat($path, 1)) { my ($err_code, $err_name) = $sftp->error(); die("STAT $path failed: [$err_name] ($err_code)"); } if ($ENV{TEST_VERBOSE}) { print STDERR "Closing SFTP channel...\n"; } # To close the SFTP channel, we have to explicitly destroy the object $sftp = undef; if ($ENV{TEST_VERBOSE}) { print STDERR "Disconnecting SSH...\n"; } $ssh2->disconnect(); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_connect_policy_per_user_by_json { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $vhost_port2 = $vhost_port - 7; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyReverseConnectPolicy} = 'PerUser'; $proxy_config->{ProxySFTPCiphers} = $cipher_algo; $proxy_config->{ProxySFTPDigests} = $digest_algo; $proxy_config->{ProxySFTPKeyExchanges} = $kex_algo; $proxy_config->{ProxyReverseServers} = "sftp://127.0.0.1:$vhost_port sftp://127.0.0.1:$vhost_port2", my $user_path = File::Spec->rel2abs("$tmpdir/$setup->{user}-servers.json"); if (open(my $fh, "> $user_path")) { print $fh "[ \"ftp://127.0.0.1:$vhost_port2\" ]\n"; unless (close($fh)) { die("Can't write $user_path: $!"); } } else { die("Can't open $user_path: $!"); } my $uservar_path = File::Spec->rel2abs("$tmpdir/%U-servers.json"); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); # For hostbased authentication my $rsa_priv_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/test_rsa_key"); my $rsa_pub_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/test_rsa_key.pub"); my $rsa_rfc4716_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/authorized_rsa_keys"); my $authorized_keys = File::Spec->rel2abs("$tmpdir/.authorized_keys"); unless (copy($rsa_rfc4716_key, $authorized_keys)) { die("Can't copy $rsa_rfc4716_key to $authorized_keys: $!"); } unless (chmod(0400, $rsa_priv_key)) { die("Can't set perms on $rsa_priv_key: $!"); } $proxy_config->{ProxySFTPHostKey} = $rsa_priv_key; # Since we need multiple ProxyReverseServers directives, convert this # hashref into an arrayref. $proxy_config = config_hash2array($proxy_config); push(@$proxy_config, "ProxyReverseServers file:$uservar_path"); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'event:20 proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key SFTPCiphers $cipher_algo SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo SFTPAuthorizedHostKeys file:~/.authorized_keys Port $vhost_port2 ServerName "Other Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key SFTPCiphers $cipher_algo SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo SFTPAuthorizedHostKeys file:~/.authorized_keys EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); for (my $i = 0; $i < 3; $i++) { my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_ts = [gettimeofday()]; if ($ENV{TEST_VERBOSE}) { print STDERR "Opening SFTP channel...\n"; } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_elapsed = tv_interval($sftp_start_ts); if ($ENV{TEST_VERBOSE}) { print STDERR "Sending SFTP STAT request ($sftp_start_elapsed since SFTP channel opened)...\n"; } my $path = 'proxy.conf'; unless ($sftp->stat($path, 1)) { my ($err_code, $err_name) = $sftp->error(); die("STAT $path failed: [$err_name] ($err_code)"); } if ($ENV{TEST_VERBOSE}) { print STDERR "Closing SFTP channel...\n"; } # To close the SFTP channel, we have to explicitly destroy the object $sftp = undef; if ($ENV{TEST_VERBOSE}) { print STDERR "Disconnecting SSH...\n"; } $ssh2->disconnect(); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_connect_policy_per_group_password_auth { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $vhost_port2 = $vhost_port - 7; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyReverseConnectPolicy} = 'PerGroup'; $proxy_config->{ProxyOptions} = 'UseReverseProxyAuth'; $proxy_config->{ProxySFTPCiphers} = $cipher_algo; $proxy_config->{ProxySFTPDigests} = $digest_algo; $proxy_config->{ProxySFTPKeyExchanges} = $kex_algo; $proxy_config->{ProxyReverseServers} = "sftp://127.0.0.1:$vhost_port sftp://127.0.0.1:$vhost_port2", my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); # For hostbased authentication my $rsa_priv_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/test_rsa_key"); my $rsa_pub_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/test_rsa_key.pub"); my $rsa_rfc4716_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/authorized_rsa_keys"); my $authorized_keys = File::Spec->rel2abs("$tmpdir/.authorized_keys"); unless (copy($rsa_rfc4716_key, $authorized_keys)) { die("Can't copy $rsa_rfc4716_key to $authorized_keys: $!"); } unless (chmod(0400, $rsa_priv_key)) { die("Can't set perms on $rsa_priv_key: $!"); } $proxy_config->{ProxySFTPHostKey} = $rsa_priv_key; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'event:20 proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key SFTPCiphers $cipher_algo SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo SFTPAuthorizedHostKeys file:~/.authorized_keys Port $vhost_port2 ServerName "Other Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key SFTPCiphers $cipher_algo SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo SFTPAuthorizedHostKeys file:~/.authorized_keys EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); for (my $i = 0; $i < 3; $i++) { my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_ts = [gettimeofday()]; if ($ENV{TEST_VERBOSE}) { print STDERR "Opening SFTP channel...\n"; } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_elapsed = tv_interval($sftp_start_ts); if ($ENV{TEST_VERBOSE}) { print STDERR "Sending SFTP STAT request ($sftp_start_elapsed since SFTP channel opened)...\n"; } my $path = 'proxy.conf'; unless ($sftp->stat($path, 1)) { my ($err_code, $err_name) = $sftp->error(); die("STAT $path failed: [$err_name] ($err_code)"); } if ($ENV{TEST_VERBOSE}) { print STDERR "Closing SFTP channel...\n"; } # To close the SFTP channel, we have to explicitly destroy the object $sftp = undef; if ($ENV{TEST_VERBOSE}) { print STDERR "Disconnecting SSH...\n"; } $ssh2->disconnect(); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_ssh_connect_policy_per_group_publickey_auth { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $vhost_port2 = $vhost_port - 7; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyReverseConnectPolicy} = 'PerGroup'; $proxy_config->{ProxyOptions} = 'UseReverseProxyAuth'; $proxy_config->{ProxySFTPCiphers} = $cipher_algo; $proxy_config->{ProxySFTPDigests} = $digest_algo; $proxy_config->{ProxySFTPKeyExchanges} = $kex_algo; $proxy_config->{ProxyReverseServers} = "sftp://127.0.0.1:$vhost_port sftp://127.0.0.1:$vhost_port2", my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); # For hostbased authentication my $rsa_priv_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/test_rsa_key"); my $rsa_pub_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/test_rsa_key.pub"); my $rsa_rfc4716_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/authorized_rsa_keys"); my $authorized_keys = File::Spec->rel2abs("$tmpdir/.authorized_keys"); unless (copy($rsa_rfc4716_key, $authorized_keys)) { die("Can't copy $rsa_rfc4716_key to $authorized_keys: $!"); } unless (chmod(0400, $rsa_priv_key)) { die("Can't set perms on $rsa_priv_key: $!"); } $proxy_config->{ProxySFTPHostKey} = $rsa_priv_key; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'event:20 proxy:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 ssh2:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", 'SFTPAuthorizedUserKeys file:~/.authorized_keys', ], 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key SFTPCiphers $cipher_algo SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo SFTPAuthorizedHostKeys file:~/.authorized_keys Port $vhost_port2 ServerName "Other Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key SFTPCiphers $cipher_algo SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo SFTPAuthorizedHostKeys file:~/.authorized_keys EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); for (my $i = 0; $i < 3; $i++) { my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_publickey($setup->{user}, $rsa_pub_key, $rsa_priv_key)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_ts = [gettimeofday()]; if ($ENV{TEST_VERBOSE}) { print STDERR "Opening SFTP channel...\n"; } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp_start_elapsed = tv_interval($sftp_start_ts); if ($ENV{TEST_VERBOSE}) { print STDERR "Sending SFTP STAT request ($sftp_start_elapsed since SFTP channel opened)...\n"; } my $path = 'proxy.conf'; unless ($sftp->stat($path, 1)) { my ($err_code, $err_name) = $sftp->error(); die("STAT $path failed: [$err_name] ($err_code)"); } if ($ENV{TEST_VERBOSE}) { print STDERR "Closing SFTP channel...\n"; } # To close the SFTP channel, we have to explicitly destroy the object $sftp = undef; if ($ENV{TEST_VERBOSE}) { print STDERR "Disconnecting SSH...\n"; } $ssh2->disconnect(); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 30) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } 1; proftpd-mod_proxy-0.9.5/t/lib/ProFTPD/Tests/Modules/mod_proxy/ssh/000077500000000000000000000000001475737016700250205ustar00rootroot00000000000000proftpd-mod_proxy-0.9.5/t/lib/ProFTPD/Tests/Modules/mod_proxy/ssh/redis.pm000066400000000000000000000322631475737016700264720ustar00rootroot00000000000000package ProFTPD::Tests::Modules::mod_proxy::ssh::redis; use lib qw(t/lib); use base qw(ProFTPD::TestSuite::Child); use strict; use Carp; use File::Copy; use File::Path qw(mkpath); use File::Spec; use IO::Handle; use IO::Socket::INET; use Time::HiRes qw(gettimeofday tv_interval usleep); use ProFTPD::TestSuite::FTP; use ProFTPD::TestSuite::Utils qw(:auth :config :running :test :testsuite); $| = 1; my $order = 0; my $TESTS = { proxy_reverse_backend_ssh_verify_server_off_with_redis => { order => ++$order, test_class => [qw(forking mod_proxy mod_redis mod_sftp reverse)], }, proxy_reverse_backend_ssh_verify_server_on_with_redis => { order => ++$order, test_class => [qw(forking mod_proxy mod_redis mod_sftp reverse)], }, }; sub new { return shift()->SUPER::new(@_); } sub list_tests { # Check for the required Perl modules: my $required = [qw( MIME::Base64 Net::SSH2 Redis )]; foreach my $req (@$required) { eval "use $req"; if ($@) { print STDERR "\nWARNING:\n + Module '$req' not found, skipping all tests\n"; if ($ENV{TEST_VERBOSE}) { print STDERR "Unable to load $req: $@\n"; } return qw(testsuite_empty_test); } } return testsuite_get_runnable_tests($TESTS); } sub set_up { my $self = shift; $self->SUPER::set_up(@_); # Make sure that mod_sftp does not complain about permissions on the hostkey # files. my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); unless (chmod(0400, $rsa_host_key)) { die("Can't set perms on mod_sftp hostkeys: $!"); } } sub get_reverse_proxy_config { my $tmpdir = shift; my $log_file = shift; my $vhost_port = shift; my $table_dir = File::Spec->rel2abs("$tmpdir/var/proxy"); my $config = { ProxyEngine => 'on', ProxyLog => $log_file, ProxyReverseServers => "sftp://127.0.0.1:$vhost_port", ProxyRole => 'reverse', ProxyTables => $table_dir, }; return $config; } # TODO: Note that this test is used for manually reviewing the generated logs; # it does NOT currently fail if hostkey caching fails (although it should). sub proxy_reverse_backend_ssh_verify_server_off_with_redis { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyDatastore} = 'Redis mod_proxy.testsuite.ssh.'; $proxy_config->{ProxySFTPCiphers} = $cipher_algo; $proxy_config->{ProxySFTPDigests} = $digest_algo; $proxy_config->{ProxySFTPKeyExchanges} = $kex_algo; $proxy_config->{ProxySFTPVerifyServer} = 'off'; my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $redis_server = '127.0.0.1'; if (defined($ENV{REDIS_HOST})) { $redis_server = $ENV{REDIS_HOST}; } my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'proxy:20 proxy.db:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.redis:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 redis:30 ssh2:20 sftp:20 table:30', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_redis.c' => { RedisEngine => 'on', RedisServer => "$redis_server:6379", RedisTimeouts => '2000 500', RedisLog => $setup->{log_file}, }, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key SFTPCiphers $cipher_algo SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } $ssh2->disconnect(); # We now connect again, to check the just-stored hostkey. $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $setup->{log_file}")) { my $no_existing = 0; my $hostkey_matches = 0; while (my $line = <$fh>) { chomp($line); if ($ENV{TEST_VERBOSE}) { print STDERR "# $line\n"; } if ($line =~ /no existing hostkey stored for vhost ID/) { $no_existing = 1; } if ($line =~ /stored hostkey matches current hostkey for vhost/) { $hostkey_matches = 1; last; } } close($fh); $self->assert($no_existing, test_msg("Did not see expected TraceLog messages about no existing stored hostkey")); $self->assert($hostkey_matches, test_msg("Did not see expected TraceLog messages about stored hostkey matching")); } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup->{log_file}, $ex); } sub redis_set_hostkey { my $redis_server = shift; my $key = shift; my $algo = shift; my $blob = shift; use MIME::Base64; use Redis; my $redis = Redis->new( server => "$redis_server:6379", reconnect => 5, ); $redis->del($key); $redis->hset($key, 'algo', $algo); $redis->hset($key, 'blob', encode_base64($blob)); $redis->quit(); return 1; } sub proxy_reverse_backend_ssh_verify_server_on_with_redis { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $cipher_algo = 'aes256-ctr'; my $digest_algo = 'hmac-sha1'; my $kex_algo = 'diffie-hellman-group14-sha1'; my $redis_namespace = 'mod_proxy.testsuite.ssh.'; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyDatastore} = "Redis $redis_namespace"; $proxy_config->{ProxySFTPCiphers} = $cipher_algo; $proxy_config->{ProxySFTPDigests} = $digest_algo; $proxy_config->{ProxySFTPKeyExchanges} = $kex_algo; $proxy_config->{ProxySFTPVerifyServer} = 'on'; my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $redis_server = '127.0.0.1'; if (defined($ENV{REDIS_HOST})) { $redis_server = $ENV{REDIS_HOST}; } my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'proxy:20 proxy.db:20 proxy.reverse:20 proxy.ssh:20 proxy.ssh.auth:20 proxy.ssh.redis:20 proxy.ssh.disconnect:20 proxy.ssh.packet:20 proxy.ssh.kex:20 redis:30 ssh2:20 sftp:20 table:30', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_redis.c' => { RedisEngine => 'on', RedisServer => "$redis_server:6379", RedisTimeouts => '2000 500', RedisLog => $setup->{log_file}, }, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", ], }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none SFTPEngine on SFTPLog $setup->{log_file} SFTPHostKey $rsa_host_key SFTPCiphers $cipher_algo SFTPDigests $digest_algo SFTPKeyExchanges $kex_algo EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # We force a hostkey mismatch by manually creating the expected Redis entry, # populated with a hostkey entry that will not match. # # Note that we have to do it here, once we know the dynamically allocated # port number. my $redis_key = $redis_namespace . 'proxy_ssh_hostkeys:' . $proxy_config->{ProxyReverseServers}; redis_set_hostkey($redis_server, $redis_key, 'ssh-ed25519', 'foobar'); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $setup->{log_file}")) { my $have_existing = 0; my $hostkey_mismatch = 0; while (my $line = <$fh>) { chomp($line); if ($ENV{TEST_VERBOSE}) { print STDERR "# $line\n"; } if ($line =~ /found stored .*? for vhost ID/) { $have_existing = 1; } if ($line =~ /stored hostkey does not match current hostkey .*? ProxySFTPVerifyServer is enabled/) { $hostkey_mismatch = 1; last; } } close($fh); $self->assert($have_existing, test_msg("Did not see expected TraceLog messages about existing stored hostkey")); $self->assert($hostkey_mismatch, test_msg("Did not see expected TraceLog messages about stored hostkey mismatch")); } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup->{log_file}, $ex); } 1; proftpd-mod_proxy-0.9.5/t/lib/ProFTPD/Tests/Modules/mod_proxy/tls.pm000066400000000000000000007237301475737016700253770ustar00rootroot00000000000000package ProFTPD::Tests::Modules::mod_proxy::tls; use lib qw(t/lib); use base qw(ProFTPD::TestSuite::Child); use strict; use Carp; use File::Copy; use File::Path qw(mkpath); use File::Spec; use IO::Handle; use IO::Socket::INET; use Time::HiRes qw(gettimeofday tv_interval usleep); use ProFTPD::TestSuite::FTP; use ProFTPD::TestSuite::Utils qw(:auth :config :running :test :testsuite); $| = 1; my $order = 0; my $TESTS = { proxy_reverse_frontend_tls_login => { order => ++$order, test_class => [qw(forking mod_tls reverse)], }, proxy_reverse_frontend_tls_login_failed => { order => ++$order, test_class => [qw(forking mod_tls reverse)], }, proxy_reverse_frontend_tls_login_tlslogin => { order => ++$order, test_class => [qw(forking mod_tls reverse)], }, proxy_reverse_frontend_tls_list_pasv => { order => ++$order, test_class => [qw(forking mod_tls reverse)], }, proxy_reverse_backend_tls_login => { order => ++$order, test_class => [qw(forking mod_tls reverse)], }, proxy_reverse_backend_tls_implicit_login => { order => ++$order, test_class => [qw(forking mod_tls reverse)], }, proxy_reverse_backend_tls_login_cached_session => { order => ++$order, test_class => [qw(forking mod_tls mod_tls_shmcache reverse)], }, proxy_reverse_backend_tls_login_cached_ticket => { order => ++$order, test_class => [qw(forking mod_tls reverse)], }, proxy_reverse_backend_tls_login_client_auth => { order => ++$order, test_class => [qw(forking mod_tls reverse)], }, # This will fail with older OpenSSL versions, and TLSv1.3 sessions; see: # https://github.com/openssl/openssl/issues/7261 proxy_reverse_backend_tls_login_psk => { order => ++$order, test_class => [qw(forking mod_tls reverse)], }, proxy_reverse_backend_tls_login_failed_unknown_ca => { order => ++$order, test_class => [qw(forking mod_tls reverse)], }, # TODO: Investigate why this fails unexpectedly when TLSv1.3 is used proxy_reverse_backend_tls_login_failed_missing_client_auth => { order => ++$order, test_class => [qw(forking mod_tls reverse)], }, proxy_reverse_backend_tls_login_tlsv13_issue197 => { order => ++$order, test_class => [qw(forking mod_tls reverse)], }, proxy_reverse_backend_tls_list_pasv => { order => ++$order, test_class => [qw(forking mod_tls reverse)], }, proxy_reverse_frontend_backend_tls_roundrobin_login_after_host => { order => ++$order, test_class => [qw(forking mod_tls reverse)], }, proxy_reverse_frontend_backend_tls_peruser_login_after_host => { order => ++$order, test_class => [qw(forking mod_tls reverse)], }, proxy_reverse_frontend_backend_tls_list_pasv => { order => ++$order, test_class => [qw(forking mod_tls reverse)], }, proxy_reverse_frontend_backend_tls_list_port => { order => ++$order, test_class => [qw(forking mod_tls reverse)], }, proxy_reverse_frontend_backend_tls_nlst_pasv_error_issue244 => { order => ++$order, test_class => [qw(bug forking mod_tls reverse)], }, proxy_reverse_frontend_backend_tls_nlst_port_error_issue244 => { order => ++$order, test_class => [qw(bug forking mod_tls reverse)], }, proxy_reverse_frontend_backend_tls_abort => { order => ++$order, test_class => [qw(forking mod_tls reverse)], }, proxy_reverse_frontend_tls_json_peruser => { order => ++$order, test_class => [qw(forking mod_tls reverse)], }, proxy_reverse_config_backend_tls_engine_auto_unavail => { order => ++$order, test_class => [qw(forking mod_tls reverse)], }, proxy_reverse_config_backend_tls_engine_auto_ftps_uri_unavail => { order => ++$order, test_class => [qw(forking mod_tls reverse)], }, proxy_reverse_config_backend_tls_engine_on_unavail => { order => ++$order, test_class => [qw(forking mod_tls reverse)], }, proxy_reverse_config_backend_tls_engine_match_client_ftp => { order => ++$order, test_class => [qw(forking mod_tls reverse)], }, proxy_reverse_config_backend_tls_engine_match_client_ftps_explicit => { order => ++$order, test_class => [qw(forking mod_tls reverse)], }, proxy_reverse_config_backend_tls_engine_match_client_ftps_implicit => { order => ++$order, test_class => [qw(forking mod_tls reverse)], }, proxy_reverse_config_backend_tls_connect_policy_per_user => { order => ++$order, test_class => [qw(forking mod_tls reverse)], }, proxy_reverse_config_backend_tls_connect_policy_per_user_failed_unknown_ca => { order => ++$order, test_class => [qw(forking mod_tls reverse)], }, proxy_reverse_config_backend_tls_verify_server_off => { order => ++$order, test_class => [qw(forking mod_tls reverse)], }, proxy_reverse_config_backend_tls_no_session_cache => { order => ++$order, test_class => [qw(forking mod_tls mod_tls_shmcache reverse)], }, proxy_reverse_config_backend_tls_ctrl_use_direct_data_transfers_pasv => { order => ++$order, test_class => [qw(forking mod_tls reverse)], }, proxy_reverse_config_frontend_backend_tls_required_use_direct_data_transfers_pasv => { order => ++$order, test_class => [qw(forking mod_tls reverse)], }, proxy_reverse_proxy_protocol_v2_tlv_ssl => { order => ++$order, test_class => [qw(forking mod_proxy_protocol mod_tls reverse)], }, proxy_forward_frontend_tls_noproxyauth_login => { order => ++$order, test_class => [qw(forking mod_tls forward)], }, proxy_forward_backend_tls_login => { order => ++$order, test_class => [qw(forking mod_tls forward)], }, proxy_forward_backend_tls_implicit_login => { order => ++$order, test_class => [qw(forking mod_tls forward)], }, proxy_forward_backend_tls_login_failed_unknown_ca => { order => ++$order, test_class => [qw(forking mod_tls forward)], }, proxy_forward_config_backend_tls_engine_match_client_ftp => { order => ++$order, test_class => [qw(forking mod_tls forward)], }, proxy_forward_config_backend_tls_engine_match_client_ftps_explicit => { order => ++$order, test_class => [qw(forking mod_tls forward)], }, proxy_forward_config_backend_tls_engine_match_client_ftps_implicit => { order => ++$order, test_class => [qw(forking mod_tls forward)], }, proxy_forward_backend_tls_list_pasv => { order => ++$order, test_class => [qw(forking mod_tls forward)], }, proxy_forward_frontend_backend_tls_login_after_host => { order => ++$order, test_class => [qw(forking mod_tls forward)], }, proxy_forward_frontend_backend_tls_list_pasv => { order => ++$order, test_class => [qw(forking mod_tls forward)], }, # ProxyTLSTransferProtection Required # # frontend FTP, backend FTPS, passive transfer, LIST # frontend FTP, backend FTPS, active transfer, LIST # frontend FTP, backend FTPS, passive transfer, STOR # frontend FTP, backend FTPS, active transfer, STOR # # frontend FTPS, backend FTPS, passive transfer, LIST # frontend FTPS, backend FTPS, active transfer, LIST # frontend FTPS, backend FTPS, passive transfer, STOR # frontend FTPS, backend FTPS, active transfer, STOR proxy_forward_frontend_plain_backend_tls_list_pasv_tls_xfer_policy_required => { order => ++$order, test_class => [qw(forking mod_tls forward)], }, proxy_forward_frontend_plain_backend_tls_list_port_tls_xfer_policy_required => { order => ++$order, test_class => [qw(forking mod_tls forward)], }, proxy_forward_frontend_plain_backend_tls_stor_pasv_tls_xfer_policy_required => { order => ++$order, test_class => [qw(forking mod_tls forward)], }, proxy_forward_frontend_plain_backend_tls_stor_port_tls_xfer_policy_required => { order => ++$order, test_class => [qw(forking mod_tls forward)], }, proxy_forward_frontend_backend_tls_list_pasv_tls_xfer_policy_required => { order => ++$order, test_class => [qw(forking mod_tls forward)], }, proxy_forward_frontend_backend_tls_list_port_tls_xfer_policy_required => { order => ++$order, test_class => [qw(forking mod_tls forward)], }, proxy_forward_frontend_backend_tls_stor_pasv_tls_xfer_policy_required => { order => ++$order, test_class => [qw(forking mod_tls forward)], }, proxy_forward_frontend_backend_tls_stor_port_tls_xfer_policy_required => { order => ++$order, test_class => [qw(forking mod_tls forward)], }, # ProxyTLSTransferProtection Clear # # frontend FTP, backend FTPS, passive transfer, LIST # frontend FTP, backend FTPS, active transfer, LIST # frontend FTP, backend FTPS, passive transfer, STOR # frontend FTP, backend FTPS, active transfer, STOR # # frontend FTPS, backend FTPS, passive transfer, LIST # frontend FTPS, backend FTPS, active transfer, LIST # frontend FTPS, backend FTPS, passive transfer, STOR # frontend FTPS, backend FTPS, active transfer, STOR proxy_forward_frontend_plain_backend_tls_list_pasv_tls_xfer_policy_clear => { order => ++$order, test_class => [qw(forking mod_tls forward)], }, proxy_forward_frontend_plain_backend_tls_list_port_tls_xfer_policy_clear => { order => ++$order, test_class => [qw(forking mod_tls forward)], }, proxy_forward_frontend_plain_backend_tls_stor_pasv_tls_xfer_policy_clear => { order => ++$order, test_class => [qw(forking mod_tls forward)], }, proxy_forward_frontend_plain_backend_tls_stor_port_tls_xfer_policy_clear => { order => ++$order, test_class => [qw(forking mod_tls forward)], }, proxy_forward_frontend_backend_tls_list_pasv_tls_xfer_policy_clear => { order => ++$order, test_class => [qw(forking mod_tls forward)], }, proxy_forward_frontend_backend_tls_list_port_tls_xfer_policy_clear => { order => ++$order, test_class => [qw(forking mod_tls forward)], }, proxy_forward_frontend_backend_tls_stor_pasv_tls_xfer_policy_clear => { order => ++$order, test_class => [qw(forking mod_tls forward)], }, proxy_forward_frontend_backend_tls_stor_port_tls_xfer_policy_clear => { order => ++$order, test_class => [qw(forking mod_tls forward)], }, # ProxyTLSTransferProtection Client # # frontend FTP, backend FTPS, passive transfer, LIST # frontend FTP, backend FTPS, active transfer, LIST # frontend FTP, backend FTPS, passive transfer, STOR # frontend FTP, backend FTPS, active transfer, STOR # # frontend FTPS, backend FTPS, passive transfer, LIST # frontend FTPS, backend FTPS, active transfer, LIST # frontend FTPS, backend FTPS, passive transfer, STOR # frontend FTPS, backend FTPS, active transfer, STOR proxy_forward_frontend_plain_backend_tls_list_pasv_tls_xfer_policy_client => { order => ++$order, test_class => [qw(forking mod_tls forward)], }, proxy_forward_frontend_plain_backend_tls_list_port_tls_xfer_policy_client => { order => ++$order, test_class => [qw(forking mod_tls forward)], }, proxy_forward_frontend_plain_backend_tls_stor_pasv_tls_xfer_policy_client => { order => ++$order, test_class => [qw(forking mod_tls forward)], }, proxy_forward_frontend_plain_backend_tls_stor_port_tls_xfer_policy_client => { order => ++$order, test_class => [qw(forking mod_tls forward)], }, proxy_forward_frontend_backend_tls_list_pasv_tls_xfer_policy_client => { order => ++$order, test_class => [qw(forking mod_tls forward)], }, proxy_forward_frontend_backend_tls_list_port_tls_xfer_policy_client => { order => ++$order, test_class => [qw(forking mod_tls forward)], }, proxy_forward_frontend_backend_tls_stor_pasv_tls_xfer_policy_client => { order => ++$order, test_class => [qw(forking mod_tls forward)], }, proxy_forward_frontend_backend_tls_stor_port_tls_xfer_policy_client => { order => ++$order, test_class => [qw(forking mod_tls forward)], }, proxy_forward_config_backend_tls_ctrl_use_direct_data_transfers_pasv => { order => ++$order, test_class => [qw(forking mod_tls forward)], }, proxy_forward_config_frontend_backend_tls_required_use_direct_data_transfers_pasv => { order => ++$order, test_class => [qw(forking mod_tls forward)], }, }; sub new { return shift()->SUPER::new(@_); } sub list_tests { return testsuite_get_runnable_tests($TESTS); } sub set_up { my $self = shift; $self->SUPER::set_up(@_); my $psk_file = File::Spec->rel2abs('t/etc/modules/mod_tls/psk.dat'); unless (chmod(0400, $psk_file)) { die("Can't set perms on $psk_file: $!"); } } sub get_reverse_proxy_config { my $tmpdir = shift; my $log_file = shift; my $vhost_port = shift; my $table_dir = File::Spec->rel2abs("$tmpdir/var/proxy"); my $config = { ProxyEngine => 'on', ProxyLog => $log_file, ProxyReverseServers => "ftp://127.0.0.1:$vhost_port", ProxyRole => 'reverse', ProxyTables => $table_dir, ProxyTLSVerifyServer => 'off', }; return $config; } sub get_forward_proxy_config { my $tmpdir = shift; my $log_file = shift; my $vhost_port = shift; my $table_dir = File::Spec->rel2abs("$tmpdir/var/proxy"); my $config = { ProxyEngine => 'on', ProxyLog => $log_file, ProxyRole => 'forward', ProxyTables => $table_dir, ProxyTLSVerifyServer => 'off', Class => { 'forward-proxy' => { From => '127.0.0.1', ProxyForwardEnabled => 'on', }, }, }; return $config; } sub ftp_list { my $self = shift; my $client = shift; my $skip_quit = shift; $skip_quit = 0 unless defined($skip_quit); my $conn = $client->list_raw(); unless ($conn) { die("Failed to LIST: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 10); eval { $conn->close() }; if ($ENV{TEST_VERBOSE}) { print STDERR "LIST:\n$buf\n"; } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); unless ($skip_quit) { ($resp_code, $resp_msg) = $client->quit(); my $expected = 221; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Goodbye.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); } 1; } sub ftp_upload { my $self = shift; my $client = shift; my $path = shift; my $conn = $client->stor_raw($path); unless ($conn) { die("Failed to STOR $path: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf = 'AbCdEfGh' x 81920; $conn->write($buf, length($buf), 10); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); 1; } sub proxy_reverse_frontend_tls_login { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_tls.c' => { TLSEngine => 'on', TLSLog => $log_file, TLSRequired => 'on', TLSRSACertificateFile => $cert_file, TLSCACertificateFile => $ca_file, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } require Net::FTPSSL; # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $client_opts = { Encryption => 'E', Port => $port, }; if ($ENV{TEST_VERBOSE}) { $client_opts->{Debug} = 1; } my $client = Net::FTPSSL->new('127.0.0.1', %$client_opts); unless ($client) { die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr()); } unless ($client->login($user, $passwd)) { die("Can't login: " . $client->last_message()); } $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_frontend_tls_login_failed { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_tls.c' => { TLSEngine => 'on', TLSLog => $log_file, TLSRequired => 'on', TLSRSACertificateFile => $cert_file, TLSCACertificateFile => $ca_file, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } require Net::FTPSSL; # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $client_opts = { Encryption => 'E', Port => $port, }; if ($ENV{TEST_VERBOSE}) { $client_opts->{Debug} = 1; } my $client = Net::FTPSSL->new('127.0.0.1', %$client_opts); unless ($client) { die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr()); } if ($client->login($user, 'foobar')) { die("Login succeeded unexpectedly"); } my $resp_msg = $client->last_message(); $client->quit(); my $expected = '530 Login incorrect.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_frontend_tls_login_tlslogin { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $server_cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $client_cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/client-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $tlslogin_file = File::Spec->rel2abs("$tmpdir/.tlslogin"); unless (copy($client_cert_file, $tlslogin_file)) { die("Can't copy $client_cert_file to $tlslogin_file: $!"); } my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 auth:10 tls:20 proxy:20 proxy.conn:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.tls:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_tls.c' => { TLSEngine => 'on', TLSLog => $log_file, TLSRequired => 'on', TLSRSACertificateFile => $server_cert_file, TLSCACertificateFile => $ca_file, TLSOptions => 'AllowDotLogin', }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } require Net::FTPSSL; # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); # IO::Socket::SSL options my $ssl_opts = { SSL_use_cert => 1, SSL_cert_file => $client_cert_file, SSL_key_file => $client_cert_file, SSL_ca_file => $ca_file, }; my $client_opts = { Encryption => 'E', Port => $port, SSL_Client_Certificate => $ssl_opts, }; if ($ENV{TEST_VERBOSE}) { $client_opts->{Debug} = 1; } my $client = Net::FTPSSL->new('127.0.0.1', %$client_opts); unless ($client) { die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr()); } unless ($client->_user($user)) { die("USER error: " . $client->last_message()); } my $resp_msg = $client->last_message(); $client->quit(); # Even though we configured mod_tls on the proxy to allow .tlslogin, # mod_proxy does not actually invoke that functionality. And for good # reason. If mod_tls/mod_proxy allowed the DotLogin, then the client # would not need to send a password. But the backend FTP session is NOT # configured for DotLogin, which means that that WILL need a password. # # Thus expecting the 331 response code here is correct. my $expected = "331 Password required for $user"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_frontend_tls_list_pasv { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 netio:20 scoreboard:0 signal:0 proxy:20 proxy.inet:20 proxy.netio:20 proxy.reverse:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 tls:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_tls.c' => { TLSEngine => 'on', TLSLog => $log_file, TLSRequired => 'on', TLSRSACertificateFile => $cert_file, TLSCACertificateFile => $ca_file, TLSOptions => 'NoSessionReuseRequired EnableDiags', TLSTimeoutHandshake => 5, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } require Net::FTPSSL; # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $ssl_opts = { SSL_ca_file => $ca_file, }; my $client_opts = { Encryption => 'E', Port => $port, SSL_Client_Certificate => $ssl_opts, }; if ($ENV{TEST_VERBOSE}) { $client_opts->{Debug} = 1; } my $client = Net::FTPSSL->new('127.0.0.1', %$client_opts); unless ($client) { die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr()); } unless ($client->login($user, $passwd)) { die("Can't login: " . $client->last_message()); } my $res = $client->list(); unless ($res) { die("LIST failed unexpectedly: " . $client->last_message() . "(" . IO::Socket::SSL::errstr() . ")"); } my $resp_msg = $client->last_message(); $client->quit(); my $expected = '226 Transfer complete'; $self->assert($expected eq $resp_msg, test_msg("Expected response '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, 20) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_backend_tls_login { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyTLSEngine} = 'auto'; $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.netio:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20 tls:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none TLSEngine on TLSLog $setup->{log_file} TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file TLSOptions EnableDiags EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_tls_implicit_login { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); # Skip this test on GitHub, as it fails unnecessarily there. return if $ENV{PROFTPD_TEST_CI} eq 'github'; my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, 990); $proxy_config->{ProxyReverseServers} = 'ftps://demo:password@test.rebex.net:990'; $proxy_config->{ProxyTimeoutConnect} = '10s'; $proxy_config->{ProxyTLSEngine} = 'auto'; $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; $proxy_config->{ProxyTLSVerifyServer} = 'off'; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.db:20 proxy.netio:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20 proxy.netio:20 proxy.tls:20 netio:20 tls:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(3); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 10, 15); $client->login($setup->{user}, $setup->{passwd}); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } # TODO: Note that this test is used for manually reviewing the generated logs; # it does NOT currently fail if session caching fails (although it should). sub proxy_reverse_backend_tls_login_cached_session { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $cache_file = File::Spec->rel2abs("$tmpdir/tls-shmcache.dat"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyTLSEngine} = 'auto'; $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.netio:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_tls_shmcache.c' => { TLSSessionCache => "shm:/file=$cache_file", }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none TLSEngine on TLSLog $log_file TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); for (my $i = 0; $i < 3; $i++) { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); $client->quit(); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } # TODO: Note that this test is used for manually reviewing the generated logs; # it does NOT currently fail if ticket caching fails (although it should). sub proxy_reverse_backend_tls_login_cached_ticket { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $cache_file = File::Spec->rel2abs("$tmpdir/tls-shmcache.dat"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyTLSEngine} = 'auto'; $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags NoSessionCache'; } my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.netio:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20 tls:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < # Recommended practice is to diable server-side session caching entirely, # if you are going to use client-side session tickets. Why? It # reduces the number of places where a session's master secret are held # in memory for "long" periods of time. TLSSessionCache off Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none TLSEngine on TLSLog $log_file TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file TLSSessionTickets on TLSStapling on TLSOptions EnableDiags EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); for (my $i = 0; $i < 3; $i++) { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, undef, 2); $client->login($user, $passwd); $client->list(); $client->quit(); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_backend_tls_login_client_auth { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $server_cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $client_cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/client-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyTLSEngine} = 'auto'; $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; $proxy_config->{ProxyTLSCertificateFile} = $client_cert_file; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.netio:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none TLSEngine on TLSLog $log_file TLSRequired on TLSRSACertificateFile $server_cert_file TLSCACertificateFile $ca_file TLSVerifyClient on EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_backend_tls_login_psk { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $server_cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $client_cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/client-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $psk_file = File::Spec->rel2abs('t/etc/modules/mod_tls/psk.dat'); my $psk_id = "proxy"; my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyTLSEngine} = 'auto'; $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; $proxy_config->{ProxyTLSCertificateFile} = $client_cert_file; $proxy_config->{ProxyTLSCipherSuite} = "PSK"; $proxy_config->{ProxyTLSPreSharedKey} = "$psk_id hex:$psk_file"; $proxy_config->{ProxyTLSProtocol} = 'TLSv1.2'; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.netio:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none TLSEngine on TLSLog $log_file TLSRequired on TLSRSACertificateFile $server_cert_file TLSCACertificateFile $ca_file TLSProtocol TLSv1.2 TLSCipherSuite PSK TLSPreSharedKey $psk_id hex:$psk_file TLSOptions EnableDiags EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_backend_tls_login_failed_unknown_ca { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyTLSEngine} = 'auto'; $proxy_config->{ProxyTLSVerifyServer} = 'on'; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.netio:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none TLSEngine on TLSLog $log_file TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); eval { ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 0) }; unless ($@) { die("Connect succeeded unexpectedly"); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_backend_tls_login_failed_missing_client_auth { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $server_cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $client_cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/client-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyTLSEngine} = 'auto'; $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; # $proxy_config->{ProxyTLSCertificateFile} = $client_cert_file; $proxy_config->{ProxyTLSProtocol} = 'TLSv1.2'; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.netio:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none TLSEngine on TLSLog $log_file TLSRequired on TLSRSACertificateFile $server_cert_file TLSCACertificateFile $ca_file TLSVerifyClient on EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); eval { ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 0) }; unless ($@) { die("Connection succeeded unexpectedly"); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_backend_tls_login_tlsv13_issue197 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyTLSEngine} = 'auto'; $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; $proxy_config->{ProxyTLSProtocol} = 'TLSv1.3'; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.netio:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20 tls:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none TLSEngine on TLSLog $setup->{log_file} TLSRequired on TLSProtocol TLSv1.3 TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file TLSOptions EnableDiags EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_backend_tls_list_pasv { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyTLSEngine} = 'auto'; $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:10 lock:0 netio:20 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.netio:20 proxy.tls:20 proxy.reverse:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20 tls:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none TLSEngine on TLSLog $log_file TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file TLSOptions EnableDiags EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); my $conn = $client->list_raw(); unless ($conn) { die("Failed to LIST: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 5); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, 20) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_frontend_backend_tls_roundrobin_login_after_host { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $host = 'ftp.castaglia.org'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 netio:20 scoreboard:0 signal:0 proxy:20 proxy.netio:20 proxy.db:20 proxy.reverse:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20 tls:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', DefaultServer => 'on', ServerName => '"Default Server"', SocketBindTight => 'on', IfModules => { 'mod_tls.c' => { TLSEngine => 'on', TLSLog => $setup->{log_file}, TLSRequired => 'on', TLSRSACertificateFile => $cert_file, TLSCACertificateFile => $ca_file, TLSOptions => 'NoSessionReuseRequired EnableDiags', TLSTimeoutHandshake => 5, TLSVerifyClient => 'off', TLSVerifyServer => 'off', }, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { my $tables_dir = File::Spec->rel2abs("$tmpdir/var/proxy"); print $fh < ProxyTables $tables_dir Port $port ServerAlias $host ServerName "Namebased Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c DelayEngine off ProxyEngine on ProxyLog $setup->{log_file} ProxyRole reverse ProxyReverseServers ftp://127.0.0.1:$vhost_port ProxyReverseConnectPolicy RoundRobin ProxyTLSEngine auto ProxyTLSCACertificateFile $ca_file ProxyTLSOptions EnableDiags ProxyTLSVerifyServer off TLSEngine on TLSLog $setup->{log_file} TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file TLSVerifyClient off TLSVerifyServer off TLSOptions EnableDiags NoSessionReuseRequired Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none TLSEngine on TLSLog $setup->{log_file} TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file TLSVerifyClient off TLSVerifyServer off TLSOptions EnableDiags NoSessionReuseRequired EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } require Net::FTPSSL; # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $ssl_opts = { SSL_hostname => $host, SSL_ca_file => $ca_file, # Yes, this is a deliberate choice. Sigh. SSL_verifycn_scheme => 'none', }; my $client_opts = { Encryption => 'E', Port => $port, SSL_Client_Certificate => $ssl_opts, }; if ($ENV{TEST_VERBOSE}) { $client_opts->{Debug} = 1; } my $client = Net::FTPSSL->new('127.0.0.1', %$client_opts); unless ($client) { die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr()); } unless ($client->quot('HOST', $host)) { die("HOST failed: " . $client->last_message()); } unless ($client->login($setup->{user}, $setup->{passwd})) { die("Can't login: " . $client->last_message()); } my $res = $client->list(); unless ($res) { die("LIST failed unexpectedly: " . $client->last_message() . "(" . IO::Socket::SSL::errstr() . ")"); } my $resp_msg = $client->last_message(); my $expected = '226 Transfer complete'; $self->assert($expected eq $resp_msg, test_msg("Expected response '$expected', got '$resp_msg'")); # Do the LIST again; there are some reports that a first transfer # might succeed, but subsequent ones will fail. $res = $client->list(); unless ($res) { die("LIST failed unexpectedly: " . $client->last_message() . "(" . IO::Socket::SSL::errstr() . ")"); } $resp_msg = $client->last_message(); $client->quit(); $expected = '226 Transfer complete'; $self->assert($expected eq $resp_msg, test_msg("Expected response '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 20) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_frontend_backend_tls_peruser_login_after_host { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $host = 'localhost'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 netio:20 scoreboard:0 signal:0 proxy:20 proxy.netio:20 proxy.db:20 proxy.reverse:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20 tls:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, DefaultServer => 'on', ServerName => '"Default Server"', SocketBindTight => 'on', IfModules => { 'mod_tls.c' => { TLSEngine => 'on', TLSLog => $log_file, TLSRequired => 'on', TLSRSACertificateFile => $cert_file, TLSCACertificateFile => $ca_file, TLSOptions => 'NoSessionReuseRequired EnableDiags', TLSTimeoutHandshake => 5, TLSVerifyClient => 'off', TLSVerifyServer => 'off', }, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { my $tables_dir = File::Spec->rel2abs("$tmpdir/var/proxy"); my $vhost_port2 = $vhost_port - 7; print $fh < ProxyTables $tables_dir Port $port ServerAlias $host ServerName "Namebased Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c DelayEngine off ProxyEngine on ProxyLog $log_file ProxyRole reverse ProxyReverseConnectPolicy PerUser ProxyReverseServers ftp://127.0.0.1:$vhost_port ftp://127.0.0.1:$vhost_port2 ProxyTimeoutConnect 1sec ProxyTLSEngine auto ProxyTLSCACertificateFile $ca_file ProxyTLSOptions EnableDiags ProxyTLSVerifyServer off TLSEngine on TLSLog $log_file TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file TLSVerifyClient off TLSVerifyServer off TLSOptions EnableDiags NoSessionReuseRequired Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none TLSEngine on TLSLog $log_file TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file TLSVerifyClient off TLSVerifyServer off TLSOptions EnableDiags NoSessionReuseRequired Port $vhost_port2 ServerName "Other Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none TLSEngine on TLSLog $log_file TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file TLSVerifyClient off TLSVerifyServer off TLSOptions EnableDiags NoSessionReuseRequired EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } require Net::FTPSSL; # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $ssl_opts = { SSL_hostname => $host, SSL_ca_file => $ca_file, # Yes, this is a deliberate choice. Sigh. SSL_verifycn_scheme => 'none', }; my $client_opts = { Encryption => 'E', Port => $port, SSL_Client_Certificate => $ssl_opts, }; if ($ENV{TEST_VERBOSE}) { $client_opts->{Debug} = 1; } my $client = Net::FTPSSL->new('127.0.0.1', %$client_opts); unless ($client) { die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr()); } unless ($client->quot('HOST', $host)) { die("HOST failed: " . $client->last_message()); } unless ($client->login($user, $passwd)) { die("Can't login: " . $client->last_message()); } my $res = $client->list(); unless ($res) { die("LIST failed unexpectedly: " . $client->last_message() . "(" . IO::Socket::SSL::errstr() . ")"); } my $resp_msg = $client->last_message(); my $expected = '226 Transfer complete'; $self->assert($expected eq $resp_msg, test_msg("Expected response '$expected', got '$resp_msg'")); # Do the LIST again; there are some reports that a first transfer # might succeed, but subsequent ones will fail. $res = $client->list(); unless ($res) { die("LIST failed unexpectedly: " . $client->last_message() . "(" . IO::Socket::SSL::errstr() . ")"); } $resp_msg = $client->last_message(); $client->quit(); $expected = '226 Transfer complete'; $self->assert($expected eq $resp_msg, test_msg("Expected response '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, 20) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_frontend_backend_tls_list_pasv { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyTLSEngine} = 'auto'; $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 netio:20 scoreboard:0 signal:0 proxy:20 proxy.netio:20 proxy.db:20 proxy.reverse:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20 tls:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_tls.c' => { TLSEngine => 'on', TLSLog => $setup->{log_file}, TLSRequired => 'on', TLSRSACertificateFile => $cert_file, TLSCACertificateFile => $ca_file, TLSOptions => 'NoSessionReuseRequired EnableDiags', TLSTimeoutHandshake => 5, TLSVerifyClient => 'off', TLSVerifyServer => 'off', }, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none TLSEngine on TLSLog $setup->{log_file} TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file TLSVerifyClient off TLSVerifyServer off TLSOptions EnableDiags NoSessionReuseRequired EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } require Net::FTPSSL; # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $ssl_opts = { SSL_verify_mode => $IO::Socket::SSL::SSL_VERIFY_NONE, }; my $client_opts = { Encryption => 'E', Port => $port, SSL_Client_Certificate => $ssl_opts, }; if ($ENV{TEST_VERBOSE}) { $client_opts->{Debug} = 1; } my $client = Net::FTPSSL->new('127.0.0.1', %$client_opts); unless ($client) { die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr()); } unless ($client->login($setup->{user}, $setup->{passwd})) { die("Can't login: " . $client->last_message()); } my $res = $client->list(); unless ($res) { die("LIST failed unexpectedly: " . $client->last_message() . "(" . IO::Socket::SSL::errstr() . ")"); } my $resp_msg = $client->last_message(); my $expected = '226 Transfer complete'; $self->assert($expected eq $resp_msg, test_msg("Expected response '$expected', got '$resp_msg'")); # Do the LIST again; there are some reports that a first transfer # might succeed, but subsequent ones will fail. for (my $i = 0; $i < 3; $i++) { $res = $client->list(); unless ($res) { die("LIST failed unexpectedly: " . $client->last_message() . "(" . IO::Socket::SSL::errstr() . ")"); } $resp_msg = $client->last_message(); $expected = '226 Transfer complete'; $self->assert($expected eq $resp_msg, test_msg("Expected response '$expected', got '$resp_msg'")); } $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 20) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_frontend_backend_tls_list_port { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyTLSEngine} = 'auto'; $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; # For this test, we want to force the use of PORT for the backend data # transfers. $proxy_config->{ProxyDataTransferPolicy} = 'PORT'; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 netio:20 scoreboard:0 signal:0 proxy:20 proxy.netio:20 proxy.db:20 proxy.reverse:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20 tls:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_tls.c' => { TLSEngine => 'on', TLSLog => $setup->{log_file}, TLSRequired => 'on', TLSRSACertificateFile => $cert_file, TLSCACertificateFile => $ca_file, TLSOptions => 'NoSessionReuseRequired EnableDiags', TLSTimeoutHandshake => 5, TLSVerifyClient => 'off', TLSVerifyServer => 'off', }, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none TLSEngine on TLSLog $setup->{log_file} TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file TLSVerifyClient off TLSVerifyServer off TLSOptions EnableDiags NoSessionReuseRequired EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } require Net::FTPSSL; # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $ssl_opts = { SSL_verify_mode => $IO::Socket::SSL::SSL_VERIFY_NONE, }; my $client_opts = { Encryption => 'E', Port => $port, SSL_Client_Certificate => $ssl_opts, }; if ($ENV{TEST_VERBOSE}) { $client_opts->{Debug} = 1; } my $client = Net::FTPSSL->new('127.0.0.1', %$client_opts); unless ($client) { die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr()); } unless ($client->login($setup->{user}, $setup->{passwd})) { die("Can't login: " . $client->last_message()); } my $res = $client->list(); unless ($res) { die("LIST failed unexpectedly: " . $client->last_message() . "(" . IO::Socket::SSL::errstr() . ")"); } my $resp_msg = $client->last_message(); my $expected = '226 Transfer complete'; $self->assert($expected eq $resp_msg, test_msg("Expected response '$expected', got '$resp_msg'")); # Do the LIST again; there are some reports that a first transfer # might succeed, but subsequent ones will fail. for (my $i = 0; $i < 3; $i++) { $res = $client->list(); unless ($res) { die("LIST failed unexpectedly: " . $client->last_message() . "(" . IO::Socket::SSL::errstr() . ")"); } $resp_msg = $client->last_message(); $expected = '226 Transfer complete'; $self->assert($expected eq $resp_msg, test_msg("Expected response '$expected', got '$resp_msg'")); } $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 20) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_frontend_backend_tls_nlst_pasv_error_issue244 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyTLSEngine} = 'auto'; $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'netio:20 proxy:20 proxy.netio:20 proxy.db:20 proxy.reverse:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20 proxy.ftp.xfer:20 tls:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_tls.c' => { TLSEngine => 'on', TLSLog => $setup->{log_file}, TLSRequired => 'on', TLSRSACertificateFile => $cert_file, TLSCACertificateFile => $ca_file, TLSOptions => 'NoSessionReuseRequired EnableDiags', TLSTimeoutHandshake => 5, TLSVerifyClient => 'off', TLSVerifyServer => 'off', }, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none TLSEngine on TLSLog $setup->{log_file} TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file TLSVerifyClient off TLSVerifyServer off TLSOptions EnableDiags NoSessionReuseRequired EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } require Net::FTPSSL; # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $ssl_opts = { SSL_verify_mode => $IO::Socket::SSL::SSL_VERIFY_NONE, }; my $client_opts = { Encryption => 'E', Port => $port, SSL_Client_Certificate => $ssl_opts, }; if ($ENV{TEST_VERBOSE}) { $client_opts->{Debug} = 1; } my $client = Net::FTPSSL->new('127.0.0.1', %$client_opts); unless ($client) { die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr()); } unless ($client->login($setup->{user}, $setup->{passwd})) { die("Can't login: " . $client->last_message()); } my $bad_path = '/foo/bar/baz'; my $res = $client->nlst($bad_path); if ($res) { die("NLST $bad_path succeeded unexpectedly"); } my $resp_msg = $client->last_message(); my $expected = "450 $bad_path: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response '$expected', got '$resp_msg'")); # Do the NLST again; there are some reports that a first transfer # might succeed, but subsequent ones will fail. for (my $i = 0; $i < 3; $i++) { $res = $client->nlst($bad_path); if ($res) { die("NLST $bad_path succeeded unexpectedly"); } $resp_msg = $client->last_message(); $self->assert($expected eq $resp_msg, test_msg("Expected response '$expected', got '$resp_msg'")); } $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 20) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_frontend_backend_tls_nlst_port_error_issue244 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyTLSEngine} = 'auto'; $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; # For this test, we want to force the use of PORT for the backend data # transfers. $proxy_config->{ProxyDataTransferPolicy} = 'PORT'; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'netio:20 proxy:20 proxy.netio:20 proxy.db:20 proxy.reverse:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20 proxy.ftp.xfer:20 tls:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_tls.c' => { TLSEngine => 'on', TLSLog => $setup->{log_file}, TLSRequired => 'on', TLSRSACertificateFile => $cert_file, TLSCACertificateFile => $ca_file, TLSOptions => 'NoSessionReuseRequired EnableDiags', TLSTimeoutHandshake => 5, TLSVerifyClient => 'off', TLSVerifyServer => 'off', }, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none TLSEngine on TLSLog $setup->{log_file} TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file TLSVerifyClient off TLSVerifyServer off TLSOptions EnableDiags NoSessionReuseRequired EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } require Net::FTPSSL; # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $ssl_opts = { SSL_verify_mode => $IO::Socket::SSL::SSL_VERIFY_NONE, }; my $client_opts = { Encryption => 'E', Port => $port, SSL_Client_Certificate => $ssl_opts, }; if ($ENV{TEST_VERBOSE}) { $client_opts->{Debug} = 1; } my $client = Net::FTPSSL->new('127.0.0.1', %$client_opts); unless ($client) { die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr()); } unless ($client->login($setup->{user}, $setup->{passwd})) { die("Can't login: " . $client->last_message()); } my $bad_path = '/foo/bar/baz'; my $res = $client->nlst($bad_path); if ($res) { die("NLST $bad_path succeeded unexpectedly"); } my $resp_msg = $client->last_message(); my $expected = "450 $bad_path: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response '$expected', got '$resp_msg'")); # Do the NLST again; there are some reports that a first transfer # might succeed, but subsequent ones will fail. for (my $i = 0; $i < 3; $i++) { $res = $client->nlst($bad_path); if ($res) { die("NLST $bad_path succeeded unexpectedly"); } $resp_msg = $client->last_message(); $self->assert($expected eq $resp_msg, test_msg("Expected response '$expected', got '$resp_msg'")); } $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 20) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_frontend_backend_tls_abort { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyTLSEngine} = 'auto'; $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; $proxy_config->{ProxyTLSVerifyServer} = 'off'; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 netio:20 scoreboard:0 signal:0 proxy:20 proxy.netio:20 proxy.db:20 proxy.reverse:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20 tls:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_tls.c' => { TLSEngine => 'on', TLSLog => $setup->{log_file}, TLSRequired => 'on', TLSRSACertificateFile => $cert_file, TLSCACertificateFile => $ca_file, TLSOptions => 'NoSessionReuseRequired EnableDiags', TLSTimeoutHandshake => 5, TLSVerifyClient => 'off', TLSVerifyServer => 'off', }, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none TLSEngine on TLSLog $setup->{log_file} TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file TLSVerifyClient off TLSVerifyServer off TLSOptions EnableDiags NoSessionReuseRequired EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } require Net::FTPSSL; # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $ssl_opts = { SSL_verify_mode => $IO::Socket::SSL::SSL_VERIFY_NONE, }; my $client_opts = { Encryption => 'E', Port => $port, SSL_Client_Certificate => $ssl_opts, }; if ($ENV{TEST_VERBOSE}) { $client_opts->{Debug} = 1; } my $client = Net::FTPSSL->new('127.0.0.1', %$client_opts); unless ($client) { die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr()); } unless ($client->login($setup->{user}, $setup->{passwd})) { die("Can't login: " . $client->last_message()); } my $res = $client->_abort(); unless ($res) { die("ABOR failed unexpectedly: " . $client->last_message() . "(" . IO::Socket::SSL::errstr() . ")"); } my $resp_msg = $client->last_message(); my $expected = '226 Abort successful'; $self->assert($expected eq $resp_msg, test_msg("Expected response '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, 20) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_frontend_tls_json_peruser { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); my $proxy_hosts_path = File::Spec->rel2abs("$tmpdir/$user.json"); if (open(my $fh, "> $proxy_hosts_path")) { print $fh "[\n \"ftp://127.0.0.1:$vhost_port\"\n]\n"; unless (close($fh)) { die("Can't write $proxy_hosts_path: $!"); } } else { die("Can't open $proxy_hosts_path: $!"); } my $proxy_hosts_file = File::Spec->rel2abs("$tmpdir/%U.json"); # Make sure that mod_proxy correctly handles a list of backend servers # read from a file. $proxy_config->{ProxyReverseServers} = "file:$proxy_hosts_file"; $proxy_config->{ProxyReverseConnectPolicy} = 'PerUser'; $proxy_config->{ProxyTLSEngine} = 'off'; $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'proxy:20 proxy.db:20 proxy.reverse:20 proxy.tls:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_tls.c' => { TLSEngine => 'on', TLSLog => $log_file, TLSRequired => 'on', TLSRSACertificateFile => $cert_file, TLSCACertificateFile => $ca_file, TLSOptions => 'NoSessionReuseRequired', TLSTimeoutHandshake => 5, TLSVerifyClient => 'off', TLSVerifyServer => 'off', }, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none TLSEngine off TLSLog $log_file TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file TLSVerifyClient off TLSVerifyServer off TLSOptions NoSessionReuseRequired EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } require Net::FTPSSL; # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $ssl_opts = { SSL_verify_mode => $IO::Socket::SSL::SSL_VERIFY_NONE, }; my $client_opts = { Encryption => 'E', Port => $port, SSL_Client_Certificate => $ssl_opts, }; if ($ENV{TEST_VERBOSE}) { $client_opts->{Debug} = 1; } my $client = Net::FTPSSL->new('127.0.0.1', %$client_opts); unless ($client) { die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr()); } unless ($client->login($user, $passwd)) { die("Can't login: " . $client->last_message()); } my $res = $client->list(); unless ($res) { die("LIST failed unexpectedly: " . $client->last_message() . "(" . IO::Socket::SSL::errstr() . ")"); } my $resp_msg = $client->last_message(); my $expected = '226 Transfer complete'; $self->assert($expected eq $resp_msg, test_msg("Expected response '$expected', got '$resp_msg'")); # Do the LIST again; there are some reports that a first transfer # might succeed, but subsequent ones will fail. $res = $client->list(); unless ($res) { die("LIST failed unexpectedly: " . $client->last_message() . "(" . IO::Socket::SSL::errstr() . ")"); } $resp_msg = $client->last_message(); $client->quit(); $expected = '226 Transfer complete'; $self->assert($expected eq $resp_msg, test_msg("Expected response '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, 20) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_backend_tls_engine_auto_unavail { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyTLSEngine} = 'auto'; $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.netio:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_backend_tls_engine_auto_ftps_uri_unavail { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); # Note: deliberately use a URI here with the 'ftps' scheme, to indicate # that FTPS is REQUIRED for that server. $proxy_config->{ProxyReverseServers} = "ftps://127.0.0.1:$vhost_port"; $proxy_config->{ProxyTLSEngine} = 'auto'; $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.netio:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); eval { ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 0) }; unless ($@) { die("Connection succeeded unexpectedly"); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_backend_tls_engine_on_unavail { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyTLSEngine} = 'on'; $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.netio:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); eval { ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 0) }; unless ($@) { die("Connection succeeded unexpectedly"); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_backend_tls_engine_match_client_ftp { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyTLSEngine} = 'MatchClient'; $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.netio:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20 proxy.tls:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 0); $client->login($setup->{user}, $setup->{passwd}); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_config_backend_tls_engine_match_client_ftps_explicit { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyReverseConnectPolicy} = 'PerUser'; $proxy_config->{ProxyTLSEngine} = 'MatchClient'; $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.netio:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20 proxy.tls:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_tls.c' => { TLSEngine => 'on', TLSLog => $setup->{log_file}, TLSRequired => 'on', TLSRSACertificateFile => $cert_file, TLSCACertificateFile => $ca_file, }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none TLSEngine on TLSLog $setup->{log_file} TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file TLSOptions EnableDiags EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::FTPSSL; my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $client_opts = { Encryption => 'E', Port => $port, }; if ($ENV{TEST_VERBOSE}) { $client_opts->{Debug} = 1; } my $client = Net::FTPSSL->new('127.0.0.1', %$client_opts); unless ($client) { die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr()); } unless ($client->login($setup->{user}, $setup->{passwd})) { die("Can't login: " . $client->last_message()); } $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_config_backend_tls_engine_match_client_ftps_implicit { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyReverseConnectPolicy} = 'PerUser'; $proxy_config->{ProxyTLSEngine} = 'MatchClient'; $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.netio:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20 proxy.tls:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_tls.c' => { TLSEngine => 'on', TLSLog => $setup->{log_file}, TLSRequired => 'on', TLSRSACertificateFile => $cert_file, TLSCACertificateFile => $ca_file, TLSOptions => 'UseImplicitSSL', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none TLSEngine on TLSLog $setup->{log_file} TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file TLSOptions EnableDiags UseImplicitSSL EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::FTPSSL; my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $client_opts = { Encryption => 'I', Port => $port, }; if ($ENV{TEST_VERBOSE}) { $client_opts->{Debug} = 1; } my $client = Net::FTPSSL->new('127.0.0.1', %$client_opts); unless ($client) { die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr()); } unless ($client->login($setup->{user}, $setup->{passwd})) { die("Can't login: " . $client->last_message()); } $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_config_backend_tls_connect_policy_per_user { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $vhost_port2 = $vhost_port - 7; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyTLSEngine} = 'auto'; $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; $proxy_config->{ProxyRetryCount} = 1; $proxy_config->{ProxyTimeoutConnect} = '2sec'; $proxy_config->{ProxyReverseConnectPolicy} = 'PerUser'; $proxy_config->{ProxyReverseServers} = "ftp://127.0.0.1:$vhost_port ftp://127.0.0.1:$vhost_port2"; my $nbackends = 2; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 response:20 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.netio:20 proxy.reverse:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20 tls:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none TLSEngine on TLSLog $log_file TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file Port $vhost_port2 ServerName "Other Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TransferLog none WtmpLog off TLSEngine on TLSLog $log_file TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); for (my $i = 0; $i < $nbackends+1; $i++) { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 0); $client->login($user, $passwd); ftp_list($self, $client); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, 15) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_backend_tls_connect_policy_per_user_failed_unknown_ca { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $vhost_port2 = $vhost_port - 7; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyTLSEngine} = 'auto'; $proxy_config->{ProxyRetryCount} = 1; $proxy_config->{ProxyTimeoutConnect} = '2sec'; $proxy_config->{ProxyReverseConnectPolicy} = 'PerUser'; $proxy_config->{ProxyReverseServers} = "ftp://127.0.0.1:$vhost_port ftp://127.0.0.1:$vhost_port2"; $proxy_config->{ProxyTLSVerifyServer} = 'on'; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 response:20 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.netio:20 proxy.reverse:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20 tls:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none TLSEngine on TLSLog $log_file TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file Port $vhost_port2 ServerName "Other Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TransferLog none WtmpLog off TLSEngine on TLSLog $log_file TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 0); eval { $client->user($user) }; unless ($@) { die("USER succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Login incorrect.'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, 15) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_backend_tls_verify_server_off { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $vhost_port2 = $vhost_port - 7; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyTLSEngine} = 'auto'; $proxy_config->{ProxyTLSVerifyServer} = 'off'; $proxy_config->{ProxyRetryCount} = 1; $proxy_config->{ProxyTimeoutConnect} = '2sec'; $proxy_config->{ProxyReverseConnectPolicy} = 'PerUser'; $proxy_config->{ProxyReverseServers} = "ftp://127.0.0.1:$vhost_port ftp://127.0.0.1:$vhost_port2"; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 response:20 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.netio:20 proxy.reverse:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20 tls:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none TLSEngine on TLSLog $log_file TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file Port $vhost_port2 ServerName "Other Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off TransferLog none WtmpLog off TLSEngine on TLSLog $log_file TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 0); $client->login($user, $passwd); ftp_list($self, $client); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, 15) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } # TODO: Note that this test is used for manually reviewing the generated logs; # it does NOT currently fail if session caching fails (although it should). sub proxy_reverse_config_backend_tls_no_session_cache { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $cache_file = File::Spec->rel2abs("$tmpdir/tls-shmcache.dat"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyTLSEngine} = 'auto'; $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'NoSessionCache EnableDiags'; } else { $proxy_config->{ProxyTLSOptions} = 'NoSessionCache'; } my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.netio:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_tls_shmcache.c' => { TLSSessionCache => "shm:/file=$cache_file", }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none TLSEngine on TLSLog $log_file TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); for (my $i = 0; $i < 3; $i++) { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); $client->quit(); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_config_backend_tls_ctrl_use_direct_data_transfers_pasv { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $cache_file = File::Spec->rel2abs("$tmpdir/tls-shmcache.dat"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyOptions} = 'UseDirectDataTransfers'; $proxy_config->{ProxyTLSEngine} = 'auto'; $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $timeout_idle = 15; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.netio:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TimeoutIdle $timeout_idle TransferLog none TLSEngine on TLSLog $setup->{log_file} # Since we are using DSR, and the frontend client is not using TLS, # data transfers will fail if we require TLS on them. So only require # TLS on the control connection, and explicitly REJECT TLS on data # connections. TLSRequired ctrl+!data TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file TLSOptions EnableDiags EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1, 1); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->list_raw(); unless ($conn) { die("Failed to LIST: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 5); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_config_frontend_backend_tls_required_use_direct_data_transfers_pasv { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyOptions} = 'UseDirectDataTransfers'; $proxy_config->{ProxyTLSEngine} = 'auto'; $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $timeout_idle = 15; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.netio:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, SocketBindTight => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_tls.c' => { TLSEngine => 'on', TLSLog => $setup->{log_file}, TLSRequired => 'on', TLSRSACertificateFile => $cert_file, TLSCACertificateFile => $ca_file, TLSOptions => 'EnableDiags', TLSTimeoutHandshake => 5, TLSVerifyClient => 'off', TLSVerifyServer => 'off', }, }, Limit => { LOGIN => { DenyUser => $setup->{user}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TimeoutIdle $timeout_idle TransferLog none TLSEngine on TLSLog $setup->{log_file} TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file TLSVerifyClient off TLSOptions EnableDiags NoSessionReuseRequired EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } require Net::FTPSSL; # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $ssl_opts = { SSL_verify_mode => $IO::Socket::SSL::SSL_VERIFY_NONE, }; my $client_opts = { Encryption => 'E', Port => $port, SSL_Client_Certificate => $ssl_opts, }; if ($ENV{TEST_VERBOSE}) { $client_opts->{Debug} = 1; } my $client = Net::FTPSSL->new('127.0.0.1', %$client_opts); unless ($client) { die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr()); } unless ($client->login($setup->{user}, $setup->{passwd})) { die("Can't login: " . $client->last_message()); } my $res = $client->list(); unless ($res) { die("LIST failed unexpectedly: " . $client->last_message() . "(" . IO::Socket::SSL::errstr() . ")"); } my $resp_msg = $client->last_message(); my $expected = '226 Transfer complete'; $self->assert($expected eq $resp_msg, test_msg("Expected response '$expected', got '$resp_msg'")); # Do the LIST again; there are some reports that a first transfer # might succeed, but subsequent ones will fail. $res = $client->list(); unless ($res) { die("LIST failed unexpectedly: " . $client->last_message() . "(" . IO::Socket::SSL::errstr() . ")"); } $resp_msg = $client->last_message(); $client->quit(); $expected = '226 Transfer complete'; $self->assert($expected eq $resp_msg, test_msg("Expected response '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_proxy_protocol_v2_tlv_ssl { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyOptions} = 'UseProxyProtocolV2 UseProxyProtocolV2TLVs'; $proxy_config->{ProxyReverseConnectPolicy} = 'PerUser'; my $hostname = 'castaglia'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:10 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy_protocol:20 tls:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, SocketBindTight => 'on', # Include the ALPN, Authority, SSL TLVs in an ExtendedLog LogFormat => 'session "ALPN=%{note:mod_proxy_protocol.alpn} AUTHORITY=%{note:mod_proxy_protocol.authority} TLS_VERSION=%{note:mod_proxy_protocol.tls.version} TLS_CIPHER=%{note:mod_proxy_protocol.tls.cipher}"', ServerAlias => $hostname, IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_tls.c' => { TLSEngine => 'on', TLSLog => $setup->{log_file}, TLSRequired => 'on', TLSRSACertificateFile => $cert_file, TLSCACertificateFile => $ca_file, TLSOptions => 'EnableDiags', }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off TransferLog none WtmpLog off ProxyProtocolEngine on ProxyProtocolVersion haproxyV2 ExtendedLog $setup->{log_file} ALL session EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } require Net::FTPSSL; # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $ssl_opts = { SSL_hostname => $hostname, # Yes, this is a deliberate choice. Sigh. SSL_verifycn_scheme => 'none', }; my $client_opts = { Encryption => 'E', Port => $port, SSL_Client_Certificate => $ssl_opts, }; if ($ENV{TEST_VERBOSE}) { $client_opts->{Debug} = 1; } my $client = Net::FTPSSL->new('127.0.0.1', %$client_opts); unless ($client) { die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr()); } unless ($client->login($setup->{user}, $setup->{passwd})) { die("Can't login: " . $client->last_message()); } $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $setup->{log_file}")) { my $ok = 0; while (my $line = <$fh>) { chomp($line); if ($ENV{TEST_VERBOSE}) { print STDERR "# $line\n"; } if ($line =~ /^ALPN=ftps AUTHORITY=$hostname TLS_VERSION=.+ TLS_CIPHER=.+/) { $ok = 1; last; } } close($fh); $self->assert($ok, test_msg("Did not see expected ALPN, Authority, SSL TLVs log message")); } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup->{log_file}, $ex); } sub proxy_forward_frontend_tls_noproxyauth_login { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@host'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_tls.c' => { TLSEngine => 'on', TLSLog => $log_file, TLSRequired => 'on', TLSRSACertificateFile => $cert_file, TLSCACertificateFile => $ca_file, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } require Net::FTPSSL; # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $client_opts = { Encryption => 'E', Port => $port, }; if ($ENV{TEST_VERBOSE}) { $client_opts->{Debug} = 1; } my $client = Net::FTPSSL->new('127.0.0.1', %$client_opts); unless ($client) { die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr()); } unless ($client->login("$user\@127.0.0.1:$vhost_port", $passwd)) { die("Can't login: " . $client->last_message()); } $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_backend_tls_login { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@host'; $proxy_config->{ProxyTLSEngine} = 'on'; $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; $proxy_config->{ProxyRetryCount} = 1; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.forward:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 tls:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none TLSEngine on TLSLog $log_file TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } require Net::FTPSSL; # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login("$user\@127.0.0.1:$vhost_port", $passwd); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_backend_tls_implicit_login { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); # Skip this test on GitHub, as it fails unnecessarily there. return if $ENV{PROFTPD_TEST_CI} eq 'github'; my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $proxy_config = get_forward_proxy_config($tmpdir, $setup->{log_file}, 990); $proxy_config->{ProxyForwardMethod} = 'user@host'; $proxy_config->{ProxyTLSEngine} = 'on'; $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; $proxy_config->{ProxyTLSVerifyServer} = 'off'; $proxy_config->{ProxyRetryCount} = 1; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.forward:20 proxy.netio:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 netio:20 tls:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); require Net::FTPSSL; # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(3); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 10, 15); $client->login('demo@test.rebex.net:990', 'password'); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_forward_backend_tls_login_failed_unknown_ca { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@host'; $proxy_config->{ProxyTLSEngine} = 'on'; $proxy_config->{ProxyRetryCount} = 1; $proxy_config->{ProxyTLSVerifyServer} = 'on'; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.forward:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 tls:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none TLSEngine on TLSLog $log_file TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } require Net::FTPSSL; # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); eval { $client->user("$user\@127.0.0.1:$vhost_port") }; unless ($@) { die("Login succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $client->quit(); my $expected = 530; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Unable to connect to 127.0.0.1:\d+: Operation not permitted'; $self->assert(qr/$expected/, $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_config_backend_tls_engine_match_client_ftp { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@host'; $proxy_config->{ProxyTLSEngine} = 'MatchClient'; $proxy_config->{ProxyRetryCount} = 1; $proxy_config->{ProxyTLSVerifyServer} = 'on'; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.forward:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 tls:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none TLSEngine on TLSLog $setup->{log_file} TLSRequired off TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } require Net::FTPSSL; # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login("$setup->{user}\@127.0.0.1:$vhost_port", $setup->{passwd}); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_forward_config_backend_tls_engine_match_client_ftps_explicit { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@host'; $proxy_config->{ProxyTLSEngine} = 'MatchClient'; $proxy_config->{ProxyRetryCount} = 1; $proxy_config->{ProxyTLSVerifyServer} = 'off'; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.forward:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 tls:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_tls.c' => { TLSEngine => 'on', TLSLog => $setup->{log_file}, TLSRequired => 'on', TLSRSACertificateFile => $cert_file, TLSCACertificateFile => $ca_file, TLSOptions => 'EnableDiags', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none TLSEngine on TLSLog $setup->{log_file} TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } require Net::FTPSSL; # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $client_opts = { Encryption => 'E', Port => $port, }; if ($ENV{TEST_VERBOSE}) { $client_opts->{Debug} = 1; } my $client = Net::FTPSSL->new('127.0.0.1', %$client_opts); unless ($client) { die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr()); } unless ($client->login("$setup->{user}\@127.0.0.1:$vhost_port", $setup->{passwd})) { die("Can't login: " . $client->last_message()); } $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_forward_config_backend_tls_engine_match_client_ftps_implicit { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@host'; $proxy_config->{ProxyTLSEngine} = 'MatchClient'; $proxy_config->{ProxyRetryCount} = 1; $proxy_config->{ProxyTLSVerifyServer} = 'off'; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.forward:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 tls:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_tls.c' => { TLSEngine => 'on', TLSLog => $setup->{log_file}, TLSRequired => 'on', TLSRSACertificateFile => $cert_file, TLSCACertificateFile => $ca_file, TLSOptions => 'EnableDiags UseImplicitSSL', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none TLSEngine on TLSLog $setup->{log_file} TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file TLSOptions UseImplicitSSL EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } require Net::FTPSSL; # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $client_opts = { Encryption => 'I', Port => $port, }; if ($ENV{TEST_VERBOSE}) { $client_opts->{Debug} = 1; } my $client = Net::FTPSSL->new('127.0.0.1', %$client_opts); unless ($client) { die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr()); } unless ($client->login("$setup->{user}\@127.0.0.1:$vhost_port", $setup->{passwd})) { die("Can't login: " . $client->last_message()); } $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_forward_backend_tls_list_pasv { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@host'; $proxy_config->{ProxyTLSEngine} = 'on'; $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; $proxy_config->{ProxyRetryCount} = 1; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 netio:20 scoreboard:0 signal:0 proxy:20 proxy.forward:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 tls:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none TLSEngine on TLSLog $log_file TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } require Net::FTPSSL; # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login("$user\@127.0.0.1:$vhost_port", $passwd); for (my $i = 0; $i < 3; $i++) { ftp_list($self, $client, 1); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_frontend_backend_tls_login_after_host { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $host = 'www.castaglia.org'; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.forward:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 tls:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, DefaultServer => 'on', ServerName => '"Default Server"', SocketBindTight => 'on', IfModules => { 'mod_tls.c' => { TLSEngine => 'on', TLSLog => $log_file, TLSRequired => 'on', TLSRSACertificateFile => $cert_file, TLSCACertificateFile => $ca_file, TLSOptions => 'NoSessionReuseRequired EnableDiags', }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { my $tables_dir = File::Spec->rel2abs("$tmpdir/var/proxy"); print $fh < ProxyTables $tables_dir From 127.0.0.1 ProxyForwardEnabled on Port $port ServerAlias $host ServerName "Namebased Server" DelayEngine off ProxyEngine on ProxyLog $log_file ProxyRole forward ProxyForwardMethod user\@host ProxyRetryCount 1 ProxyTLSEngine on ProxyTLSCACertificateFile $ca_file ProxyTLSOptions EnableDiags ProxyTLSVerifyServer off TLSEngine on TLSLog $log_file TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file TLSOptions NoSessionReuseRequired Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none TLSEngine on TLSLog $log_file TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file TLSOptions NoSessionReuseRequired EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } require Net::FTPSSL; # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $ssl_opts = { SSL_hostname => $host, SSL_ca_file => $ca_file, # Yes, this is a deliberate choice. Sigh. SSL_verifycn_scheme => 'none', }; my $client_opts = { Encryption => 'E', Port => $port, SSL_Client_Certificate => $ssl_opts, }; if ($ENV{TEST_VERBOSE}) { $client_opts->{Debug} = 1; } my $client = Net::FTPSSL->new('127.0.0.1', %$client_opts); unless ($client) { die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr()); } unless ($client->quot('HOST', $host)) { die("HOST failed: " . $client->last_message()); } unless ($client->login("$user\@127.0.0.1:$vhost_port", $passwd)) { die("Can't login: " . $client->last_message()); } my $res = $client->list(); unless ($res) { die("LIST failed unexpectedly: " . $client->last_message() . "(" . IO::Socket::SSL::errstr() . ")"); } my $resp_msg = $client->last_message(); $client->quit(); my $expected = '226 Transfer complete'; $self->assert($expected eq $resp_msg, test_msg("Expected response '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_forward_frontend_backend_tls_list_pasv { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@host'; $proxy_config->{ProxyTLSEngine} = 'on'; $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; $proxy_config->{ProxyRetryCount} = 1; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.forward:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 tls:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_tls.c' => { TLSEngine => 'on', TLSLog => $log_file, TLSRequired => 'on', TLSRSACertificateFile => $cert_file, TLSCACertificateFile => $ca_file, TLSOptions => 'NoSessionReuseRequired EnableDiags', }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none TLSEngine on TLSLog $log_file TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } require Net::FTPSSL; # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $ssl_opts = { SSL_ca_file => $ca_file, }; my $client_opts = { Encryption => 'E', Port => $port, SSL_Client_Certificate => $ssl_opts, }; if ($ENV{TEST_VERBOSE}) { $client_opts->{Debug} = 1; } my $client = Net::FTPSSL->new('127.0.0.1', %$client_opts); unless ($client) { die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr()); } unless ($client->login("$user\@127.0.0.1:$vhost_port", $passwd)) { die("Can't login: " . $client->last_message()); } my $res = $client->list(); unless ($res) { die("LIST failed unexpectedly: " . $client->last_message() . "(" . IO::Socket::SSL::errstr() . ")"); } my $resp_msg = $client->last_message(); my $expected = '226 Transfer complete'; $self->assert($expected eq $resp_msg, test_msg("Expected response '$expected', got '$resp_msg'")); # Do the LIST again; there are some reports that a first transfer # might succeed, but subsequent ones will fail. for (my $i = 0; $i < 3; $i++) { $res = $client->list(); unless ($res) { die("LIST failed unexpectedly: " . $client->last_message() . "(" . IO::Socket::SSL::errstr() . ")"); } $resp_msg = $client->last_message(); $expected = '226 Transfer complete'; $self->assert($expected eq $resp_msg, test_msg("Expected response '$expected', got '$resp_msg'")); } $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub forward_frontend_plain_backend_tls_tls_xfer_policy { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $use_port = shift; $use_port = 0 unless defined($use_port); my $tls_xfer_policy = shift; my $use_upload = shift; my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@host'; $proxy_config->{ProxyTLSEngine} = 'on'; $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; $proxy_config->{ProxyTLSTransferProtectionPolicy} = $tls_xfer_policy; $proxy_config->{ProxyTLSVerifyServer} = 'off'; $proxy_config->{ProxyRetryCount} = 1; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $backend_tls_required = 'ctrl'; if ($tls_xfer_policy =~ /required/i) { $backend_tls_required = 'on'; } my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.forward:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 tls:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off AllowOverwrite on WtmpLog off TransferLog none TLSEngine on TLSLog $setup->{log_file} TLSRequired $backend_tls_required TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file TLSOptions EnableDiags EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } require Net::FTPSSL; # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(5); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, $use_port, 5); $client->login("$setup->{user}\@127.0.0.1:$vhost_port", $setup->{passwd}); for (my $i = 0; $i < 3; $i++) { if ($use_upload) { ftp_upload($self, $client, 1); } else { ftp_list($self, $client, 1); } } $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub forward_frontend_backend_tls_tls_xfer_policy { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $use_port = shift; $use_port = 0 unless defined($use_port); my $tls_xfer_policy = shift; my $use_upload = shift; my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); if ($use_port) { $proxy_config->{ProxyDataTransferPolicy} = 'PORT'; } $proxy_config->{ProxyForwardMethod} = 'user@host'; $proxy_config->{ProxyTLSEngine} = 'on'; $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; $proxy_config->{ProxyTLSTransferProtectionPolicy} = $tls_xfer_policy; $proxy_config->{ProxyTLSVerifyServer} = 'off'; $proxy_config->{ProxyRetryCount} = 1; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $backend_tls_required = 'ctrl'; if ($tls_xfer_policy =~ /required/i) { $backend_tls_required = 'on'; } my $test_file = File::Spec->rel2abs("$tmpdir/test.dat"); if (open(my $fh, "> $test_file")) { print $fh 'AbCdEfGh' x 81920; unless (close($fh)) { die("Can't write $test_file: $!"); } } else { die("Can't open $test_file: $!"); } my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.forward:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 tls:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_tls.c' => { TLSEngine => 'on', TLSLog => $setup->{log_file}, TLSRequired => 'ctrl', TLSRSACertificateFile => $cert_file, TLSCACertificateFile => $ca_file, TLSCipherSuite => 'RSA', TLSOptions => 'NoSessionReuseRequired EnableDiags', }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off AllowOverwrite on WtmpLog off TransferLog none TLSEngine on TLSLog $setup->{log_file} TLSRequired $backend_tls_required TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file TLSOptions EnableDiags NoSessionReuseRequired EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } require Net::FTPSSL; # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(3); my $ssl_opts = { SSL_ca_file => $ca_file, }; my $client_opts = { Encryption => 'E', Port => $port, SSL_Client_Certificate => $ssl_opts, # Request protection only on the control channel DataProtLevel => 'C', }; if ($ENV{TEST_VERBOSE}) { $client_opts->{Debug} = 1; } my $client = Net::FTPSSL->new('127.0.0.1', %$client_opts); unless ($client) { die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr()); } unless ($client->login("$setup->{user}\@127.0.0.1:$vhost_port", $setup->{passwd})) { die("Can't login: " . $client->last_message()); } my $res; if ($use_upload) { $res = $client->put($test_file, '/dev/null'); unless ($res) { die("STOR failed unexpectedly: " . $client->last_message() . "(" . IO::Socket::SSL::errstr() . ")"); } } else { $res = $client->list(); unless ($res) { die("LIST failed unexpectedly: " . $client->last_message() . "(" . IO::Socket::SSL::errstr() . ")"); } } my $resp_msg = $client->last_message(); my $expected = '226 Transfer complete'; $self->assert($expected eq $resp_msg, test_msg("Expected response '$expected', got '$resp_msg'")); # Do the data transfer again; there are some reports that a first transfer # might succeed, but subsequent ones will fail. for (my $i = 0; $i < 3; $i++) { if ($use_upload) { $res = $client->put($test_file, '/dev/null'); unless ($res) { die("STOR failed unexpectedly: " . $client->last_message() . "(" . IO::Socket::SSL::errstr() . ")"); } } else { $res = $client->list(); unless ($res) { die("LIST failed unexpectedly: " . $client->last_message() . "(" . IO::Socket::SSL::errstr() . ")"); } } $resp_msg = $client->last_message(); $expected = '226 Transfer complete'; $self->assert($expected eq $resp_msg, test_msg("Expected response '$expected', got '$resp_msg'")); } $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_forward_frontend_plain_backend_tls_list_pasv_tls_xfer_policy_required { my $self = shift; return forward_frontend_plain_backend_tls_tls_xfer_policy($self, 0, 'Required', 0); } sub proxy_forward_frontend_plain_backend_tls_list_port_tls_xfer_policy_required { my $self = shift; return forward_frontend_plain_backend_tls_tls_xfer_policy($self, 1, 'Required', 0); } sub proxy_forward_frontend_plain_backend_tls_stor_pasv_tls_xfer_policy_required { my $self = shift; return forward_frontend_plain_backend_tls_tls_xfer_policy($self, 0, 'Required', 1); } sub proxy_forward_frontend_plain_backend_tls_stor_port_tls_xfer_policy_required { my $self = shift; return forward_frontend_plain_backend_tls_tls_xfer_policy($self, 1, 'Required', 1); } sub proxy_forward_frontend_backend_tls_list_pasv_tls_xfer_policy_required { my $self = shift; return forward_frontend_backend_tls_tls_xfer_policy($self, 0, 'Required', 0); } sub proxy_forward_frontend_backend_tls_list_port_tls_xfer_policy_required { my $self = shift; return forward_frontend_backend_tls_tls_xfer_policy($self, 1, 'Required', 0); } sub proxy_forward_frontend_backend_tls_stor_pasv_tls_xfer_policy_required { my $self = shift; return forward_frontend_backend_tls_tls_xfer_policy($self, 0, 'Required', 1); } sub proxy_forward_frontend_backend_tls_stor_port_tls_xfer_policy_required { my $self = shift; return forward_frontend_backend_tls_tls_xfer_policy($self, 1, 'Required', 1); } sub proxy_forward_frontend_plain_backend_tls_list_pasv_tls_xfer_policy_clear { my $self = shift; return forward_frontend_plain_backend_tls_tls_xfer_policy($self, 0, 'Clear', 0); } sub proxy_forward_frontend_plain_backend_tls_list_port_tls_xfer_policy_clear { my $self = shift; return forward_frontend_plain_backend_tls_tls_xfer_policy($self, 1, 'Clear', 0); } sub proxy_forward_frontend_plain_backend_tls_stor_pasv_tls_xfer_policy_clear { my $self = shift; return forward_frontend_plain_backend_tls_tls_xfer_policy($self, 0, 'Clear', 1); } sub proxy_forward_frontend_plain_backend_tls_stor_port_tls_xfer_policy_clear { my $self = shift; return forward_frontend_plain_backend_tls_tls_xfer_policy($self, 1, 'Clear', 1); } sub proxy_forward_frontend_backend_tls_list_pasv_tls_xfer_policy_clear { my $self = shift; return forward_frontend_backend_tls_tls_xfer_policy($self, 0, 'Clear', 0); } sub proxy_forward_frontend_backend_tls_list_port_tls_xfer_policy_clear { my $self = shift; return forward_frontend_backend_tls_tls_xfer_policy($self, 1, 'Clear', 0); } sub proxy_forward_frontend_backend_tls_stor_pasv_tls_xfer_policy_clear { my $self = shift; return forward_frontend_backend_tls_tls_xfer_policy($self, 0, 'Clear', 1); } sub proxy_forward_frontend_backend_tls_stor_port_tls_xfer_policy_clear { my $self = shift; return forward_frontend_backend_tls_tls_xfer_policy($self, 1, 'Clear', 1); } sub proxy_forward_frontend_plain_backend_tls_list_pasv_tls_xfer_policy_client { my $self = shift; return forward_frontend_plain_backend_tls_tls_xfer_policy($self, 0, 'Client', 0); } sub proxy_forward_frontend_plain_backend_tls_list_port_tls_xfer_policy_client { my $self = shift; return forward_frontend_plain_backend_tls_tls_xfer_policy($self, 1, 'Client', 0); } sub proxy_forward_frontend_plain_backend_tls_stor_pasv_tls_xfer_policy_client { my $self = shift; return forward_frontend_plain_backend_tls_tls_xfer_policy($self, 0, 'Client', 1); } sub proxy_forward_frontend_plain_backend_tls_stor_port_tls_xfer_policy_client { my $self = shift; return forward_frontend_plain_backend_tls_tls_xfer_policy($self, 1, 'Client', 1); } sub proxy_forward_frontend_backend_tls_list_pasv_tls_xfer_policy_client { my $self = shift; return forward_frontend_backend_tls_tls_xfer_policy($self, 0, 'Client', 0); } sub proxy_forward_frontend_backend_tls_list_port_tls_xfer_policy_client { my $self = shift; return forward_frontend_backend_tls_tls_xfer_policy($self, 1, 'Client', 0); } sub proxy_forward_frontend_backend_tls_stor_pasv_tls_xfer_policy_client { my $self = shift; return forward_frontend_backend_tls_tls_xfer_policy($self, 0, 'Client', 1); } sub proxy_forward_frontend_backend_tls_stor_port_tls_xfer_policy_client { my $self = shift; return forward_frontend_backend_tls_tls_xfer_policy($self, 1, 'Client', 1); } sub proxy_forward_config_backend_tls_ctrl_use_direct_data_transfers_pasv { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@host'; $proxy_config->{ProxyTLSEngine} = 'on'; $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; $proxy_config->{ProxyRetryCount} = 1; $proxy_config->{ProxyOptions} = 'UseDirectDataTransfers'; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.forward:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 tls:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none TLSEngine on TLSLog $setup->{log_file} # Since we are using DSR, and the frontend client is not using TLS, # data transfers will fail if we require TLS on them. So only require # TLS on the control connection, and explicitly REJECT TLS on data # connections. TLSRequired ctrl+!data TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1, 1); $client->login("$setup->{user}\@127.0.0.1:$vhost_port", $setup->{passwd}); my $conn = $client->list_raw(); unless ($conn) { die("Failed to LIST: " . $client->response_code() . ' ' . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 5); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub proxy_forward_config_frontend_backend_tls_required_use_direct_data_transfers_pasv { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 17; my $proxy_config = get_forward_proxy_config($tmpdir, $setup->{log_file}, $vhost_port); $proxy_config->{ProxyForwardMethod} = 'user@host'; $proxy_config->{ProxyTLSEngine} = 'on'; $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; $proxy_config->{ProxyRetryCount} = 1; $proxy_config->{ProxyOptions} = 'UseDirectDataTransfers'; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.forward:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 tls:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_tls.c' => { TLSEngine => 'on', TLSLog => $setup->{log_file}, TLSRequired => 'on', TLSRSACertificateFile => $cert_file, TLSCACertificateFile => $ca_file, TLSOptions => 'NoSessionReuseRequired EnableDiags', }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $setup->{auth_user_file} AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none TLSEngine on TLSLog $setup->{log_file} TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file TLSOptions NoSessionReuseRequired EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } require Net::FTPSSL; # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); my $ssl_opts = { SSL_ca_file => $ca_file, }; my $client_opts = { Encryption => 'E', Port => $port, SSL_Client_Certificate => $ssl_opts, }; if ($ENV{TEST_VERBOSE}) { $client_opts->{Debug} = 1; } my $client = Net::FTPSSL->new('127.0.0.1', %$client_opts); unless ($client) { die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr()); } unless ($client->login("$setup->{user}\@127.0.0.1:$vhost_port", $setup->{passwd})) { die("Can't login: " . $client->last_message()); } my $res = $client->list(); unless ($res) { die("LIST failed unexpectedly: " . $client->last_message() . "(" . IO::Socket::SSL::errstr() . ")"); } my $resp_msg = $client->last_message(); $client->quit(); my $expected = '226 Transfer complete'; $self->assert($expected eq $resp_msg, test_msg("Expected response '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } 1; proftpd-mod_proxy-0.9.5/t/lib/ProFTPD/Tests/Modules/mod_proxy/tls/000077500000000000000000000000001475737016700250255ustar00rootroot00000000000000proftpd-mod_proxy-0.9.5/t/lib/ProFTPD/Tests/Modules/mod_proxy/tls/redis.pm000066400000000000000000000256061475737016700265020ustar00rootroot00000000000000package ProFTPD::Tests::Modules::mod_proxy::tls::redis; use lib qw(t/lib); use base qw(ProFTPD::TestSuite::Child); use strict; use Carp; use File::Copy; use File::Path qw(mkpath); use File::Spec; use IO::Handle; use IO::Socket::INET; use Time::HiRes qw(gettimeofday tv_interval usleep); use ProFTPD::TestSuite::FTP; use ProFTPD::TestSuite::Utils qw(:auth :config :running :test :testsuite); $| = 1; my $order = 0; my $TESTS = { proxy_reverse_backend_tls_login_redis_cached_session => { order => ++$order, test_class => [qw(forking mod_redis mod_tls mod_tls_shmcache reverse)], }, proxy_reverse_backend_tls_login_redis_cached_ticket => { order => ++$order, test_class => [qw(forking mod_redis mod_tls reverse)], }, }; sub new { return shift()->SUPER::new(@_); } sub list_tests { return testsuite_get_runnable_tests($TESTS); } sub get_reverse_proxy_config { my $tmpdir = shift; my $log_file = shift; my $vhost_port = shift; my $table_dir = File::Spec->rel2abs("$tmpdir/var/proxy"); my $config = { ProxyEngine => 'on', ProxyLog => $log_file, ProxyReverseServers => "ftp://127.0.0.1:$vhost_port", ProxyRole => 'reverse', ProxyTables => $table_dir, }; return $config; } # TODO: Note that this test is used for manually reviewing the generated logs; # it does NOT currently fail if session caching fails (although it should). sub proxy_reverse_backend_tls_login_redis_cached_session { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $cache_file = File::Spec->rel2abs("$tmpdir/tls-shmcache.dat"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyDatastore} = 'Redis mod_proxy.testsuite.'; $proxy_config->{ProxyTLSEngine} = 'auto'; $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $redis_server = '127.0.0.1'; if (defined($ENV{REDIS_HOST})) { $redis_server = $ENV{REDIS_HOST}; } my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 redis:20 proxy:20 proxy.db:20 proxy.netio:20 proxy.tls:20 proxy.tls.redis:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_redis.c' => { RedisEngine => 'on', RedisServer => "$redis_server:6379", RedisTimeouts => '2000 500', RedisLog => $log_file, }, 'mod_tls_shmcache.c' => { TLSSessionCache => "shm:/file=$cache_file", }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none TLSEngine on TLSLog $log_file TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); for (my $i = 0; $i < 3; $i++) { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); $client->quit(); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } # TODO: Note that this test is used for manually reviewing the generated logs; # it does NOT currently fail if ticket caching fails (although it should). sub proxy_reverse_backend_tls_login_redis_cached_ticket { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/proxy.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); my $cache_file = File::Spec->rel2abs("$tmpdir/tls-shmcache.dat"); my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); $proxy_config->{ProxyDatastore} = 'Redis mod_proxy.testsuite.'; $proxy_config->{ProxyTLSEngine} = 'auto'; $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags NoSessionCache'; } my $redis_server = '127.0.0.1'; if (defined($ENV{REDIS_HOST})) { $redis_server = $ENV{REDIS_HOST}; } my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 redis:20 proxy:20 proxy.db:20 proxy.netio:20 proxy.tls:20 proxy.tls.redis:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20 tls:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_redis.c' => { RedisEngine => 'on', RedisServer => "$redis_server:6379", RedisTimeouts => '2000 500', RedisLog => $log_file, }, }, Limit => { LOGIN => { DenyUser => $user, }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); if (open(my $fh, ">> $config_file")) { print $fh < # Recommended practice is to diable server-side session caching entirely, # if you are going to use client-side session tickets. Why? It # reduces the number of places where a session's master secret are held # in memory for "long" periods of time. TLSSessionCache off Port $vhost_port ServerName "Real Server" AuthUserFile $auth_user_file AuthGroupFile $auth_group_file AuthOrder mod_auth_file.c AllowOverride off WtmpLog off TransferLog none TLSEngine on TLSLog $log_file TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file TLSSessionTickets on TLSStapling on TLSOptions EnableDiags EOC unless (close($fh)) { die("Can't write $config_file: $!"); } } else { die("Can't open $config_file: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(2); for (my $i = 0; $i < 3; $i++) { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, undef, 1); $client->login($user, $passwd); $client->list(); $client->quit(); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } 1; proftpd-mod_proxy-0.9.5/t/modules/000077500000000000000000000000001475737016700171555ustar00rootroot00000000000000proftpd-mod_proxy-0.9.5/t/modules/mod_proxy.t000066400000000000000000000002651475737016700213650ustar00rootroot00000000000000#!/usr/bin/env perl use lib qw(t/lib); use strict; use Test::Unit::HarnessUnit; $| = 1; my $r = Test::Unit::HarnessUnit->new(); $r->start("ProFTPD::Tests::Modules::mod_proxy"); proftpd-mod_proxy-0.9.5/t/modules/mod_proxy/000077500000000000000000000000001475737016700211755ustar00rootroot00000000000000proftpd-mod_proxy-0.9.5/t/modules/mod_proxy/ban.t000066400000000000000000000002721475737016700221230ustar00rootroot00000000000000#!/usr/bin/env perl use lib qw(t/lib); use strict; use Test::Unit::HarnessUnit; $| = 1; my $r = Test::Unit::HarnessUnit->new(); $r->start("ProFTPD::Tests::Modules::mod_proxy::ban"); proftpd-mod_proxy-0.9.5/t/modules/mod_proxy/redis.t000066400000000000000000000002741475737016700224730ustar00rootroot00000000000000#!/usr/bin/env perl use lib qw(t/lib); use strict; use Test::Unit::HarnessUnit; $| = 1; my $r = Test::Unit::HarnessUnit->new(); $r->start("ProFTPD::Tests::Modules::mod_proxy::redis"); proftpd-mod_proxy-0.9.5/t/modules/mod_proxy/reverse/000077500000000000000000000000001475737016700226505ustar00rootroot00000000000000proftpd-mod_proxy-0.9.5/t/modules/mod_proxy/reverse/ipv6.t000066400000000000000000000003041475737016700237160ustar00rootroot00000000000000#!/usr/bin/env perl use lib qw(t/lib); use strict; use Test::Unit::HarnessUnit; $| = 1; my $r = Test::Unit::HarnessUnit->new(); $r->start("ProFTPD::Tests::Modules::mod_proxy::reverse::ipv6"); proftpd-mod_proxy-0.9.5/t/modules/mod_proxy/sql.t000066400000000000000000000002721475737016700221620ustar00rootroot00000000000000#!/usr/bin/env perl use lib qw(t/lib); use strict; use Test::Unit::HarnessUnit; $| = 1; my $r = Test::Unit::HarnessUnit->new(); $r->start("ProFTPD::Tests::Modules::mod_proxy::sql"); proftpd-mod_proxy-0.9.5/t/modules/mod_proxy/ssh.t000066400000000000000000000002721475737016700221600ustar00rootroot00000000000000#!/usr/bin/env perl use lib qw(t/lib); use strict; use Test::Unit::HarnessUnit; $| = 1; my $r = Test::Unit::HarnessUnit->new(); $r->start("ProFTPD::Tests::Modules::mod_proxy::ssh"); proftpd-mod_proxy-0.9.5/t/modules/mod_proxy/ssh/000077500000000000000000000000001475737016700217725ustar00rootroot00000000000000proftpd-mod_proxy-0.9.5/t/modules/mod_proxy/ssh/redis.t000066400000000000000000000003011475737016700232570ustar00rootroot00000000000000#!/usr/bin/env perl use lib qw(t/lib); use strict; use Test::Unit::HarnessUnit; $| = 1; my $r = Test::Unit::HarnessUnit->new(); $r->start("ProFTPD::Tests::Modules::mod_proxy::ssh::redis"); proftpd-mod_proxy-0.9.5/t/modules/mod_proxy/tls.t000066400000000000000000000002721475737016700221650ustar00rootroot00000000000000#!/usr/bin/env perl use lib qw(t/lib); use strict; use Test::Unit::HarnessUnit; $| = 1; my $r = Test::Unit::HarnessUnit->new(); $r->start("ProFTPD::Tests::Modules::mod_proxy::tls"); proftpd-mod_proxy-0.9.5/t/modules/mod_proxy/tls/000077500000000000000000000000001475737016700217775ustar00rootroot00000000000000proftpd-mod_proxy-0.9.5/t/modules/mod_proxy/tls/redis.t000066400000000000000000000003011475737016700232640ustar00rootroot00000000000000#!/usr/bin/env perl use lib qw(t/lib); use strict; use Test::Unit::HarnessUnit; $| = 1; my $r = Test::Unit::HarnessUnit->new(); $r->start("ProFTPD::Tests::Modules::mod_proxy::tls::redis"); proftpd-mod_proxy-0.9.5/tests.pl000066400000000000000000000064611475737016700167500ustar00rootroot00000000000000#!/usr/bin/env perl use strict; use Cwd qw(abs_path); use File::Spec; use Getopt::Long; use Test::Harness qw(&runtests $verbose); my $opts = {}; GetOptions($opts, 'h|help', 'C|class=s@', 'K|keep-tmpfiles', 'F|file-pattern=s', 'V|verbose'); if ($opts->{h}) { usage(); } if ($opts->{K}) { $ENV{KEEP_TMPFILES} = 1; } $verbose = 1; if ($opts->{V}) { $ENV{TEST_VERBOSE} = 1; } # We use this, rather than use(), since use() is equivalent to a BEGIN # block, and we want the module to be loaded at run-time. if ($ENV{PROFTPD_TEST_DIR}) { push(@INC, "$ENV{PROFTPD_TEST_DIR}/t/lib"); } my $test_dir = (File::Spec->splitpath(abs_path(__FILE__)))[1]; push(@INC, "$test_dir/t/lib"); require ProFTPD::TestSuite::Utils; import ProFTPD::TestSuite::Utils qw(:testsuite); # This is to handle the case where this tests.pl script might be # being used to run test files other than those that ship with proftpd, # e.g. to run the tests that come with third-party modules. unless (defined($ENV{PROFTPD_TEST_BIN})) { $ENV{PROFTPD_TEST_BIN} = File::Spec->catfile($test_dir, '..', 'proftpd'); } $| = 1; my $test_files; if (scalar(@ARGV) > 0) { $test_files = [@ARGV]; } else { $test_files = [qw( t/modules/mod_proxy.t )]; # Now interrogate the build to see which module/feature-specific test files # should be added to the list. my $order = 0; my $FEATURE_TESTS = { 't/modules/mod_proxy/ban.t' => { order => ++$order, test_class => [qw(mod_ban mod_proxy)], }, 't/modules/mod_proxy/redis.t' => { order => ++$order, test_class => [qw(feature_redis mod_proxy)], }, 't/modules/mod_proxy/reverse/ipv6.t' => { order => ++$order, test_class => [qw(feature_ipv6 mod_proxy)], }, 't/modules/mod_proxy/sql.t' => { order => ++$order, test_class => [qw(mod_proxy mod_sql mod_sql_sqlite)], }, 't/modules/mod_proxy/ssh.t' => { order => ++$order, test_class => [qw(mod_proxy mod_sftp)], }, 't/modules/mod_proxy/tls.t' => { order => ++$order, test_class => [qw(mod_proxy mod_tls)], }, 't/modules/mod_proxy/tls/redis.t' => { order => ++$order, test_class => [qw(feature_redis mod_proxy mod_tls)], }, }; my @feature_tests = testsuite_get_runnable_tests($FEATURE_TESTS); my $feature_ntests = scalar(@feature_tests); if ($feature_ntests > 1 || ($feature_ntests == 1 && $feature_tests[0] ne 'testsuite_empty_test')) { push(@$test_files, @feature_tests); } } $ENV{PROFTPD_TEST} = 1; if (defined($opts->{C})) { $ENV{PROFTPD_TEST_ENABLE_CLASS} = join(':', @{ $opts->{C} }); } else { # Disable all 'flaky', 'inprogress' and 'slow' tests by default $ENV{PROFTPD_TEST_DISABLE_CLASS} = 'flaky:inprogress:slow'; } if (defined($opts->{F})) { # Using the provided string as a regex, and run only the tests whose # files match the pattern my $file_pattern = $opts->{F}; my $filtered_files = []; foreach my $test_file (@$test_files) { if ($test_file =~ /$file_pattern/) { push(@$filtered_files, $test_file); } } $test_files = $filtered_files; } runtests(@$test_files) if scalar(@$test_files) > 0; exit 0; sub usage { print STDOUT <