pax_global_header00006660000000000000000000000064140207403070014507gustar00rootroot0000000000000052 comment=85476a91b7180c41484a194f326ec8310ef488e3 proftpd-mod_proxy-0.8/000077500000000000000000000000001402074030700150525ustar00rootroot00000000000000proftpd-mod_proxy-0.8/.gitattributes000066400000000000000000000000621402074030700177430ustar00rootroot00000000000000*.pl linguist-language=C *.pm linguist-language=C proftpd-mod_proxy-0.8/.gitignore000066400000000000000000000002321402074030700170370ustar00rootroot00000000000000configure 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.8/.travis.yml000066400000000000000000000057361402074030700171760ustar00rootroot00000000000000language: c compiler: - gcc - clang install: - sudo apt-get update -qq # for unit tests - sudo apt-get install -y check - sudo apt-get install -y libsubunit-dev # for Redis support - sudo apt-get install -y libhiredis-dev # for OpenSSL support - sudo apt-get install -y libssl-dev # for mod_sql_sqlite - sudo apt-get install -y libsqlite3-dev sqlite3 # for static code analysis # - sudo apt-get install -y cppcheck # - sudo apt-get install -y rats # for test code coverage - sudo apt-get install -y lcov - gem install coveralls-lcov # For reference - sqlite3 --version # For HTML validation - sudo apt-get install -y tidy before_script: - cd ${TRAVIS_BUILD_DIR} - lcov --directory . --zerocounters script: # - find . -type f -name "*.c" -print | grep -v t\/ | xargs cppcheck 2>&1 # - find . -type f -name "*.c" -print | grep -v t\/ | xargs rats --language=c # git check - | 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 - git clone --depth 10 https://github.com/proftpd/proftpd.git - mkdir -p proftpd/contrib/mod_proxy/ - cp -R include/ proftpd/contrib/mod_proxy/include - cp -R lib/ proftpd/contrib/mod_proxy/lib/ - cp -R t/ proftpd/contrib/mod_proxy/t/ - cp mod_proxy.* proftpd/contrib/mod_proxy/ - cp Makefile.in proftpd/contrib/mod_proxy/ - cp config* proftpd/contrib/mod_proxy/ - cp install-sh proftpd/contrib/mod_proxy/ - cd proftpd # First, a build without mod_tls and without Redis - ./configure LIBS='-lm -lsubunit -lrt -pthread' --enable-devel=coverage --enable-tests --with-module=mod_proxy - make # Next, a build with Redis, without mod_tls - make clean - ./configure LIBS='-lm -lsubunit -lrt -pthread' --enable-devel=coverage --enable-redis --enable-tests --with-module=mod_proxy - make # Next, a build WITH mod_tls (as a shared module) - make clean - ./configure LIBS='-lm -lsubunit -lrt -pthread' --enable-devel=coverage --enable-dso --enable-redis --enable-tests --with-shared=mod_tls:mod_proxy - make # Last, a build with mod_tls (as a static module), and run the tests - make clean - ./configure LIBS='-lm -lsubunit -lrt -pthread' --enable-devel=coverage --enable-redis --enable-tests --with-modules=mod_tls:mod_proxy - make - cd contrib/mod_proxy && make TEST_VERBOSE=1 check && cd ../../ after_success: - cd ${TRAVIS_BUILD_DIR} # capture the test coverage info - lcov --ignore-errors gcov,source --base-directory ${TRAVIS_BUILD_DIR}/proftpd/contrib/mod_proxy --directory proftpd/contrib/mod_proxy --capture --output-file coverage.info # filter out system and test code - lcov --remove coverage.info 'api/*' 't/*' '/usr/*' --output-file coverage.info # debug before upload - lcov --list coverage.info # upload coverage info to coveralls - coveralls-lcov coverage.info # Run some validation on the HTML docs - tidy -errors -omit -q mod_proxy.html proftpd-mod_proxy-0.8/Makefile.in000066400000000000000000000057051402074030700171260ustar00rootroot00000000000000top_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/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 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/tls.lo \ lib/proxy/tls/db.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 # 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) $(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/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.8/README.md000066400000000000000000000011621402074030700163310ustar00rootroot00000000000000proftpd-mod_proxy ================= Status ------ [![Build Status](https://travis-ci.org/Castaglia/proftpd-mod_proxy.svg?branch=master)](https://travis-ci.org/Castaglia/proftpd-mod_proxy) [![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.8/cacerts.pem000066400000000000000000007705701402074030700172210ustar00rootroot00000000000000## ## Bundle of CA Root Certificates ## ## Certificate data from Mozilla as of: Fri Jul 31 17:12:05 2015 ## ## 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: ## http://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.25. ## SHA1: ed3c0bbfb7912bcc00cd2033b0cb85c98d10559c ## Equifax Secure CA ================= -----BEGIN CERTIFICATE----- MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJVUzEQMA4GA1UE ChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoT B0VxdWlmYXgxLTArBgNVBAsTJEVxdWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCB nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPR fM6fBeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+AcJkVV5MW 8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kCAwEAAaOCAQkwggEFMHAG A1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UE CxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoG A1UdEAQTMBGBDzIwMTgwODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvS spXXR9gjIBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQFMAMB Af8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUAA4GBAFjOKer89961 zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y7qj/WsjTVbJmcVfewCHrPSqnI0kB BIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee95 70+sB3c4 -----END CERTIFICATE----- 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 3 Public Primary Certification Authority - G3 ============================================================ -----BEGIN CERTIFICATE----- MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQswCQYDVQQGEwJV UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv cmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl IG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNh dGlvbiBBdXRob3JpdHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQsw CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy dXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhv cml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkg Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC ggEBAMu6nFL8eB8aHm8bN3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1 EUGO+i2tKmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGukxUc cLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBmCC+Vk7+qRy+oRpfw EuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJXwzw3sJ2zq/3avL6QaaiMxTJ5Xpj 055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWuimi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA ERSWwauSCPc/L8my/uRan2Te2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5f j267Cz3qWhMeDGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC /Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565pF4ErWjfJXir0 xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGtTxzhT5yvDwyd93gN2PQ1VoDa t20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ== -----END CERTIFICATE----- Verisign Class 4 Public Primary Certification Authority - G3 ============================================================ -----BEGIN CERTIFICATE----- MIIEGjCCAwICEQDsoKeLbnVqAc/EfMwvlF7XMA0GCSqGSIb3DQEBBQUAMIHKMQswCQYDVQQGEwJV UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv cmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl IG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNh dGlvbiBBdXRob3JpdHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQsw CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy dXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhv cml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDQgUHVibGljIFByaW1hcnkg Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC ggEBAK3LpRFpxlmr8Y+1GQ9Wzsy1HyDkniYlS+BzZYlZ3tCD5PUPtbut8XzoIfzk6AzufEUiGXaS tBO3IFsJ+mGuqPKljYXCKtbeZjbSmwL0qJJgfJxptI8kHtCGUvYynEFYHiK9zUVilQhu0GbdU6LM 8BDcVHOLBKFGMzNcF0C5nk3T875Vg+ixiY5afJqWIpA7iCXy0lOIAgwLePLmNxdLMEYH5IBtptiW Lugs+BGzOA1mppvqySNb247i8xOOGlktqgLw7KSHZtzBP/XYufTsgsbSPZUd5cBPhMnZo0QoBmrX Razwa2rvTl/4EYIeOGM0ZlDUPpNz+jDDZq3/ky2X7wMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA j/ola09b5KROJ1WrIhVZPMq1CtRK26vdoV9TxaBXOcLORyu+OshWv8LZJxA6sQU8wHcxuzrTBXtt mhwwjIDLk5Mqg6sFUYICABFna/OIYUdfA5PVWw3g8dShMjWFsjrbsIKr0csKvE+MW8VLADsfKoKm fjaF3H48ZwC15DtS4KjrXRX5xm3wrR0OhbepmnMUWluPQSjA1egtTaRezarZ7c7c2NU8Qh0XwRJd RTjDOPP8hS6DRkiy1yBfkjaP53kPmF6Z6PDQpLv1U70qzlmwr25/bLvSHgCwIe34QWKCudiyxLtG UPMxxY8BqHTr9Xgn2uf3ZkPznoM+IKrDNWCRzg== -----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----- AddTrust Low-Value Services Root ================================ -----BEGIN CERTIFICATE----- MIIEGDCCAwCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJTRTEUMBIGA1UEChML QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYDVQQDExhBZGRU cnVzdCBDbGFzcyAxIENBIFJvb3QwHhcNMDAwNTMwMTAzODMxWhcNMjAwNTMwMTAzODMxWjBlMQsw CQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBO ZXR3b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwggEiMA0GCSqGSIb3DQEB AQUAA4IBDwAwggEKAoIBAQCWltQhSWDia+hBBwzexODcEyPNwTXH+9ZOEQpnXvUGW2ulCDtbKRY6 54eyNAbFvAWlA3yCyykQruGIgb3WntP+LVbBFc7jJp0VLhD7Bo8wBN6ntGO0/7Gcrjyvd7ZWxbWr oulpOj0OM3kyP3CCkplhbY0wCI9xP6ZIVxn4JdxLZlyldI+Yrsj5wAYi56xz36Uu+1LcsRVlIPo1 Zmne3yzxbrww2ywkEtvrNTVokMsAsJchPXQhI2U0K7t4WaPW4XY5mqRJjox0r26kmqPZm9I4XJui GMx1I4S+6+JNM3GOGvDC+Mcdoq0Dlyz4zyXG9rgkMbFjXZJ/Y/AlyVMuH79NAgMBAAGjgdIwgc8w HQYDVR0OBBYEFJWxtPCUtr3H2tERCSG+wa9J/RB7MAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8EBTAD AQH/MIGPBgNVHSMEgYcwgYSAFJWxtPCUtr3H2tERCSG+wa9J/RB7oWmkZzBlMQswCQYDVQQGEwJT RTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEw HwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBACxt ZBsfzQ3duQH6lmM0MkhHma6X7f1yFqZzR1r0693p9db7RcwpiURdv0Y5PejuvE1Uhh4dbOMXJ0Ph iVYrqW9yTkkz43J8KiOavD7/KCrto/8cI7pDVwlnTUtiBi34/2ydYB7YHEt9tTEv2dB8Xfjea4MY eDdXL+gzB2ffHsdrKpV2ro9Xo/D0UrSpUwjP4E/TelOL/bscVjby/rK25Xa71SJlpz/+0WatC7xr mYbvP33zGDLKe8bjq2RGlfgmadlVg3sslgf/WSxEo8bl6ancoWOAWiFeIc9TVPC6b4nbqKqVz4vj ccweGyBECMB6tkD9xOQ14R0WHNC8K47Wcdk= -----END CERTIFICATE----- AddTrust External Root ====================== -----BEGIN CERTIFICATE----- MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEUMBIGA1UEChML QWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYD VQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEw NDgzOFowbzELMAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRU cnVzdCBFeHRlcm5hbCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0Eg Um9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvtH7xsD821 +iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9uMq/NzgtHj6RQa1wVsfw Tz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzXmk6vBbOmcZSccbNQYArHE504B4YCqOmo aSYYkKtMsE8jqzpPhNjfzp/haW+710LXa0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy 2xSoRcRdKn23tNbE7qzNE0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv7 7+ldU9U0WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYDVR0P BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0Jvf6xCZU7wO94CTL VBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEmMCQGA1UECxMdQWRk VHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsxIjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENB IFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZl j7DYd7usQWxHYINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5 6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvCNr4TDea9Y355 e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEXc4g/VhsxOBi0cQ+azcgOno4u G+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5amnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= -----END CERTIFICATE----- AddTrust Public Services Root ============================= -----BEGIN CERTIFICATE----- MIIEFTCCAv2gAwIBAgIBATANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJTRTEUMBIGA1UEChML QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSAwHgYDVQQDExdBZGRU cnVzdCBQdWJsaWMgQ0EgUm9vdDAeFw0wMDA1MzAxMDQxNTBaFw0yMDA1MzAxMDQxNTBaMGQxCzAJ BgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5l dHdvcmsxIDAeBgNVBAMTF0FkZFRydXN0IFB1YmxpYyBDQSBSb290MIIBIjANBgkqhkiG9w0BAQEF AAOCAQ8AMIIBCgKCAQEA6Rowj4OIFMEg2Dybjxt+A3S72mnTRqX4jsIMEZBRpS9mVEBV6tsfSlbu nyNu9DnLoblv8n75XYcmYZ4c+OLspoH4IcUkzBEMP9smcnrHAZcHF/nXGCwwfQ56HmIexkvA/X1i d9NEHif2P0tEs7c42TkfYNVRknMDtABp4/MUTu7R3AnPdzRGULD4EfL+OHn3Bzn+UZKXC1sIXzSG Aa2Il+tmzV7R/9x98oTaunet3IAIx6eH1lWfl2royBFkuucZKT8Rs3iQhCBSWxHveNCD9tVIkNAw HM+A+WD+eeSI8t0A65RF62WUaUC6wNW0uLp9BBGo6zEFlpROWCGOn9Bg/QIDAQABo4HRMIHOMB0G A1UdDgQWBBSBPjfYkrAfd59ctKtzquf2NGAv+jALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB /zCBjgYDVR0jBIGGMIGDgBSBPjfYkrAfd59ctKtzquf2NGAv+qFopGYwZDELMAkGA1UEBhMCU0Ux FDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRUcnVzdCBUVFAgTmV0d29yazEgMB4G A1UEAxMXQWRkVHJ1c3QgUHVibGljIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBAAP3FUr4 JNojVhaTdt02KLmuG7jD8WS6IBh4lSknVwW8fCr0uVFV2ocC3g8WFzH4qnkuCRO7r7IgGRLlk/lL +YPoRNWyQSW/iHVv/xD8SlTQX/D67zZzfRs2RcYhbbQVuE7PnFylPVoAjgbjPGsye/Kf8Lb93/Ao GEjwxrzQvzSAlsJKsW2Ox5BF3i9nrEUEo3rcVZLJR2bYGozH7ZxOmuASu7VqTITh4SINhwBk/ox9 Yjllpu9CtoAlEmEBqCQTcAARJl/6NVDFSMwGR+gn2HCNX2TmoUQmXiLsks3/QppEIW1cxeMiHV9H EufOX1362KqxMy3ZdvJOOjMMK7MtkAY= -----END CERTIFICATE----- AddTrust Qualified Certificates Root ==================================== -----BEGIN CERTIFICATE----- MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJTRTEUMBIGA1UEChML QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSMwIQYDVQQDExpBZGRU cnVzdCBRdWFsaWZpZWQgQ0EgUm9vdDAeFw0wMDA1MzAxMDQ0NTBaFw0yMDA1MzAxMDQ0NTBaMGcx CzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQ IE5ldHdvcmsxIzAhBgNVBAMTGkFkZFRydXN0IFF1YWxpZmllZCBDQSBSb290MIIBIjANBgkqhkiG 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5B6a/twJWoekn0e+EV+vhDTbYjx5eLfpMLXsDBwqxBb/4Oxx 64r1EW7tTw2R0hIYLUkVAcKkIhPHEWT/IhKauY5cLwjPcWqzZwFZ8V1G87B4pfYOQnrjfxvM0PC3 KP0q6p6zsLkEqv32x7SxuCqg+1jxGaBvcCV+PmlKfw8i2O+tCBGaKZnhqkRFmhJePp1tUvznoD1o L/BLcHwTOK28FSXx1s6rosAx1i+f4P8UWfyEk9mHfExUE+uf0S0R+Bg6Ot4l2ffTQO2kBhLEO+GR wVY18BTcZTYJbqukB8c10cIDMzZbdSZtQvESa0NvS3GU+jQd7RNuyoB/mC9suWXY6QIDAQABo4HU MIHRMB0GA1UdDgQWBBQ5lYtii1zJ1IC6WA+XPxUIQ8yYpzALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/ BAUwAwEB/zCBkQYDVR0jBIGJMIGGgBQ5lYtii1zJ1IC6WA+XPxUIQ8yYp6FrpGkwZzELMAkGA1UE BhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRUcnVzdCBUVFAgTmV0d29y azEjMCEGA1UEAxMaQWRkVHJ1c3QgUXVhbGlmaWVkIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQAD ggEBABmrder4i2VhlRO6aQTvhsoToMeqT2QbPxj2qC0sVY8FtzDqQmodwCVRLae/DLPt7wh/bDxG GuoYQ992zPlmhpwsaPXpF/gxsxjE1kh9I0xowX67ARRvxdlu3rsEQmr49lx95dr6h+sNNVJn0J6X dgWTP5XHAeZpVTh/EGGZyeNfpso+gmNIquIISD6q8rKFYqa0p9m9N5xotS1WfbC3P6CxB9bpT9ze RXEwMn8bLgn5v1Kh7sKAPgZcLlVAwRv1cEWw3F369nJad9Jjzc9YiQBCYz95OdBEsIJuQRno3eDB iFrRHnGTHyQwdOUeqN48Jzd/g66ed8/wMLH/S5noxqE= -----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----- RSA Security 2048 v3 ==================== -----BEGIN CERTIFICATE----- MIIDYTCCAkmgAwIBAgIQCgEBAQAAAnwAAAAKAAAAAjANBgkqhkiG9w0BAQUFADA6MRkwFwYDVQQK ExBSU0EgU2VjdXJpdHkgSW5jMR0wGwYDVQQLExRSU0EgU2VjdXJpdHkgMjA0OCBWMzAeFw0wMTAy MjIyMDM5MjNaFw0yNjAyMjIyMDM5MjNaMDoxGTAXBgNVBAoTEFJTQSBTZWN1cml0eSBJbmMxHTAb BgNVBAsTFFJTQSBTZWN1cml0eSAyMDQ4IFYzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC AQEAt49VcdKA3XtpeafwGFAyPGJn9gqVB93mG/Oe2dJBVGutn3y+Gc37RqtBaB4Y6lXIL5F4iSj7 Jylg/9+PjDvJSZu1pJTOAeo+tWN7fyb9Gd3AIb2E0S1PRsNO3Ng3OTsor8udGuorryGlwSMiuLgb WhOHV4PR8CDn6E8jQrAApX2J6elhc5SYcSa8LWrg903w8bYqODGBDSnhAMFRD0xS+ARaqn1y07iH KrtjEAMqs6FPDVpeRrc9DvV07Jmf+T0kgYim3WBU6JU2PcYJk5qjEoAAVZkZR73QpXzDuvsf9/UP +Ky5tfQ3mBMY3oVbtwyCO4dvlTlYMNpuAWgXIszACwIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/ MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQHw1EwpKrpRa41JPr/JCwz0LGdjDAdBgNVHQ4E FgQUB8NRMKSq6UWuNST6/yQsM9CxnYwwDQYJKoZIhvcNAQEFBQADggEBAF8+hnZuuDU8TjYcHnmY v/3VEhF5Ug7uMYm83X/50cYVIeiKAVQNOvtUudZj1LGqlk2iQk3UUx+LEN5/Zb5gEydxiKRz44Rj 0aRV4VCT5hsOedBnvEbIvz8XDZXmxpBp3ue0L96VfdASPz0+f00/FGj1EVDVwfSQpQgdMWD/YIwj VAqv/qFuxdF6Kmh4zx6CCiC0H63lhbJqaHVOrSU3lIW+vaHU6rcMSzyd6BIA8F+sDeGscGNz9395 nzIlQnQFgCi/vcEkllgVsRch6YlL2weIZ/QVrXA+L02FO8K32/6YaCOJ4XQP3vTFhGMpG8zLB8kA pKnXwiJPZ9d37CAFYd4= -----END CERTIFICATE----- GeoTrust Global CA ================== -----BEGIN CERTIFICATE----- MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVTMRYwFAYDVQQK Ew1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9iYWwgQ0EwHhcNMDIwNTIxMDQw MDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5j LjEbMBkGA1UEAxMSR2VvVHJ1c3QgR2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB CgKCAQEA2swYYzD99BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjo BbdqfnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDviS2Aelet 8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU1XupGc1V3sjs0l44U+Vc T4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+bw8HHa8sHo9gOeL6NlMTOdReJivbPagU vTLrGAMoUgRx5aszPeE4uwc2hGKceeoWMPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTAD AQH/MB0GA1UdDgQWBBTAephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVk DBF9qn1luMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKInZ57Q zxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfStQWVYrmm3ok9Nns4 d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcFPseKUgzbFbS9bZvlxrFUaKnjaZC2 mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Unhw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6p XE0zX5IJL4hmXXeXxx12E6nV5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvm Mw== -----END CERTIFICATE----- GeoTrust Global CA 2 ==================== -----BEGIN CERTIFICATE----- MIIDZjCCAk6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN R2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFsIENBIDIwHhcNMDQwMzA0MDUw MDAwWhcNMTkwMzA0MDUwMDAwWjBEMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5j LjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFsIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw ggEKAoIBAQDvPE1APRDfO1MA4Wf+lGAVPoWI8YkNkMgoI5kF6CsgncbzYEbYwbLVjDHZ3CB5JIG/ NTL8Y2nbsSpr7iFY8gjpeMtvy/wWUsiRxP89c96xPqfCfWbB9X5SJBri1WeR0IIQ13hLTytCOb1k LUCgsBDTOEhGiKEMuzozKmKY+wCdE1l/bztyqu6mD4b5BWHqZ38MN5aL5mkWRxHCJ1kDs6ZgwiFA Vvqgx306E+PsV8ez1q6diYD3Aecs9pYrEw15LNnA5IZ7S4wMcoKK+xfNAGw6EzywhIdLFnopsk/b HdQL82Y3vdj2V7teJHq4PIu5+pIaGoSe2HSPqht/XvT+RSIhAgMBAAGjYzBhMA8GA1UdEwEB/wQF MAMBAf8wHQYDVR0OBBYEFHE4NvICMVNHK266ZUapEBVYIAUJMB8GA1UdIwQYMBaAFHE4NvICMVNH K266ZUapEBVYIAUJMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAQEAA/e1K6tdEPx7 srJerJsOflN4WT5CBP51o62sgU7XAotexC3IUnbHLB/8gTKY0UvGkpMzNTEv/NgdRN3ggX+d6Yvh ZJFiCzkIjKx0nVnZellSlxG5FntvRdOW2TF9AjYPnDtuzywNA0ZF66D0f0hExghAzN4bcLUprbqL OzRldRtxIR0sFAqwlpW41uryZfspuk/qkZN0abby/+Ea0AzRdoXLiiW9l14sbxWZJue2Kf8i7MkC x1YAzUm5s2x7UwQa4qjJqhIFI8LO57sEAszAR6LkxCkvW0VXiVHuPOtSCP8HNR6fNWpHSlaY0VqF H4z1Ir+rzoPz4iIprn2DQKi6bA== -----END CERTIFICATE----- GeoTrust Universal CA ===================== -----BEGIN CERTIFICATE----- MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN R2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVyc2FsIENBMB4XDTA0MDMwNDA1 MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IElu Yy4xHjAcBgNVBAMTFUdlb1RydXN0IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIP ADCCAgoCggIBAKYVVaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9t JPi8cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTTQjOgNB0e RXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFhF7em6fgemdtzbvQKoiFs 7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2vc7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d 8Lsrlh/eezJS/R27tQahsiFepdaVaH/wmZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7V qnJNk22CDtucvc+081xdVHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3Cga Rr0BHdCXteGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZf9hB Z3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfReBi9Fi1jUIxaS5BZu KGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+nhutxx9z3SxPGWX9f5NAEC7S8O08 ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0 XG0D08DYj3rWMB8GA1UdIwQYMBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIB hjANBgkqhkiG9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fXIwjhmF7DWgh2 qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzynANXH/KttgCJwpQzgXQQpAvvL oJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0zuzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsK xr2EoyNB3tZ3b4XUhRxQ4K5RirqNPnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxF KyDuSN/n3QmOGKjaQI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2 DFKWkoRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9ER/frslK xfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQtDF4JbAiXfKM9fJP/P6EU p8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/SfuvmbJxPgWp6ZKy7PtXny3YuxadIwVyQD8vI P/rmMuGNG2+k5o7Y+SlIis5z/iw= -----END CERTIFICATE----- GeoTrust Universal CA 2 ======================= -----BEGIN CERTIFICATE----- MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN R2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVyc2FsIENBIDIwHhcNMDQwMzA0 MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3Qg SW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUA A4ICDwAwggIKAoICAQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0 DE81WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUGFF+3Qs17 j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdqXbboW0W63MOhBW9Wjo8Q JqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxLse4YuU6W3Nx2/zu+z18DwPw76L5GG//a QMJS9/7jOvdqdzXQ2o3rXhhqMcceujwbKNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2 WP0+GfPtDCapkzj4T8FdIgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP 20gaXT73y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRthAAn ZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgocQIgfksILAAX/8sgC SqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4Lt1ZrtmhN79UNdxzMk+MBB4zsslG 8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2 +/CfXGJx7Tz0RzgQKzAfBgNVHSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8E BAMCAYYwDQYJKoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQL1EuxBRa3ugZ 4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgrFg5fNuH8KrUwJM/gYwx7WBr+ mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSoag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpq A1Ihn0CoZ1Dy81of398j9tx4TuaYT1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpg Y+RdM4kX2TGq2tbzGDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiP pm8m1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJVOCiNUW7d FGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH6aLcr34YEoP9VhdBLtUp gn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwXQMAJKOSLakhT2+zNVVXxxvjpoixMptEm X36vWkzaH6byHCx+rgIW0lbQL1dTR+iS -----END CERTIFICATE----- Visa eCommerce Root =================== -----BEGIN CERTIFICATE----- MIIDojCCAoqgAwIBAgIQE4Y1TR0/BvLB+WUF1ZAcYjANBgkqhkiG9w0BAQUFADBrMQswCQYDVQQG EwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRlcm5hdGlvbmFsIFNlcnZpY2Ug QXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNvbW1lcmNlIFJvb3QwHhcNMDIwNjI2MDIxODM2 WhcNMjIwNjI0MDAxNjEyWjBrMQswCQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMm VmlzYSBJbnRlcm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNv bW1lcmNlIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvV95WHm6h2mCxlCfL F9sHP4CFT8icttD0b0/Pmdjh28JIXDqsOTPHH2qLJj0rNfVIsZHBAk4ElpF7sDPwsRROEW+1QK8b RaVK7362rPKgH1g/EkZgPI2h4H3PVz4zHvtH8aoVlwdVZqW1LS7YgFmypw23RuwhY/81q6UCzyr0 TP579ZRdhE2o8mCP2w4lPJ9zcc+U30rq299yOIzzlr3xF7zSujtFWsan9sYXiwGd/BmoKoMWuDpI /k4+oKsGGelT84ATB+0tvz8KPFUgOSwsAGl0lUq8ILKpeeUYiZGo3BxN77t+Nwtd/jmliFKMAGzs GHxBvfaLdXe6YJ2E5/4tAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEG MB0GA1UdDgQWBBQVOIMPPyw/cDMezUb+B4wg4NfDtzANBgkqhkiG9w0BAQUFAAOCAQEAX/FBfXxc CLkr4NWSR/pnXKUTwwMhmytMiUbPWU3J/qVAtmPN3XEolWcRzCSs00Rsca4BIGsDoo8Ytyk6feUW YFN4PMCvFYP3j1IzJL1kk5fui/fbGKhtcbP3LBfQdCVp9/5rPJS+TUtBjE7ic9DjkCJzQ83z7+pz zkWKsKZJ/0x9nXGIxHYdkFsd7v3M9+79YKWxehZx0RbQfBI8bGmX265fOZpwLwU8GUYEmSA20GBu YQa7FkKMcPcw++DbZqMAAb3mLNqRX6BGi01qnD093QVG/na/oAo85ADmJ7f/hC3euiInlhBx6yLt 398znM/jra6O1I7mT1GvFpLgXPYHDw== -----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----- Comodo Secure Services root =========================== -----BEGIN CERTIFICATE----- MIIEPzCCAyegAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg TGltaXRlZDEkMCIGA1UEAwwbU2VjdXJlIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAw MDAwMFoXDTI4MTIzMTIzNTk1OVowfjELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFu Y2hlc3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxJDAi BgNVBAMMG1NlY3VyZSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP ADCCAQoCggEBAMBxM4KK0HDrc4eCQNUd5MvJDkKQ+d40uaG6EfQlhfPMcm3ye5drswfxdySRXyWP 9nQ95IDC+DwN879A6vfIUtFyb+/Iq0G4bi4XKpVpDM3SHpR7LZQdqnXXs5jLrLxkU0C8j6ysNstc rbvd4JQX7NFc0L/vpZXJkMWwrPsbQ996CF23uPJAGysnnlDOXmWCiIxe004MeuoIkbY2qitC++rC oznl2yY4rYsK7hljxxwk3wN42ubqwUcaCwtGCd0C/N7Lh1/XMGNooa7cMqG6vv5Eq2i2pRcV/b3V p6ea5EQz6YiO/O1R65NxTq0B50SOqy3LqP4BSUjwwN3HaNiS/j0CAwEAAaOBxzCBxDAdBgNVHQ4E FgQUPNiTiMLAggnMAZkGkyDpnnAJY08wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w gYEGA1UdHwR6MHgwO6A5oDeGNWh0dHA6Ly9jcmwuY29tb2RvY2EuY29tL1NlY3VyZUNlcnRpZmlj YXRlU2VydmljZXMuY3JsMDmgN6A1hjNodHRwOi8vY3JsLmNvbW9kby5uZXQvU2VjdXJlQ2VydGlm aWNhdGVTZXJ2aWNlcy5jcmwwDQYJKoZIhvcNAQEFBQADggEBAIcBbSMdflsXfcFhMs+P5/OKlFlm 4J4oqF7Tt/Q05qo5spcWxYJvMqTpjOev/e/C6LlLqqP05tqNZSH7uoDrJiiFGv45jN5bBAS0VPmj Z55B+glSzAVIqMk/IQQezkhr/IXownuvf7fM+F86/TXGDe+X3EyrEeFryzHRbPtIgKvcnDe4IRRL DXE97IMzbtFuMhbsmMcWi1mmNKsFVy2T96oTy9IT4rcuO81rUBcJaD61JlfutuC23bkpgHl9j6Pw pCikFcSF9CfUa7/lXORlAnZUtOM3ZiTTGWHIUhDlizeauan5Hb/qmZJhlv8BzaFfDbxxvA6sCx1H RR3B7Hzs/Sk= -----END CERTIFICATE----- Comodo Trusted Services root ============================ -----BEGIN CERTIFICATE----- MIIEQzCCAyugAwIBAgIBATANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg TGltaXRlZDElMCMGA1UEAwwcVHJ1c3RlZCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczAeFw0wNDAxMDEw MDAwMDBaFw0yODEyMzEyMzU5NTlaMH8xCzAJBgNVBAYTAkdCMRswGQYDVQQIDBJHcmVhdGVyIE1h bmNoZXN0ZXIxEDAOBgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoMEUNvbW9kbyBDQSBMaW1pdGVkMSUw IwYDVQQDDBxUcnVzdGVkIENlcnRpZmljYXRlIFNlcnZpY2VzMIIBIjANBgkqhkiG9w0BAQEFAAOC AQ8AMIIBCgKCAQEA33FvNlhTWvI2VFeAxHQIIO0Yfyod5jWaHiWsnOWWfnJSoBVC21ndZHoa0Lh7 3TkVvFVIxO06AOoxEbrycXQaZ7jPM8yoMa+j49d/vzMtTGo87IvDktJTdyR0nAducPy9C1t2ul/y /9c3S0pgePfw+spwtOpZqqPOSC+pw7ILfhdyFgymBwwbOM/JYrc/oJOlh0Hyt3BAd9i+FHzjqMB6 juljatEPmsbS9Is6FARW1O24zG71++IsWL1/T2sr92AkWCTOJu80kTrV44HQsvAEAtdbtz6SrGsS ivnkBbA7kUlcsutT6vifR4buv5XAwAaf0lteERv0xwQ1KdJVXOTt6wIDAQABo4HJMIHGMB0GA1Ud DgQWBBTFe1i97doladL3WRaoszLAeydb9DAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB /zCBgwYDVR0fBHwwejA8oDqgOIY2aHR0cDovL2NybC5jb21vZG9jYS5jb20vVHJ1c3RlZENlcnRp ZmljYXRlU2VydmljZXMuY3JsMDqgOKA2hjRodHRwOi8vY3JsLmNvbW9kby5uZXQvVHJ1c3RlZENl cnRpZmljYXRlU2VydmljZXMuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQDIk4E7ibSvuIQSTI3S8Ntw uleGFTQQuS9/HrCoiWChisJ3DFBKmwCL2Iv0QeLQg4pKHBQGsKNoBXAxMKdTmw7pSqBYaWcOrp32 pSxBvzwGa+RZzG0Q8ZZvH9/0BAKkn0U+yNj6NkZEUD+Cl5EfKNsYEYwq5GWDVxISjBc/lDb+XbDA BHcTuPQV1T84zJQ6VdCsmPW6AF/ghhmBeC8owH7TzEIK9a5QoNE+xqFx7D+gIIxmOom0jtTYsU0l R+4viMi14QVFwL4Ucd56/Y57fU0IlqUSc/AtyjcndBInTMu2l+nZrghtWjlA3QVHdWpaIbOjGM9O 9y5Xt5hwXsjEeLBi -----END CERTIFICATE----- QuoVadis Root CA ================ -----BEGIN CERTIFICATE----- MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJCTTEZMBcGA1UE ChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0 eTEuMCwGA1UEAxMlUXVvVmFkaXMgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMTAz MTkxODMzMzNaFw0yMTAzMTcxODMzMzNaMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRp cyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQD EyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF AAOCAQ8AMIIBCgKCAQEAv2G1lVO6V/z68mcLOhrfEYBklbTRvM16z/Ypli4kVEAkOPcahdxYTMuk J0KX0J+DisPkBgNbAKVRHnAEdOLB1Dqr1607BxgFjv2DrOpm2RgbaIr1VxqYuvXtdj182d6UajtL F8HVj71lODqV0D1VNk7feVcxKh7YWWVJWCCYfqtffp/p1k3sg3Spx2zY7ilKhSoGFPlU5tPaZQeL YzcS19Dsw3sgQUSj7cugF+FxZc4dZjH3dgEZyH0DWLaVSR2mEiboxgx24ONmy+pdpibu5cxfvWen AScOospUxbF6lR1xHkopigPcakXBpBlebzbNw6Kwt/5cOOJSvPhEQ+aQuwIDAQABo4ICUjCCAk4w PQYIKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwczovL29jc3AucXVvdmFkaXNvZmZzaG9y ZS5jb20wDwYDVR0TAQH/BAUwAwEB/zCCARoGA1UdIASCAREwggENMIIBCQYJKwYBBAG+WAABMIH7 MIHUBggrBgEFBQcCAjCBxxqBxFJlbGlhbmNlIG9uIHRoZSBRdW9WYWRpcyBSb290IENlcnRpZmlj YXRlIGJ5IGFueSBwYXJ0eSBhc3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJs ZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRpb24gcHJh Y3RpY2VzLCBhbmQgdGhlIFF1b1ZhZGlzIENlcnRpZmljYXRlIFBvbGljeS4wIgYIKwYBBQUHAgEW Fmh0dHA6Ly93d3cucXVvdmFkaXMuYm0wHQYDVR0OBBYEFItLbe3TKbkGGew5Oanwl4Rqy+/fMIGu BgNVHSMEgaYwgaOAFItLbe3TKbkGGew5Oanwl4Rqy+/foYGEpIGBMH8xCzAJBgNVBAYTAkJNMRkw FwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0 aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ6 tlCLMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAitQUtf70mpKnGdSkfnIYj9lo fFIk3WdvOXrEql494liwTXCYhGHoG+NpGA7O+0dQoE7/8CQfvbLO9Sf87C9TqnN7Az10buYWnuul LsS/VidQK2K6vkscPFVcQR0kvoIgR13VRH56FmjffU1RcHhXHTMe/QKZnAzNCgVPx7uOpHX6Sm2x gI4JVrmcGmD+XcHXetwReNDWXcG31a0ymQM6isxUJTkxgXsTIlG6Rmyhu576BGxJJnSP0nPrzDCi 5upZIof4l/UO/erMkqQWxFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi 5nrQNiOKSnQ2+Q== -----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----- Sonera Class 2 Root CA ====================== -----BEGIN CERTIFICATE----- MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEPMA0GA1UEChMG U29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAxMDQwNjA3Mjk0MFoXDTIxMDQw NjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNVBAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJh IENsYXNzMiBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJAXSjWdyvANlsdE+hY3 /Ei9vX+ALTU74W+oZ6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gXGM2RX/uJ4+q/Tl18GybT dXnt5oTjV+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7Jp12W5dCsv+u8E7s3TmVToMG f+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCWctRUz2EjvOr7nQKV0ba5cTppCD8P tOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu8nYybieDwnPz3BjotJPqdURrBGAgcVeH nfO+oJAjPYok4doh28MCAwEAAaMzMDEwDwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITT XjwwCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt 0jSv9zilzqsWuasvfDXLrNAPtEwr/IDva4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/3DEI cbCdjdY0RzKQxmUk96BKfARzjzlvF4xytb1LyHr4e4PDKE6cCepnP7JnBBvDFNr450kkkdAdavph Oe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh+Re/n570K6Tk6ezAyNlNzZRZxe7EJQY670XcSx EtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLH llpwrN9M -----END CERTIFICATE----- Staat der Nederlanden Root CA ============================= -----BEGIN CERTIFICATE----- MIIDujCCAqKgAwIBAgIEAJiWijANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJOTDEeMBwGA1UE ChMVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSYwJAYDVQQDEx1TdGFhdCBkZXIgTmVkZXJsYW5kZW4g Um9vdCBDQTAeFw0wMjEyMTcwOTIzNDlaFw0xNTEyMTYwOTE1MzhaMFUxCzAJBgNVBAYTAk5MMR4w HAYDVQQKExVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xJjAkBgNVBAMTHVN0YWF0IGRlciBOZWRlcmxh bmRlbiBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmNK1URF6gaYUmHFt vsznExvWJw56s2oYHLZhWtVhCb/ekBPHZ+7d89rFDBKeNVU+LCeIQGv33N0iYfXCxw719tV2U02P jLwYdjeFnejKScfST5gTCaI+Ioicf9byEGW07l8Y1Rfj+MX94p2i71MOhXeiD+EwR+4A5zN9RGca C1Hoi6CeUJhoNFIfLm0B8mBF8jHrqTFoKbt6QZ7GGX+UtFE5A3+y3qcym7RHjm+0Sq7lr7HcsBth vJly3uSJt3omXdozSVtSnA71iq3DuD3oBmrC1SoLbHuEvVYFy4ZlkuxEK7COudxwC0barbxjiDn6 22r+I/q85Ej0ZytqERAhSQIDAQABo4GRMIGOMAwGA1UdEwQFMAMBAf8wTwYDVR0gBEgwRjBEBgRV HSAAMDwwOgYIKwYBBQUHAgEWLmh0dHA6Ly93d3cucGtpb3ZlcmhlaWQubmwvcG9saWNpZXMvcm9v dC1wb2xpY3kwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSofeu8Y6R0E3QA7Jbg0zTBLL9s+DAN BgkqhkiG9w0BAQUFAAOCAQEABYSHVXQ2YcG70dTGFagTtJ+k/rvuFbQvBgwp8qiSpGEN/KtcCFtR EytNwiphyPgJWPwtArI5fZlmgb9uXJVFIGzmeafR2Bwp/MIgJ1HI8XxdNGdphREwxgDS1/PTfLbw MVcoEoJz6TMvplW0C5GUR5z6u3pCMuiufi3IvKwUv9kP2Vv8wfl6leF9fpb8cbDCTMjfRTTJzg3y nGQI0DvDKcWy7ZAEwbEpkcUwb8GpcjPM/l0WFywRaed+/sWDCN+83CI6LiBpIzlWYGeQiy52OfsR iJf2fL1LuCAWZwWN4jvBcj+UlTfHXbme2JOhF4//DGYVwSR8MnwDHTuhWEUykw== -----END CERTIFICATE----- UTN DATACorp SGC Root CA ======================== -----BEGIN CERTIFICATE----- MIIEXjCCA0agAwIBAgIQRL4Mi1AAIbQR0ypoBqmtaTANBgkqhkiG9w0BAQUFADCBkzELMAkGA1UE BhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhl IFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZ BgNVBAMTElVUTiAtIERBVEFDb3JwIFNHQzAeFw05OTA2MjQxODU3MjFaFw0xOTA2MjQxOTA2MzBa MIGTMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4w HAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRy dXN0LmNvbTEbMBkGA1UEAxMSVVROIC0gREFUQUNvcnAgU0dDMIIBIjANBgkqhkiG9w0BAQEFAAOC AQ8AMIIBCgKCAQEA3+5YEKIrblXEjr8uRgnn4AgPLit6E5Qbvfa2gI5lBZMAHryv4g+OGQ0SR+ys raP6LnD43m77VkIVni5c7yPeIbkFdicZD0/Ww5y0vpQZY/KmEQrrU0icvvIpOxboGqBMpsn0GFlo wHDyUwDAXlCCpVZvNvlK4ESGoE1O1kduSUrLZ9emxAW5jh70/P/N5zbgnAVssjMiFdC04MwXwLLA 9P4yPykqlXvY8qdOD1R8oQ2AswkDwf9c3V6aPryuvEeKaq5xyh+xKrhfQgUL7EYw0XILyulWbfXv 33i+Ybqypa4ETLyorGkVl73v67SMvzX41MPRKA5cOp9wGDMgd8SirwIDAQABo4GrMIGoMAsGA1Ud DwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRTMtGzz3/64PGgXYVOktKeRR20TzA9 BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY3JsLnVzZXJ0cnVzdC5jb20vVVROLURBVEFDb3JwU0dD LmNybDAqBgNVHSUEIzAhBggrBgEFBQcDAQYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMA0GCSqGSIb3 DQEBBQUAA4IBAQAnNZcAiosovcYzMB4p/OL31ZjUQLtgyr+rFywJNn9Q+kHcrpY6CiM+iVnJowft Gzet/Hy+UUla3joKVAgWRcKZsYfNjGjgaQPpxE6YsjuMFrMOoAyYUJuTqXAJyCyjj98C5OBxOvG0 I3KgqgHf35g+FFCgMSa9KOlaMCZ1+XtgHI3zzVAmbQQnmt/VDUVHKWss5nbZqSl9Mt3JNjy9rjXx EZ4du5A/EkdOjtd+D2JzHVImOBwYSf0wdJrE5SIv2MCN7ZF6TACPcn9d2t0bi0Vr591pl6jFVkwP DPafepE39peC4N1xaf92P2BNPM/3mfnGV/TJVTl4uix5yaaIK/QI -----END CERTIFICATE----- UTN USERFirst Hardware Root CA ============================== -----BEGIN CERTIFICATE----- MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCBlzELMAkGA1UE BhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhl IFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAd BgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgx OTIyWjCBlzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0 eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVz ZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdhcmUwggEiMA0GCSqGSIb3 DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlI wrthdBKWHTxqctU8EGc6Oe0rE81m65UJM6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFd tqdt++BxF2uiiPsA3/4aMXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8 i4fDidNdoI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqIDsjf Pe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9KsyoUhbAgMBAAGjgbkw gbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFKFyXyYbKJhDlV0HN9WF lp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNF UkZpcnN0LUhhcmR3YXJlLmNybDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUF BwMGBggrBgEFBQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM //bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28GpgoiskliCE7/yMgUsogW XecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gECJChicsZUN/KHAG8HQQZexB2 lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kn iCrVWFCVH/A7HFe7fRQ5YiuayZSSKqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67 nfhmqA== -----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----- NetLock Notary (Class A) Root ============================= -----BEGIN CERTIFICATE----- MIIGfTCCBWWgAwIBAgICAQMwDQYJKoZIhvcNAQEEBQAwga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQI EwdIdW5nYXJ5MREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6 dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9j ayBLb3pqZWd5em9pIChDbGFzcyBBKSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNDIzMTQ0N1oX DTE5MDIxOTIzMTQ0N1owga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQIEwdIdW5nYXJ5MREwDwYDVQQH EwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9uc2FnaSBLZnQuMRowGAYD VQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9jayBLb3pqZWd5em9pIChDbGFz cyBBKSBUYW51c2l0dmFueWtpYWRvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvHSM D7tM9DceqQWC2ObhbHDqeLVu0ThEDaiDzl3S1tWBxdRL51uUcCbbO51qTGL3cfNk1mE7PetzozfZ z+qMkjvN9wfcZnSX9EUi3fRc4L9t875lM+QVOr/bmJBVOMTtplVjC7B4BPTjbsE/jvxReB+SnoPC /tmwqcm8WgD/qaiYdPv2LD4VOQ22BFWoDpggQrOxJa1+mm9dU7GrDPzr4PN6s6iz/0b2Y6LYOph7 tqyF/7AlT3Rj5xMHpQqPBffAZG9+pyeAlt7ULoZgx2srXnN7F+eRP2QM2EsiNCubMvJIH5+hCoR6 4sKtlz2O1cH5VqNQ6ca0+pii7pXmKgOM3wIDAQABo4ICnzCCApswDgYDVR0PAQH/BAQDAgAGMBIG A1UdEwEB/wQIMAYBAf8CAQQwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaC Ak1GSUdZRUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pv bGdhbHRhdGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQu IEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2Vn LWJpenRvc2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0 ZXRlbGUgYXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFz IGxlaXJhc2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBh IGh0dHBzOi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVu b3J6ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1YW5jZSBh bmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sg Q1BTIGF2YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFp bCBhdCBjcHNAbmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4IBAQBIJEb3ulZv+sgoA0BO5TE5 ayZrU3/b39/zcT0mwBQOxmd7I6gMc90Bu8bKbjc5VdXHjFYgDigKDtIqpLBJUsY4B/6+CgmM0ZjP ytoUMaFP0jn8DxEsQ8Pdq5PHVT5HfBgaANzze9jyf1JsIPQLX2lS9O74silg6+NJMSEN1rUQQeJB CWziGppWS3cC9qCbmieH6FUpccKQn0V4GuEVZD3QDtigdp+uxdAu6tYPVuxkf1qbFFgBJ34TUMdr KuZoPL9coAob4Q566eKAw+np9v1sEZ7Q5SgnK1QyQhSCdeZK8CtmdWOMovsEPoMOmzbwGOQmIMOM 8CgHrTwXZoi1/baI -----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----- StartCom Certification Authority ================================ -----BEGIN CERTIFICATE----- MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMN U3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmlu ZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0 NjM2WhcNMzYwOTE3MTk0NjM2WjB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRk LjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMg U3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw ggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZkpMyONvg45iPwbm2xPN1y o4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rfOQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/ Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/CJi/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/d eMotHweXMAEtcnn6RtYTKqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt 2PZE4XNiHzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMMAv+Z 6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w+2OqqGwaVLRcJXrJ osmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/ untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVc UjyJthkqcwEKDwOzEmDyei+B26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT 37uMdBNSSwIDAQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYE FE4L7xqkQFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9jZXJ0LnN0YXJ0 Y29tLm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3JsLnN0YXJ0Y29tLm9yZy9zZnNj YS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFMBgsrBgEEAYG1NwEBATCCATswLwYIKwYBBQUH AgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIBFilodHRw Oi8vY2VydC5zdGFydGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUHAgIwgcMwJxYg U3RhcnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0ZWQgTGlhYmlsaXR5 LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2YgdGhlIFN0YXJ0Q29tIENl cnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFpbGFibGUgYXQgaHR0cDovL2NlcnQuc3Rh cnRjb20ub3JnL3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilT dGFydENvbSBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQUFAAOC AgEAFmyZ9GYMNPXQhV59CuzaEE44HF7fpiUFS5Eyweg78T3dRAlbB0mKKctmArexmvclmAk8jhvh 3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk4gNXcGmXCPleWKYK34wGmkUWFjgKXlf2Ysd6AgXm vB618p70qSmD+LIU424oh0TDkBreOKk8rENNZEXO3SipXPJzewT4F+irsfMuXGRuczE6Eri8sxHk fY+BUZo7jYn0TZNmezwD7dOaHZrzZVD1oNB1ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3 fsNrarnDy0RLrHiQi+fHLB5LEUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZ EoalHmdkrQYuL6lwhceWD3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+Pwq yvqCUqDvr0tVk+vBtfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuCO3NJo2pXh5Tl 1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6Vum0ABj6y6koQOdjQK/W/7HW/ lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkyShNOsF/5oirpt9P/FlUQqmMGqz9IgcgA38coro g14= -----END CERTIFICATE----- Taiwan GRCA =========== -----BEGIN CERTIFICATE----- MIIFcjCCA1qgAwIBAgIQH51ZWtcvwgZEpYAIaeNe9jANBgkqhkiG9w0BAQUFADA/MQswCQYDVQQG EwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4X DTAyMTIwNTEzMjMzM1oXDTMyMTIwNTEzMjMzM1owPzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dv dmVybm1lbnQgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQAD ggIPADCCAgoCggIBAJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN86aXfTEc2pBsBHH8eV4qN w8XRIePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOUT0b3EEk3+qhZSV1qgQdW8or5 BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQkclSGxtKyyhwOeYHWtXBiCAEuTk8O 1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAtsF/tnyMKtsc2AtJfcdgEWFelq16TheEfO htX7MfP6Mb40qij7cEwdScevLJ1tZqa2jWR+tSBqnTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wov J5pGfaENda1UhhXcSTvxls4Pm6Dso3pdvtUqdULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7 Q3hub/FCVGqY8A2tl+lSXunVanLeavcbYBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHKYS1t B6iEfC3uUSXxY5Ce/eFXiGvviiNtsea9P63RPZYLhY3Naye7twWb7LuRqQoHEgKXTiCQ8P8NHuJB O9NAOueNXdpm5AKwB1KYXA6OM5zCppX7VRluTI6uSw+9wThNXo+EHWbNxWCWtFJaBYmOlXqYwZE8 lSOyDvR5tMl8wUohAgMBAAGjajBoMB0GA1UdDgQWBBTMzO/MKWCkO7GStjz6MmKPrCUVOzAMBgNV HRMEBTADAQH/MDkGBGcqBwAEMTAvMC0CAQAwCQYFKw4DAhoFADAHBgVnKgMAAAQUA5vwIhP/lSg2 09yewDL7MTqKUWUwDQYJKoZIhvcNAQEFBQADggIBAECASvomyc5eMN1PhnR2WPWus4MzeKR6dBcZ TulStbngCnRiqmjKeKBMmo4sIy7VahIkv9Ro04rQ2JyftB8M3jh+Vzj8jeJPXgyfqzvS/3WXy6Tj Zwj/5cAWtUgBfen5Cv8b5Wppv3ghqMKnI6mGq3ZW6A4M9hPdKmaKZEk9GhiHkASfQlK3T8v+R0F2 Ne//AHY2RTKbxkaFXeIksB7jSJaYV0eUVXoPQbFEJPPB/hprv4j9wabak2BegUqZIJxIZhm1AHlU D7gsL0u8qV1bYH+Mh6XgUmMqvtg7hUAV/h62ZT/FS9p+tXo1KaMuephgIqP0fSdOLeq0dDzpD6Qz DxARvBMB1uUO07+1EqLhRSPAzAhuYbeJq4PjJB7mXQfnHyA+z2fI56wwbSdLaG5LKlwCCDTb+Hbk Z6MmnD+iMsJKxYEYMRBWqoTvLQr/uB930r+lWKBi5NdLkXWNiYCYfm3LU05er/ayl4WXudpVBrkk 7tfGOB5jGxI7leFYrPLfhNVfmS8NVVvmONsuP3LpSIXLuykTjx44VbnzssQwmSNOXfJIoRIM3BKQ CZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDeLMDDav7v3Aun+kbfYNucpllQdSNpc5Oy +fwC00fmcc4QAu4njIT/rEUNE1yDMuAlpYYsfPQS -----END CERTIFICATE----- Swisscom Root CA 1 ================== -----BEGIN CERTIFICATE----- MIIF2TCCA8GgAwIBAgIQXAuFXAvnWUHfV8w/f52oNjANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQG EwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0YWwgQ2VydGlmaWNhdGUgU2Vy dmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3QgQ0EgMTAeFw0wNTA4MTgxMjA2MjBaFw0yNTA4 MTgyMjA2MjBaMGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGln aXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAxMIIC IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0LmwqAzZuz8h+BvVM5OAFmUgdbI9m2BtRsiM MW8Xw/qabFbtPMWRV8PNq5ZJkCoZSx6jbVfd8StiKHVFXqrWW/oLJdihFvkcxC7mlSpnzNApbjyF NDhhSbEAn9Y6cV9Nbc5fuankiX9qUvrKm/LcqfmdmUc/TilftKaNXXsLmREDA/7n29uj/x2lzZAe AR81sH8A25Bvxn570e56eqeqDFdvpG3FEzuwpdntMhy0XmeLVNxzh+XTF3xmUHJd1BpYwdnP2IkC b6dJtDZd0KTeByy2dbcokdaXvij1mB7qWybJvbCXc9qukSbraMH5ORXWZ0sKbU/Lz7DkQnGMU3nn 7uHbHaBuHYwadzVcFh4rUx80i9Fs/PJnB3r1re3WmquhsUvhzDdf/X/NTa64H5xD+SpYVUNFvJbN cA78yeNmuk6NO4HLFWR7uZToXTNShXEuT46iBhFRyePLoW4xCGQMwtI89Tbo19AOeCMgkckkKmUp WyL3Ic6DXqTz3kvTaI9GdVyDCW4pa8RwjPWd1yAv/0bSKzjCL3UcPX7ape8eYIVpQtPM+GP+HkM5 haa2Y0EQs3MevNP6yn0WR+Kn1dCjigoIlmJWbjTb2QK5MHXjBNLnj8KwEUAKrNVxAmKLMb7dxiNY MUJDLXT5xp6mig/p/r+D5kNXJLrvRjSq1xIBOO0CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYw HQYDVR0hBBYwFDASBgdghXQBUwABBgdghXQBUwABMBIGA1UdEwEB/wQIMAYBAf8CAQcwHwYDVR0j BBgwFoAUAyUv3m+CATpcLNwroWm1Z9SM0/0wHQYDVR0OBBYEFAMlL95vggE6XCzcK6FptWfUjNP9 MA0GCSqGSIb3DQEBBQUAA4ICAQA1EMvspgQNDQ/NwNurqPKIlwzfky9NfEBWMXrrpA9gzXrzvsMn jgM+pN0S734edAY8PzHyHHuRMSG08NBsl9Tpl7IkVh5WwzW9iAUPWxAaZOHHgjD5Mq2eUCzneAXQ MbFamIp1TpBcahQq4FJHgmDmHtqBsfsUC1rxn9KVuj7QG9YVHaO+htXbD8BJZLsuUBlL0iT43R4H VtA4oJVwIHaM190e3p9xxCPvgxNcoyQVTSlAPGrEqdi3pkSlDfTgnXceQHAm/NrZNuR55LU/vJtl vrsRls/bxig5OgjOR1tTWsWZ/l2p3e9M1MalrQLmjAcSHm8D0W+go/MpvRLHUKKwf4ipmXeascCl OS5cfGniLLDqN2qk4Vrh9VDlg++luyqI54zb/W1elxmofmZ1a3Hqv7HHb6D0jqTsNFFbjCYDcKF3 1QESVwA12yPeDooomf2xEG9L/zgtYE4snOtnta1J7ksfrK/7DZBaZmBwXarNeNQk7shBoJMBkpxq nvy5JMWzFYJ+vq6VK+uxwNrjAWALXmmshFZhvnEX/h0TD/7Gh0Xp/jKgGg0TpJRVcaUWi7rKibCy x/yP2FS1k2Kdzs9Z+z0YzirLNRWCXf9UIltxUvu3yf5gmwBBZPCqKuy2QkPOiWaByIufOVQDJdMW NY6E0F/6MBr1mmz0DlP5OlvRHA== -----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----- Certplus Class 2 Primary CA =========================== -----BEGIN CERTIFICATE----- MIIDkjCCAnqgAwIBAgIRAIW9S/PY2uNp9pTXX8OlRCMwDQYJKoZIhvcNAQEFBQAwPTELMAkGA1UE BhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRswGQYDVQQDExJDbGFzcyAyIFByaW1hcnkgQ0EwHhcN OTkwNzA3MTcwNTAwWhcNMTkwNzA2MjM1OTU5WjA9MQswCQYDVQQGEwJGUjERMA8GA1UEChMIQ2Vy dHBsdXMxGzAZBgNVBAMTEkNsYXNzIDIgUHJpbWFyeSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP ADCCAQoCggEBANxQltAS+DXSCHh6tlJw/W/uz7kRy1134ezpfgSN1sxvc0NXYKwzCkTsA18cgCSR 5aiRVhKC9+Ar9NuuYS6JEI1rbLqzAr3VNsVINyPi8Fo3UjMXEuLRYE2+L0ER4/YXJQyLkcAbmXuZ Vg2v7tK8R1fjeUl7NIknJITesezpWE7+Tt9avkGtrAjFGA7v0lPubNCdEgETjdyAYveVqUSISnFO YFWe2yMZeVYHDD9jC1yw4r5+FfyUM1hBOHTE4Y+L3yasH7WLO7dDWWuwJKZtkIvEcupdM5i3y95e e++U8Rs+yskhwcWYAqqi9lt3m/V+llU0HGdpwPFC40es/CgcZlUCAwEAAaOBjDCBiTAPBgNVHRME CDAGAQH/AgEKMAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQU43Mt38sOKAze3bOkynm4jrvoMIkwEQYJ YIZIAYb4QgEBBAQDAgEGMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly93d3cuY2VydHBsdXMuY29t L0NSTC9jbGFzczIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQCnVM+IRBnL39R/AN9WM2K191EBkOvD P9GIROkkXe/nFL0gt5o8AP5tn9uQ3Nf0YtaLcF3n5QRIqWh8yfFC82x/xXp8HVGIutIKPidd3i1R TtMTZGnkLuPT55sJmabglZvOGtd/vjzOUrMRFcEPF80Du5wlFbqidon8BvEY0JNLDnyCt6X09l/+ 7UCmnYR0ObncHoUW2ikbhiMAybuJfm6AiB4vFLQDJKgybwOaRywwvlbGp0ICcBvqQNi6BQNwB6SW //1IMwrh3KWBkJtN3X3n57LNXMhqlfil9o3EXXgIvnsG1knPGTZQIy4I5p4FTUcY1Rbpsda2ENW7 l7+ijrRU -----END CERTIFICATE----- DST Root CA X3 ============== -----BEGIN CERTIFICATE----- MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/MSQwIgYDVQQK ExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMTDkRTVCBSb290IENBIFgzMB4X DTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVowPzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1 cmUgVHJ1c3QgQ28uMRcwFQYDVQQDEw5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQAD ggEPADCCAQoCggEBAN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmT rE4Orz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEqOLl5CjH9 UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9bxiqKqy69cK3FCxolkHRy xXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40d utolucbY38EVAjqr2m7xPi71XAicPNaDaeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0T AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQ MA0GCSqGSIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69ikug dB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXrAvHRAosZy5Q6XkjE GB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZzR8srzJmwN0jP41ZL9c8PDHIyh8bw RLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubS fZGL+T0yjWW06XyxV3bqxbYoOb8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ -----END CERTIFICATE----- DST ACES CA X6 ============== -----BEGIN CERTIFICATE----- MIIECTCCAvGgAwIBAgIQDV6ZCtadt3js2AdWO4YV2TANBgkqhkiG9w0BAQUFADBbMQswCQYDVQQG EwJVUzEgMB4GA1UEChMXRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QxETAPBgNVBAsTCERTVCBBQ0VT MRcwFQYDVQQDEw5EU1QgQUNFUyBDQSBYNjAeFw0wMzExMjAyMTE5NThaFw0xNzExMjAyMTE5NTha MFsxCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdDERMA8GA1UE CxMIRFNUIEFDRVMxFzAVBgNVBAMTDkRTVCBBQ0VTIENBIFg2MIIBIjANBgkqhkiG9w0BAQEFAAOC AQ8AMIIBCgKCAQEAuT31LMmU3HWKlV1j6IR3dma5WZFcRt2SPp/5DgO0PWGSvSMmtWPuktKe1jzI DZBfZIGxqAgNTNj50wUoUrQBJcWVHAx+PhCEdc/BGZFjz+iokYi5Q1K7gLFViYsx+tC3dr5BPTCa pCIlF3PoHuLTrCq9Wzgh1SpL11V94zpVvddtawJXa+ZHfAjIgrrep4c9oW24MFbCswKBXy314pow GCi4ZtPLAZZv6opFVdbgnf9nKxcCpk4aahELfrd755jWjHZvwTvbUJN+5dCOHze4vbrGn2zpfDPy MjwmR/onJALJfh1biEITajV8fTXpLmaRcpPVMibEdPVTo7NdmvYJywIDAQABo4HIMIHFMA8GA1Ud EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgHGMB8GA1UdEQQYMBaBFHBraS1vcHNAdHJ1c3Rkc3Qu Y29tMGIGA1UdIARbMFkwVwYKYIZIAWUDAgEBATBJMEcGCCsGAQUFBwIBFjtodHRwOi8vd3d3LnRy dXN0ZHN0LmNvbS9jZXJ0aWZpY2F0ZXMvcG9saWN5L0FDRVMtaW5kZXguaHRtbDAdBgNVHQ4EFgQU CXIGThhDD+XWzMNqizF7eI+og7gwDQYJKoZIhvcNAQEFBQADggEBAKPYjtay284F5zLNAdMEA+V2 5FYrnJmQ6AgwbN99Pe7lv7UkQIRJ4dEorsTCOlMwiPH1d25Ryvr/ma8kXxug/fKshMrfqfBfBC6t Fr8hlxCBPeP/h40y3JTlR4peahPJlJU90u7INJXQgNStMgiAVDzgvVJT11J8smk/f3rPanTK+gQq nExaBqXpIK1FZg9p8d2/6eMyi/rgwYZNcjwu2JN4Cir42NInPRmJX1p7ijvMDNpRrscL9yuwNwXs vFcj4jjSm2jzVhKIT0J8uDHEtdvkyCE06UgRNe76x5JXxZ805Mf29w4LTJxoeHtxMcfrHuBnQfO3 oKfN5XozNmr6mis= -----END CERTIFICATE----- TURKTRUST Certificate Services Provider Root 1 ============================================== -----BEGIN CERTIFICATE----- MIID+zCCAuOgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBtzE/MD0GA1UEAww2VMOcUktUUlVTVCBF bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGDAJUUjEP MA0GA1UEBwwGQU5LQVJBMVYwVAYDVQQKDE0oYykgMjAwNSBUw5xSS1RSVVNUIEJpbGdpIMSwbGV0 acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLjAeFw0wNTA1MTMx MDI3MTdaFw0xNTAzMjIxMDI3MTdaMIG3MT8wPQYDVQQDDDZUw5xSS1RSVVNUIEVsZWt0cm9uaWsg U2VydGlmaWthIEhpem1ldCBTYcSfbGF5xLFjxLFzxLExCzAJBgNVBAYMAlRSMQ8wDQYDVQQHDAZB TktBUkExVjBUBgNVBAoMTShjKSAyMDA1IFTDnFJLVFJVU1QgQmlsZ2kgxLBsZXRpxZ9pbSB2ZSBC aWxpxZ9pbSBHw7x2ZW5sacSfaSBIaXptZXRsZXJpIEEuxZ4uMIIBIjANBgkqhkiG9w0BAQEFAAOC AQ8AMIIBCgKCAQEAylIF1mMD2Bxf3dJ7XfIMYGFbazt0K3gNfUW9InTojAPBxhEqPZW8qZSwu5GX yGl8hMW0kWxsE2qkVa2kheiVfrMArwDCBRj1cJ02i67L5BuBf5OI+2pVu32Fks66WJ/bMsW9Xe8i Si9BB35JYbOG7E6mQW6EvAPs9TscyB/C7qju6hJKjRTP8wrgUDn5CDX4EVmt5yLqS8oUBt5CurKZ 8y1UiBAG6uEaPj1nH/vO+3yC6BFdSsG5FOpU2WabfIl9BJpiyelSPJ6c79L1JuTm5Rh8i27fbMx4 W09ysstcP4wFjdFMjK2Sx+F4f2VsSQZQLJ4ywtdKxnWKWU51b0dewQIDAQABoxAwDjAMBgNVHRME BTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAV9VX/N5aAWSGk/KEVTCD21F/aAyT8z5Aa9CEKmu46 sWrv7/hg0Uw2ZkUd82YCdAR7kjCo3gp2D++Vbr3JN+YaDayJSFvMgzbC9UZcWYJWtNX+I7TYVBxE q8Sn5RTOPEFhfEPmzcSBCYsk+1Ql1haolgxnB2+zUEfjHCQo3SqYpGH+2+oSN7wBGjSFvW5P55Fy B0SFHljKVETd96y5y4khctuPwGkplyqjrhgjlxxBKot8KsF8kOipKMDTkcatKIdAaLX/7KfS0zgY nNN9aV3wxqUeJBujR/xpB2jn5Jq07Q+hh4cCzofSSE7hvP/L8XKSRGQDJereW26fyfJOrN3H -----END CERTIFICATE----- TURKTRUST Certificate Services Provider Root 2 ============================================== -----BEGIN CERTIFICATE----- MIIEPDCCAySgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBF bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEP MA0GA1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUg QmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwHhcN MDUxMTA3MTAwNzU3WhcNMTUwOTE2MTAwNzU3WjCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBFbGVr dHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEPMA0G A1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmls acWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwggEiMA0G CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCpNn7DkUNMwxmYCMjHWHtPFoylzkkBH3MOrHUTpvqe LCDe2JAOCtFp0if7qnefJ1Il4std2NiDUBd9irWCPwSOtNXwSadktx4uXyCcUHVPr+G1QRT0mJKI x+XlZEdhR3n9wFHxwZnn3M5q+6+1ATDcRhzviuyV79z/rxAc653YsKpqhRgNF8k+v/Gb0AmJQv2g QrSdiVFVKc8bcLyEVK3BEx+Y9C52YItdP5qtygy/p1Zbj3e41Z55SZI/4PGXJHpsmxcPbe9TmJEr 5A++WXkHeLuXlfSfadRYhwqp48y2WBmfJiGxxFmNskF1wK1pzpwACPI2/z7woQ8arBT9pmAPAgMB AAGjQzBBMB0GA1UdDgQWBBTZN7NOBf3Zz58SFq62iS/rJTqIHDAPBgNVHQ8BAf8EBQMDBwYAMA8G A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHJglrfJ3NgpXiOFX7KzLXb7iNcX/ntt Rbj2hWyfIvwqECLsqrkw9qtY1jkQMZkpAL2JZkH7dN6RwRgLn7Vhy506vvWolKMiVW4XSf/SKfE4 Jl3vpao6+XF75tpYHdN0wgH6PmlYX63LaL4ULptswLbcoCb6dxriJNoaN+BnrdFzgw2lGh1uEpJ+ hGIAF728JRhX8tepb1mIvDS3LoV4nZbcFMMsilKbloxSZj2GFotHuFEJjOp9zYhys2AzsfAKRO8P 9Qk3iCQOLGsgOqL6EfJANZxEaGM7rDNvY7wsu/LSy3Z9fYjYHcgFHW68lKlmjHdxx/qR+i9Rnuk5 UrbnBEI= -----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----- GeoTrust Primary Certification Authority ======================================== -----BEGIN CERTIFICATE----- MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQG EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMoR2VvVHJ1c3QgUHJpbWFyeSBD ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgx CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQ cmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB CgKCAQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9AWbK7hWN b6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjAZIVcFU2Ix7e64HXprQU9 nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE07e9GceBrAqg1cmuXm2bgyxx5X9gaBGge RwLmnWDiNpcB3841kt++Z8dtd1k7j53WkBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGt tm/81w7a4DSwDRp35+MImO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJKoZI hvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ16CePbJC/kRYkRj5K Ts4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl4b7UVXGYNTq+k+qurUKykG/g/CFN NWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6KoKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHa Floxt/m0cYASSJlyc1pZU8FjUjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG 1riR/aYNKxoUAT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk= -----END CERTIFICATE----- thawte Primary Root CA ====================== -----BEGIN CERTIFICATE----- MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCBqTELMAkGA1UE BhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2 aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhv cml6ZWQgdXNlIG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3 MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwg SW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMv KGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNVBAMT FnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCs oPD7gFnUnMekz52hWXMJEEUMDSxuaPFsW0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ 1CRfBsDMRJSUjQJib+ta3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGc q/gcfomk6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6Sk/K aAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94JNqR32HuHUETVPm4p afs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XPr87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUF AAOCAQEAeRHAS7ORtvzw6WfUDW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeE uzLlQRHAd9mzYJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2/qxAeeWsEG89 jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/LHbTY5xZ3Y+m4Q6gLkH3LpVH z7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7jVaMaA== -----END CERTIFICATE----- VeriSign Class 3 Public Primary Certification Authority - G5 ============================================================ -----BEGIN CERTIFICATE----- MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCByjELMAkGA1UE BhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBO ZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVk IHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRp ZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCB yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2ln biBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZvciBh dXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmlt YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw ggEKAoIBAQCvJAgIKXo1nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKz j/i5Vbext0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIzSdhD Y2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQGBO+QueQA5N06tRn/ Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+rCpSx4/VBEnkjWNHiDxpg8v+R70r fk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/ BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2Uv Z2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKvMzEzMA0GCSqG SIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzEp6B4Eq1iDkVwZMXnl2YtmAl+ X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKE KQsTb47bDN0lAtukixlE0kF6BWlKWE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiC Km0oHw0LxOXnGiYZ4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vE ZV8NhnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq -----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----- WellsSecure Public Root Certificate Authority ============================================= -----BEGIN CERTIFICATE----- MIIEvTCCA6WgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoM F1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYw NAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcN MDcxMjEzMTcwNzU0WhcNMjIxMjE0MDAwNzU0WjCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dl bGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYD VQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0G CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDub7S9eeKPCCGeOARBJe+rWxxTkqxtnt3CxC5FlAM1 iGd0V+PfjLindo8796jE2yljDpFoNoqXjopxaAkH5OjUDk/41itMpBb570OYj7OeUt9tkTmPOL13 i0Nj67eT/DBMHAGTthP796EfvyXhdDcsHqRePGj4S78NuR4uNuip5Kf4D8uCdXw1LSLWwr8L87T8 bJVhHlfXBIEyg1J55oNjz7fLY4sR4r1e6/aN7ZVyKLSsEmLpSjPmgzKuBXWVvYSV2ypcm44uDLiB K0HmOFafSZtsdvqKXfcBeYF8wYNABf5x/Qw/zE5gCQ5lRxAvAcAFP4/4s0HvWkJ+We/SlwxlAgMB AAGjggE0MIIBMDAPBgNVHRMBAf8EBTADAQH/MDkGA1UdHwQyMDAwLqAsoCqGKGh0dHA6Ly9jcmwu cGtpLndlbGxzZmFyZ28uY29tL3dzcHJjYS5jcmwwDgYDVR0PAQH/BAQDAgHGMB0GA1UdDgQWBBQm lRkQ2eihl5H/3BnZtQQ+0nMKajCBsgYDVR0jBIGqMIGngBQmlRkQ2eihl5H/3BnZtQQ+0nMKaqGB i6SBiDCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRww GgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMg Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHmCAQEwDQYJKoZIhvcNAQEFBQADggEBALkVsUSRzCPI K0134/iaeycNzXK7mQDKfGYZUMbVmO2rvwNa5U3lHshPcZeG1eMd/ZDJPHV3V3p9+N701NX3leZ0 bh08rnyd2wIDBSxxSyU+B+NemvVmFymIGjifz6pBA4SXa5M4esowRBskRDPQ5NHcKDj0E0M1NSlj qHyita04pO2t/caaH/+Xc/77szWnk4bGdpEA5qxRFsQnMlzbc9qlk1eOPm01JghZ1edE13YgY+es E2fDbbFwRnzVlhE9iW9dqKHrjQrawx0zbKPqZxmamX9LPYNRKh3KL4YMon4QLSvUFpULB6ouFJJJ tylv2G0xffX8oRAHh84vWdw+WNs= -----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----- IGC/A ===== -----BEGIN CERTIFICATE----- MIIEAjCCAuqgAwIBAgIFORFFEJQwDQYJKoZIhvcNAQEFBQAwgYUxCzAJBgNVBAYTAkZSMQ8wDQYD VQQIEwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVE Q1NTSTEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZy MB4XDTAyMTIxMzE0MjkyM1oXDTIwMTAxNzE0MjkyMlowgYUxCzAJBgNVBAYTAkZSMQ8wDQYDVQQI EwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVEQ1NT STEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZyMIIB IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsh/R0GLFMzvABIaIs9z4iPf930Pfeo2aSVz2 TqrMHLmh6yeJ8kbpO0px1R2OLc/mratjUMdUC24SyZA2xtgv2pGqaMVy/hcKshd+ebUyiHDKcMCW So7kVc0dJ5S/znIq7Fz5cyD+vfcuiWe4u0dzEvfRNWk68gq5rv9GQkaiv6GFGvm/5P9JhfejcIYy HF2fYPepraX/z9E0+X1bF8bc1g4oa8Ld8fUzaJ1O/Id8NhLWo4DoQw1VYZTqZDdH6nfK0LJYBcNd frGoRpAxVs5wKpayMLh35nnAvSk7/ZR3TL0gzUEl4C7HG7vupARB0l2tEmqKm0f7yd1GQOGdPDPQ tQIDAQABo3cwdTAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBRjAVBgNVHSAEDjAMMAoGCCqB egF5AQEBMB0GA1UdDgQWBBSjBS8YYFDCiQrdKyFP/45OqDAxNjAfBgNVHSMEGDAWgBSjBS8YYFDC iQrdKyFP/45OqDAxNjANBgkqhkiG9w0BAQUFAAOCAQEABdwm2Pp3FURo/C9mOnTgXeQp/wYHE4RK q89toB9RlPhJy3Q2FLwV3duJL92PoF189RLrn544pEfMs5bZvpwlqwN+Mw+VgQ39FuCIvjfwbF3Q MZsyK10XZZOYYLxuj7GoPB7ZHPOpJkL5ZB3C55L29B5aqhlSXa/oovdgoPaN8In1buAKBQGVyYsg Crpa/JosPL3Dt8ldeCUFP1YUmwza+zpI/pdpXsoQhvdOlgQITeywvl3cO45Pwf2aNjSaTFR+FwNI lQgRHAdvhQh+XU3Endv7rs6y0bO4g2wdsrN58dhwmX7wEwLOXt1R0982gaEbeC9xs/FZTEYYKKuF 0mBWWg== -----END CERTIFICATE----- Security Communication EV RootCA1 ================================= -----BEGIN CERTIFICATE----- MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJKUDElMCMGA1UEChMc U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEqMCgGA1UECxMhU2VjdXJpdHkgQ29tbXVuaWNh dGlvbiBFViBSb290Q0ExMB4XDTA3MDYwNjAyMTIzMloXDTM3MDYwNjAyMTIzMlowYDELMAkGA1UE BhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKjAoBgNVBAsTIVNl Y3VyaXR5IENvbW11bmljYXRpb24gRVYgUm9vdENBMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC AQoCggEBALx/7FebJOD+nLpCeamIivqA4PUHKUPqjgo0No0c+qe1OXj/l3X3L+SqawSERMqm4miO /VVQYg+kcQ7OBzgtQoVQrTyWb4vVog7P3kmJPdZkLjjlHmy1V4qe70gOzXppFodEtZDkBp2uoQSX WHnvIEqCa4wiv+wfD+mEce3xDuS4GBPMVjZd0ZoeUWs5bmB2iDQL87PRsJ3KYeJkHcFGB7hj3R4z ZbOOCVVSPbW9/wfrrWFVGCypaZhKqkDFMxRldAD5kd6vA0jFQFTcD4SQaCDFkpbcLuUCRarAX1T4 bepJz11sS6/vmsJWXMY1VkJqMF/Cq/biPT+zyRGPMUzXn0kCAwEAAaNCMEAwHQYDVR0OBBYEFDVK 9U2vP9eCOKyrcWUXdYydVZPmMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqG SIb3DQEBBQUAA4IBAQCoh+ns+EBnXcPBZsdAS5f8hxOQWsTvoMpfi7ent/HWtWS3irO4G8za+6xm iEHO6Pzk2x6Ipu0nUBsCMCRGef4Eh3CXQHPRwMFXGZpppSeZq51ihPZRwSzJIxXYKLerJRO1RuGG Av8mjMSIkh1W/hln8lXkgKNrnKt34VFxDSDbEJrbvXZ5B3eZKK2aXtqxT0QsNY6llsf9g/BYxnnW mHyojf6GPgcWkuF75x3sM3Z+Qi5KhfmRiWiEA4Glm5q+4zfFVKtWOxgtQaQM+ELbmaDgcm+7XeEW T1MKZPlO9L9OVL14bIjqv5wTJMJwaaJ/D8g8rQjJsJhAoyrniIPtd490 -----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----- Microsec e-Szigno Root CA ========================= -----BEGIN CERTIFICATE----- MIIHqDCCBpCgAwIBAgIRAMy4579OKRr9otxmpRwsDxEwDQYJKoZIhvcNAQEFBQAwcjELMAkGA1UE BhMCSFUxETAPBgNVBAcTCEJ1ZGFwZXN0MRYwFAYDVQQKEw1NaWNyb3NlYyBMdGQuMRQwEgYDVQQL EwtlLVN6aWdubyBDQTEiMCAGA1UEAxMZTWljcm9zZWMgZS1Temlnbm8gUm9vdCBDQTAeFw0wNTA0 MDYxMjI4NDRaFw0xNzA0MDYxMjI4NDRaMHIxCzAJBgNVBAYTAkhVMREwDwYDVQQHEwhCdWRhcGVz dDEWMBQGA1UEChMNTWljcm9zZWMgTHRkLjEUMBIGA1UECxMLZS1Temlnbm8gQ0ExIjAgBgNVBAMT GU1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB AQDtyADVgXvNOABHzNuEwSFpLHSQDCHZU4ftPkNEU6+r+ICbPHiN1I2uuO/TEdyB5s87lozWbxXG d36hL+BfkrYn13aaHUM86tnsL+4582pnS4uCzyL4ZVX+LMsvfUh6PXX5qqAnu3jCBspRwn5mS6/N oqdNAoI/gqyFxuEPkEeZlApxcpMqyabAvjxWTHOSJ/FrtfX9/DAFYJLG65Z+AZHCabEeHXtTRbjc QR/Ji3HWVBTji1R4P770Yjtb9aPs1ZJ04nQw7wHb4dSrmZsqa/i9phyGI0Jf7Enemotb9HI6QMVJ PqW+jqpx62z69Rrkav17fVVA71hu5tnVvCSrwe+3AgMBAAGjggQ3MIIEMzBnBggrBgEFBQcBAQRb MFkwKAYIKwYBBQUHMAGGHGh0dHBzOi8vcmNhLmUtc3ppZ25vLmh1L29jc3AwLQYIKwYBBQUHMAKG IWh0dHA6Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNydDAPBgNVHRMBAf8EBTADAQH/MIIBcwYD VR0gBIIBajCCAWYwggFiBgwrBgEEAYGoGAIBAQEwggFQMCgGCCsGAQUFBwIBFhxodHRwOi8vd3d3 LmUtc3ppZ25vLmh1L1NaU1ovMIIBIgYIKwYBBQUHAgIwggEUHoIBEABBACAAdABhAG4A+gBzAO0A dAB2AOEAbgB5ACAA6QByAHQAZQBsAG0AZQB6AOkAcwDpAGgAZQB6ACAA6QBzACAAZQBsAGYAbwBn AGEAZADhAHMA4QBoAG8AegAgAGEAIABTAHoAbwBsAGcA4QBsAHQAYQB0APMAIABTAHoAbwBsAGcA 4QBsAHQAYQB0AOEAcwBpACAAUwB6AGEAYgDhAGwAeQB6AGEAdABhACAAcwB6AGUAcgBpAG4AdAAg AGsAZQBsAGwAIABlAGwAagDhAHIAbgBpADoAIABoAHQAdABwADoALwAvAHcAdwB3AC4AZQAtAHMA egBpAGcAbgBvAC4AaAB1AC8AUwBaAFMAWgAvMIHIBgNVHR8EgcAwgb0wgbqggbeggbSGIWh0dHA6 Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNybIaBjmxkYXA6Ly9sZGFwLmUtc3ppZ25vLmh1L0NO PU1pY3Jvc2VjJTIwZS1Temlnbm8lMjBSb290JTIwQ0EsT1U9ZS1Temlnbm8lMjBDQSxPPU1pY3Jv c2VjJTIwTHRkLixMPUJ1ZGFwZXN0LEM9SFU/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdDtiaW5h cnkwDgYDVR0PAQH/BAQDAgEGMIGWBgNVHREEgY4wgYuBEGluZm9AZS1zemlnbm8uaHWkdzB1MSMw IQYDVQQDDBpNaWNyb3NlYyBlLVN6aWduw7MgUm9vdCBDQTEWMBQGA1UECwwNZS1TemlnbsOzIEhT WjEWMBQGA1UEChMNTWljcm9zZWMgS2Z0LjERMA8GA1UEBxMIQnVkYXBlc3QxCzAJBgNVBAYTAkhV MIGsBgNVHSMEgaQwgaGAFMegSXUWYYTbMUuE0vE3QJDvTtz3oXakdDByMQswCQYDVQQGEwJIVTER MA8GA1UEBxMIQnVkYXBlc3QxFjAUBgNVBAoTDU1pY3Jvc2VjIEx0ZC4xFDASBgNVBAsTC2UtU3pp Z25vIENBMSIwIAYDVQQDExlNaWNyb3NlYyBlLVN6aWdubyBSb290IENBghEAzLjnv04pGv2i3Gal HCwPETAdBgNVHQ4EFgQUx6BJdRZhhNsxS4TS8TdAkO9O3PcwDQYJKoZIhvcNAQEFBQADggEBANMT nGZjWS7KXHAM/IO8VbH0jgdsZifOwTsgqRy7RlRw7lrMoHfqaEQn6/Ip3Xep1fvj1KcExJW4C+FE aGAHQzAxQmHl7tnlJNUb3+FKG6qfx1/4ehHqE5MAyopYse7tDk2016g2JnzgOsHVV4Lxdbb9iV/a 86g4nzUGCM4ilb7N1fy+W955a9x6qWVmvrElWl/tftOsRm1M9DKHtCAE4Gx4sHfRhUZLphK3dehK yVZs15KrnfVJONJPU+NVkBHbmJbGSfI+9J8b4PeI3CVimUTYc78/MPMMNz7UwiiAc7EBt51alhQB S6kRnSlqLtBdgcDPsiBDxwPgN05dCtxZICU= -----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----- TC TrustCenter Class 2 CA II ============================ -----BEGIN CERTIFICATE----- MIIEqjCCA5KgAwIBAgIOLmoAAQACH9dSISwRXDswDQYJKoZIhvcNAQEFBQAwdjELMAkGA1UEBhMC REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNVBAsTGVRDIFRydXN0Q2VudGVy IENsYXNzIDIgQ0ExJTAjBgNVBAMTHFRDIFRydXN0Q2VudGVyIENsYXNzIDIgQ0EgSUkwHhcNMDYw MTEyMTQzODQzWhcNMjUxMjMxMjI1OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1 c3RDZW50ZXIgR21iSDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQTElMCMGA1UE AxMcVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC AQoCggEBAKuAh5uO8MN8h9foJIIRszzdQ2Lu+MNF2ujhoF/RKrLqk2jftMjWQ+nEdVl//OEd+DFw IxuInie5e/060smp6RQvkL4DUsFJzfb95AhmC1eKokKguNV/aVyQMrKXDcpK3EY+AlWJU+MaWss2 xgdW94zPEfRMuzBwBJWl9jmM/XOBCH2JXjIeIqkiRUuwZi4wzJ9l/fzLganx4Duvo4bRierERXlQ Xa7pIXSSTYtZgo+U4+lK8edJsBTj9WLL1XK9H7nSn6DNqPoByNkN39r8R52zyFTfSUrxIan+GE7u SNQZu+995OKdy1u2bv/jzVrndIIFuoAlOMvkaZ6vQaoahPUCAwEAAaOCATQwggEwMA8GA1UdEwEB /wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTjq1RMgKHbVkO3kUrL84J6E1wIqzCB 7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRydXN0Y2VudGVyLmRlL2NybC92Mi90 Y19jbGFzc18yX2NhX0lJLmNybIaBn2xkYXA6Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBU cnVzdENlbnRlciUyMENsYXNzJTIwMiUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21i SCxPVT1yb290Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEAjNfffu4bgBCzg/XbEeprS6iSGNn3Bzn1LL4G dXpoUxUc6krtXvwjshOg0wn/9vYua0Fxec3ibf2uWWuFHbhOIprtZjluS5TmVfwLG4t3wVMTZonZ KNaL80VKY7f9ewthXbhtvsPcW3nS7Yblok2+XnR8au0WOB9/WIFaGusyiC2y8zl3gK9etmF1Kdsj TYjKUCjLhdLTEKJZbtOTVAB6okaVhgWcqRmY5TFyDADiZ9lA4CQze28suVyrZZ0srHbqNZn1l7kP JOzHdiEoZa5X6AeIdUpWoNIFOqTmjZKILPPy4cHGYdtBxceb9w4aUUXCYWvcZCcXjFq32nQozZfk vQ== -----END CERTIFICATE----- TC TrustCenter Universal CA I ============================= -----BEGIN CERTIFICATE----- MIID3TCCAsWgAwIBAgIOHaIAAQAC7LdggHiNtgYwDQYJKoZIhvcNAQEFBQAweTELMAkGA1UEBhMC REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxJDAiBgNVBAsTG1RDIFRydXN0Q2VudGVy IFVuaXZlcnNhbCBDQTEmMCQGA1UEAxMdVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBIEkwHhcN MDYwMzIyMTU1NDI4WhcNMjUxMjMxMjI1OTU5WjB5MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMg VHJ1c3RDZW50ZXIgR21iSDEkMCIGA1UECxMbVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBMSYw JAYDVQQDEx1UQyBUcnVzdENlbnRlciBVbml2ZXJzYWwgQ0EgSTCCASIwDQYJKoZIhvcNAQEBBQAD ggEPADCCAQoCggEBAKR3I5ZEr5D0MacQ9CaHnPM42Q9e3s9B6DGtxnSRJJZ4Hgmgm5qVSkr1YnwC qMqs+1oEdjneX/H5s7/zA1hV0qq34wQi0fiU2iIIAI3TfCZdzHd55yx4Oagmcw6iXSVphU9VDprv xrlE4Vc93x9UIuVvZaozhDrzznq+VZeujRIPFDPiUHDDSYcTvFHe15gSWu86gzOSBnWLknwSaHtw ag+1m7Z3W0hZneTvWq3zwZ7U10VOylY0Ibw+F1tvdwxIAUMpsN0/lm7mlaoMwCC2/T42J5zjXM9O gdwZu5GQfezmlwQek8wiSdeXhrYTCjxDI3d+8NzmzSQfO4ObNDqDNOMCAwEAAaNjMGEwHwYDVR0j BBgwFoAUkqR1LKSevoFE63n8isWVpesQdXMwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC AYYwHQYDVR0OBBYEFJKkdSyknr6BROt5/IrFlaXrEHVzMA0GCSqGSIb3DQEBBQUAA4IBAQAo0uCG 1eb4e/CX3CJrO5UUVg8RMKWaTzqwOuAGy2X17caXJ/4l8lfmXpWMPmRgFVp/Lw0BxbFg/UU1z/Cy vwbZ71q+s2IhtNerNXxTPqYn8aEt2hojnczd7Dwtnic0XQ/CNnm8yUpiLe1r2X1BQ3y2qsrtYbE3 ghUJGooWMNjsydZHcnhLEEYUjl8Or+zHL6sQ17bxbuyGssLoDZJz3KL0Dzq/YSMQiZxIQG5wALPT ujdEWBF6AmqI8Dc08BnprNRlc/ZpjGSUOnmFKbAWKwyCPwacx/0QK54PLLae4xW/2TYcuiUaUj0a 7CIMHOCkoj3w6DnPgcB77V0fb8XQC9eY -----END CERTIFICATE----- Deutsche Telekom Root CA 2 ========================== -----BEGIN CERTIFICATE----- MIIDnzCCAoegAwIBAgIBJjANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJERTEcMBoGA1UEChMT RGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxlU2VjIFRydXN0IENlbnRlcjEjMCEG A1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290IENBIDIwHhcNOTkwNzA5MTIxMTAwWhcNMTkwNzA5 MjM1OTAwWjBxMQswCQYDVQQGEwJERTEcMBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0G A1UECxMWVC1UZWxlU2VjIFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBS b290IENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrC6M14IspFLEUha88EOQ5 bzVdSq7d6mGNlUn0b2SjGmBmpKlAIoTZ1KXleJMOaAGtuU1cOs7TuKhCQN/Po7qCWWqSG6wcmtoI KyUn+WkjR/Hg6yx6m/UTAtB+NHzCnjwAWav12gz1MjwrrFDa1sPeg5TKqAyZMg4ISFZbavva4VhY AUlfckE8FQYBjl2tqriTtM2e66foai1SNNs671x1Udrb8zH57nGYMsRUFUQM+ZtV7a3fGAigo4aK Se5TBY8ZTNXeWHmb0mocQqvF1afPaA+W5OFhmHZhyJF81j4A4pFQh+GdCuatl9Idxjp9y7zaAzTV jlsB9WoHtxa2bkp/AgMBAAGjQjBAMB0GA1UdDgQWBBQxw3kbuvVT1xfgiXotF2wKsyudMzAPBgNV HRMECDAGAQH/AgEFMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAlGRZrTlk5ynr E/5aw4sTV8gEJPB0d8Bg42f76Ymmg7+Wgnxu1MM9756AbrsptJh6sTtU6zkXR34ajgv8HzFZMQSy zhfzLMdiNlXiItiJVbSYSKpk+tYcNthEeFpaIzpXl/V6ME+un2pMSyuOoAPjPuCp1NJ70rOo4nI8 rZ7/gFnkm0W09juwzTkZmDLl6iFhkOQxIY40sfcvNUqFENrnijchvllj4PKFiDFT1FQUhXB59C4G dyd1Lx+4ivn+xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU Cm26OWMohpLzGITY+9HPBVZkVw== -----END CERTIFICATE----- ComSign Secured CA ================== -----BEGIN CERTIFICATE----- MIIDqzCCApOgAwIBAgIRAMcoRwmzuGxFjB36JPU2TukwDQYJKoZIhvcNAQEFBQAwPDEbMBkGA1UE AxMSQ29tU2lnbiBTZWN1cmVkIENBMRAwDgYDVQQKEwdDb21TaWduMQswCQYDVQQGEwJJTDAeFw0w NDAzMjQxMTM3MjBaFw0yOTAzMTYxNTA0NTZaMDwxGzAZBgNVBAMTEkNvbVNpZ24gU2VjdXJlZCBD QTEQMA4GA1UEChMHQ29tU2lnbjELMAkGA1UEBhMCSUwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw ggEKAoIBAQDGtWhfHZQVw6QIVS3joFd67+l0Kru5fFdJGhFeTymHDEjWaueP1H5XJLkGieQcPOqs 49ohgHMhCu95mGwfCP+hUH3ymBvJVG8+pSjsIQQPRbsHPaHA+iqYHU4Gk/v1iDurX8sWv+bznkqH 7Rnqwp9D5PGBpX8QTz7RSmKtUxvLg/8HZaWSLWapW7ha9B20IZFKF3ueMv5WJDmyVIRD9YTC2LxB kMyd1mja6YJQqTtoz7VdApRgFrFD2UNd3V2Hbuq7s8lr9gOUCXDeFhF6K+h2j0kQmHe5Y1yLM5d1 9guMsqtb3nQgJT/j8xH5h2iGNXHDHYwt6+UarA9z1YJZQIDTAgMBAAGjgacwgaQwDAYDVR0TBAUw AwEB/zBEBgNVHR8EPTA7MDmgN6A1hjNodHRwOi8vZmVkaXIuY29tc2lnbi5jby5pbC9jcmwvQ29t U2lnblNlY3VyZWRDQS5jcmwwDgYDVR0PAQH/BAQDAgGGMB8GA1UdIwQYMBaAFMFL7XC29z58ADsA j8c+DkWfHl3sMB0GA1UdDgQWBBTBS+1wtvc+fAA7AI/HPg5Fnx5d7DANBgkqhkiG9w0BAQUFAAOC AQEAFs/ukhNQq3sUnjO2QiBq1BW9Cav8cujvR3qQrFHBZE7piL1DRYHjZiM/EoZNGeQFsOY3wo3a BijJD4mkU6l1P7CW+6tMM1X5eCZGbxs2mPtCdsGCuY7e+0X5YxtiOzkGynd6qDwJz2w2PQ8KRUtp FhpFfTMDZflScZAmlaxMDPWLkz/MdXSFmLr/YnpNH4n+rr2UAJm/EaXc4HnFFgt9AmEd6oX5AhVP 51qJThRv4zdLhfXBPGHg/QVBspJ/wx2g0K5SZGBrGMYmnNj1ZOQ2GmKfig8+/21OGVZOIJFsnzQz OjRXUDpvgV4GxvU+fE6OK85lBi5d0ipTdF7Tbieejw== -----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----- T\xc3\x9c\x42\xC4\xB0TAK UEKAE K\xC3\xB6k Sertifika Hizmet Sa\xC4\x9Flay\xc4\xb1\x63\xc4\xb1s\xc4\xb1 - S\xC3\xBCr\xC3\xBCm 3 ============================================================================================================================= -----BEGIN CERTIFICATE----- MIIFFzCCA/+gAwIBAgIBETANBgkqhkiG9w0BAQUFADCCASsxCzAJBgNVBAYTAlRSMRgwFgYDVQQH DA9HZWJ6ZSAtIEtvY2FlbGkxRzBFBgNVBAoMPlTDvHJraXllIEJpbGltc2VsIHZlIFRla25vbG9q aWsgQXJhxZ90xLFybWEgS3VydW11IC0gVMOcQsSwVEFLMUgwRgYDVQQLDD9VbHVzYWwgRWxla3Ry b25payB2ZSBLcmlwdG9sb2ppIEFyYcWfdMSxcm1hIEVuc3RpdMO8c8O8IC0gVUVLQUUxIzAhBgNV BAsMGkthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppMUowSAYDVQQDDEFUw5xCxLBUQUsgVUVLQUUg S8O2ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSAtIFPDvHLDvG0gMzAeFw0wNzA4 MjQxMTM3MDdaFw0xNzA4MjExMTM3MDdaMIIBKzELMAkGA1UEBhMCVFIxGDAWBgNVBAcMD0dlYnpl IC0gS29jYWVsaTFHMEUGA1UECgw+VMO8cmtpeWUgQmlsaW1zZWwgdmUgVGVrbm9sb2ppayBBcmHF n3TEsXJtYSBLdXJ1bXUgLSBUw5xCxLBUQUsxSDBGBgNVBAsMP1VsdXNhbCBFbGVrdHJvbmlrIHZl IEtyaXB0b2xvamkgQXJhxZ90xLFybWEgRW5zdGl0w7xzw7wgLSBVRUtBRTEjMCEGA1UECwwaS2Ft dSBTZXJ0aWZpa2FzeW9uIE1lcmtlemkxSjBIBgNVBAMMQVTDnELEsFRBSyBVRUtBRSBLw7ZrIFNl cnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIC0gU8O8csO8bSAzMIIBIjANBgkqhkiG9w0B AQEFAAOCAQ8AMIIBCgKCAQEAim1L/xCIOsP2fpTo6iBkcK4hgb46ezzb8R1Sf1n68yJMlaCQvEhO Eav7t7WNeoMojCZG2E6VQIdhn8WebYGHV2yKO7Rm6sxA/OOqbLLLAdsyv9Lrhc+hDVXDWzhXcLh1 xnnRFDDtG1hba+818qEhTsXOfJlfbLm4IpNQp81McGq+agV/E5wrHur+R84EpW+sky58K5+eeROR 6Oqeyjh1jmKwlZMq5d/pXpduIF9fhHpEORlAHLpVK/swsoHvhOPc7Jg4OQOFCKlUAwUp8MmPi+oL hmUZEdPpCSPeaJMDyTYcIW7OjGbxmTDY17PDHfiBLqi9ggtm/oLL4eAagsNAgQIDAQABo0IwQDAd BgNVHQ4EFgQUvYiHyY/2pAoLquvF/pEjnatKijIwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAB18+kmPNOm3JpIWmgV050vQbTlswyb2zrgxvMTfvCr4 N5EY3ATIZJkrGG2AA1nJrvhY0D7twyOfaTyGOBye79oneNGEN3GKPEs5z35FBtYt2IpNeBLWrcLT y9LQQfMmNkqblWwM7uXRQydmwYj3erMgbOqwaSvHIOgMA8RBBZniP+Rr+KCGgceExh/VS4ESshYh LBOhgLJeDEoTniDYYkCrkOpkSi+sDQESeUWoL4cZaMjihccwsnX5OD+ywJO0a+IDRM5noN+J1q2M dqMTw5RhK2vZbMEHCiIHhWyFJEapvj+LeISCfiQMnf2BN+MlqO02TpUsyZyQ2uypQjyttgI= -----END CERTIFICATE----- Buypass Class 2 CA 1 ==================== -----BEGIN CERTIFICATE----- MIIDUzCCAjugAwIBAgIBATANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU QnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3MgQ2xhc3MgMiBDQSAxMB4XDTA2 MTAxMzEwMjUwOVoXDTE2MTAxMzEwMjUwOVowSzELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBh c3MgQVMtOTgzMTYzMzI3MR0wGwYDVQQDDBRCdXlwYXNzIENsYXNzIDIgQ0EgMTCCASIwDQYJKoZI hvcNAQEBBQADggEPADCCAQoCggEBAIs8B0XY9t/mx8q6jUPFR42wWsE425KEHK8T1A9vNkYgxC7M cXA0ojTTNy7Y3Tp3L8DrKehc0rWpkTSHIln+zNvnma+WwajHQN2lFYxuyHyXA8vmIPLXl18xoS83 0r7uvqmtqEyeIWZDO6i88wmjONVZJMHCR3axiFyCO7srpgTXjAePzdVBHfCuuCkslFJgNJQ72uA4 0Z0zPhX0kzLFANq1KWYOOngPIVJfAuWSeyXTkh4vFZ2B5J2O6O+JzhRMVB0cgRJNcKi+EAUXfh/R uFdV7c27UsKwHnjCTTZoy1YmwVLBvXb3WNVyfh9EdrsAiR0WnVE1703CVu9r4Iw7DekCAwEAAaNC MEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUP42aWYv8e3uco684sDntkHGA1sgwDgYDVR0P AQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQAVGn4TirnoB6NLJzKyQJHyIdFkhb5jatLPgcIV 1Xp+DCmsNx4cfHZSldq1fyOhKXdlyTKdqC5Wq2B2zha0jX94wNWZUYN/Xtm+DKhQ7SLHrQVMdvvt 7h5HZPb3J31cKA9FxVxiXqaakZG3Uxcu3K1gnZZkOb1naLKuBctN518fV4bVIJwo+28TOPX2EZL2 fZleHwzoq0QkKXJAPTZSr4xYkHPB7GEseaHsh7U/2k3ZIQAw3pDaDtMaSKk+hQsUi4y8QZ5q9w5w wDX3OaJdZtB7WZ+oRxKaJyOkLY4ng5IgodcVf/EuGO70SH8vf/GhGLWhC5SgYiAynB321O+/TIho -----END CERTIFICATE----- Buypass Class 3 CA 1 ==================== -----BEGIN CERTIFICATE----- MIIDUzCCAjugAwIBAgIBAjANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU QnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3MgQ2xhc3MgMyBDQSAxMB4XDTA1 MDUwOTE0MTMwM1oXDTE1MDUwOTE0MTMwM1owSzELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBh c3MgQVMtOTgzMTYzMzI3MR0wGwYDVQQDDBRCdXlwYXNzIENsYXNzIDMgQ0EgMTCCASIwDQYJKoZI hvcNAQEBBQADggEPADCCAQoCggEBAKSO13TZKWTeXx+HgJHqTjnmGcZEC4DVC69TB4sSveZn8AKx ifZgisRbsELRwCGoy+Gb72RRtqfPFfV0gGgEkKBYouZ0plNTVUhjP5JW3SROjvi6K//zNIqeKNc0 n6wv1g/xpC+9UrJJhW05NfBEMJNGJPO251P7vGGvqaMU+8IXF4Rs4HyI+MkcVyzwPX6UvCWThOia AJpFBUJXgPROztmuOfbIUxAMZTpHe2DC1vqRycZxbL2RhzyRhkmr8w+gbCZ2Xhysm3HljbybIR6c 1jh+JIAVMYKWsUnTYjdbiAwKYjT+p0h+mbEwi5A3lRyoH6UsjfRVyNvdWQrCrXig9IsCAwEAAaNC MEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUOBTmyPCppAP0Tj4io1vy1uCtQHQwDgYDVR0P AQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQABZ6OMySU9E2NdFm/soT4JXJEVKirZgCFPBdy7 pYmrEzMqnji3jG8CcmPHc3ceCQa6Oyh7pEfJYWsICCD8igWKH7y6xsL+z27sEzNxZy5p+qksP2bA EllNC1QCkoS72xLvg3BweMhT+t/Gxv/ciC8HwEmdMldg0/L2mSlf56oBzKwzqBwKu5HEA6BvtjT5 htOzdlSY9EqBs1OdTUDs5XcTRa9bqh/YL0yCe/4qxFi7T/ye/QNlGioOw6UgFpRreaaiErS7GqQj el/wroQk5PMr+4okoyeYZdowdXb8GZHo2+ubPzK/QJcHJrrM85SFSnonk8+QQtS4Wxam58tAA915 -----END CERTIFICATE----- EBG Elektronik Sertifika Hizmet Sa\xC4\x9Flay\xc4\xb1\x63\xc4\xb1s\xc4\xb1 ========================================================================== -----BEGIN CERTIFICATE----- MIIF5zCCA8+gAwIBAgIITK9zQhyOdAIwDQYJKoZIhvcNAQEFBQAwgYAxODA2BgNVBAMML0VCRyBF bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMTcwNQYDVQQKDC5FQkcg QmlsacWfaW0gVGVrbm9sb2ppbGVyaSB2ZSBIaXptZXRsZXJpIEEuxZ4uMQswCQYDVQQGEwJUUjAe Fw0wNjA4MTcwMDIxMDlaFw0xNjA4MTQwMDMxMDlaMIGAMTgwNgYDVQQDDC9FQkcgRWxla3Ryb25p ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTE3MDUGA1UECgwuRUJHIEJpbGnFn2lt IFRla25vbG9qaWxlcmkgdmUgSGl6bWV0bGVyaSBBLsWeLjELMAkGA1UEBhMCVFIwggIiMA0GCSqG SIb3DQEBAQUAA4ICDwAwggIKAoICAQDuoIRh0DpqZhAy2DE4f6en5f2h4fuXd7hxlugTlkaDT7by X3JWbhNgpQGR4lvFzVcfd2NR/y8927k/qqk153nQ9dAktiHq6yOU/im/+4mRDGSaBUorzAzu8T2b gmmkTPiab+ci2hC6X5L8GCcKqKpE+i4stPtGmggDg3KriORqcsnlZR9uKg+ds+g75AxuetpX/dfr eYteIAbTdgtsApWjluTLdlHRKJ2hGvxEok3MenaoDT2/F08iiFD9rrbskFBKW5+VQarKD7JK/oCZ TqNGFav4c0JqwmZ2sQomFd2TkuzbqV9UIlKRcF0T6kjsbgNs2d1s/OsNA/+mgxKb8amTD8UmTDGy Y5lhcucqZJnSuOl14nypqZoaqsNW2xCaPINStnuWt6yHd6i58mcLlEOzrz5z+kI2sSXFCjEmN1Zn uqMLfdb3ic1nobc6HmZP9qBVFCVMLDMNpkGMvQQxahByCp0OLna9XvNRiYuoP1Vzv9s6xiQFlpJI qkuNKgPlV5EQ9GooFW5Hd4RcUXSfGenmHmMWOeMRFeNYGkS9y8RsZteEBt8w9DeiQyJ50hBs37vm ExH8nYQKE3vwO9D8owrXieqWfo1IhR5kX9tUoqzVegJ5a9KK8GfaZXINFHDk6Y54jzJ0fFfy1tb0 Nokb+Clsi7n2l9GkLqq+CxnCRelwXQIDAJ3Zo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB /wQEAwIBBjAdBgNVHQ4EFgQU587GT/wWZ5b6SqMHwQSny2re2kcwHwYDVR0jBBgwFoAU587GT/wW Z5b6SqMHwQSny2re2kcwDQYJKoZIhvcNAQEFBQADggIBAJuYml2+8ygjdsZs93/mQJ7ANtyVDR2t FcU22NU57/IeIl6zgrRdu0waypIN30ckHrMk2pGI6YNw3ZPX6bqz3xZaPt7gyPvT/Wwp+BVGoGgm zJNSroIBk5DKd8pNSe/iWtkqvTDOTLKBtjDOWU/aWR1qeqRFsIImgYZ29fUQALjuswnoT4cCB64k XPBfrAowzIpAoHMEwfuJJPaaHFy3PApnNgUIMbOv2AFoKuB4j3TeuFGkjGwgPaL7s9QJ/XvCgKqT bCmYIai7FvOpEl90tYeY8pUm3zTvilORiF0alKM/fCL414i6poyWqD1SNGKfAB5UVUJnxk1Gj7sU RT0KlhaOEKGXmdXTMIXM3rRyt7yKPBgpaP3ccQfuJDlq+u2lrDgv+R4QDgZxGhBM/nV+/x5XOULK 1+EVoVZVWRvRo68R2E7DpSvvkL/A7IITW43WciyTTo9qKd+FPNMN4KIYEsxVL0e3p5sC/kH2iExt 2qkBR4NkJ2IQgtYSe14DHzSpyZH+r11thie3I6p1GMog57AP14kOpmciY/SDQSsGS7tY1dHXt7kQ Y9iJSrSq3RZj9W6+YKH47ejWkE8axsWgKdOnIaj1Wjz3x0miIZpKlVIglnKaZsv30oZDfCK+lvm9 AahH3eU7QPl1K5srRmSGjR70j/sHd9DqSaIcjVIUpgqT -----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----- CNNIC ROOT ========== -----BEGIN CERTIFICATE----- MIIDVTCCAj2gAwIBAgIESTMAATANBgkqhkiG9w0BAQUFADAyMQswCQYDVQQGEwJDTjEOMAwGA1UE ChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1QwHhcNMDcwNDE2MDcwOTE0WhcNMjcwNDE2MDcw OTE0WjAyMQswCQYDVQQGEwJDTjEOMAwGA1UEChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1Qw ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDTNfc/c3et6FtzF8LRb+1VvG7q6KR5smzD o+/hn7E7SIX1mlwhIhAsxYLO2uOabjfhhyzcuQxauohV3/2q2x8x6gHx3zkBwRP9SFIhxFXf2tiz VHa6dLG3fdfA6PZZxU3Iva0fFNrfWEQlMhkqx35+jq44sDB7R3IJMfAw28Mbdim7aXZOV/kbZKKT VrdvmW7bCgScEeOAH8tjlBAKqeFkgjH5jCftppkA9nCTGPihNIaj3XrCGHn2emU1z5DrvTOTn1Or czvmmzQgLx3vqR1jGqCA2wMv+SYahtKNu6m+UjqHZ0gNv7Sg2Ca+I19zN38m5pIEo3/PIKe38zrK y5nLAgMBAAGjczBxMBEGCWCGSAGG+EIBAQQEAwIABzAfBgNVHSMEGDAWgBRl8jGtKvf33VKWCscC wQ7vptU7ETAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIB/jAdBgNVHQ4EFgQUZfIxrSr3991S lgrHAsEO76bVOxEwDQYJKoZIhvcNAQEFBQADggEBAEs17szkrr/Dbq2flTtLP1se31cpolnKOOK5 Gv+e5m4y3R6u6jW39ZORTtpC4cMXYFDy0VwmuYK36m3knITnA3kXr5g9lNvHugDnuL8BV8F3RTIM O/G0HAiw/VGgod2aHRM2mm23xzy54cXZF/qD1T0VoDy7HgviyJA/qIYM/PmLXoXLT1tLYhFHxUV8 BS9BsZ4QaRuZluBVeftOhpm4lNqGOGqTo+fLbuXf6iFViZx9fX+Y9QCJ7uOEwFyWtcVG6kbghVW2 G8kS1sHNzYDzAgE8yGnLRUhj2JTQ7IUOO04RZfSCjKY9ri4ilAnIXOo8gV0WKgOXFlUJ24pBgp5m mxE= -----END CERTIFICATE----- ApplicationCA - Japanese Government =================================== -----BEGIN CERTIFICATE----- MIIDoDCCAoigAwIBAgIBMTANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJKUDEcMBoGA1UEChMT SmFwYW5lc2UgR292ZXJubWVudDEWMBQGA1UECxMNQXBwbGljYXRpb25DQTAeFw0wNzEyMTIxNTAw MDBaFw0xNzEyMTIxNTAwMDBaMEMxCzAJBgNVBAYTAkpQMRwwGgYDVQQKExNKYXBhbmVzZSBHb3Zl cm5tZW50MRYwFAYDVQQLEw1BcHBsaWNhdGlvbkNBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB CgKCAQEAp23gdE6Hj6UG3mii24aZS2QNcfAKBZuOquHMLtJqO8F6tJdhjYq+xpqcBrSGUeQ3DnR4 fl+Kf5Sk10cI/VBaVuRorChzoHvpfxiSQE8tnfWuREhzNgaeZCw7NCPbXCbkcXmP1G55IrmTwcrN wVbtiGrXoDkhBFcsovW8R0FPXjQilbUfKW1eSvNNcr5BViCH/OlQR9cwFO5cjFW6WY2H/CPek9AE jP3vbb3QesmlOmpyM8ZKDQUXKi17safY1vC+9D/qDihtQWEjdnjDuGWk81quzMKq2edY3rZ+nYVu nyoKb58DKTCXKB28t89UKU5RMfkntigm/qJj5kEW8DOYRwIDAQABo4GeMIGbMB0GA1UdDgQWBBRU WssmP3HMlEYNllPqa0jQk/5CdTAOBgNVHQ8BAf8EBAMCAQYwWQYDVR0RBFIwUKROMEwxCzAJBgNV BAYTAkpQMRgwFgYDVQQKDA/ml6XmnKzlm73mlL/lupwxIzAhBgNVBAsMGuOCouODl+ODquOCseOD vOOCt+ODp+ODs0NBMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADlqRHZ3ODrs o2dGD/mLBqj7apAxzn7s2tGJfHrrLgy9mTLnsCTWw//1sogJhyzjVOGjprIIC8CFqMjSnHH2HZ9g /DgzE+Ge3Atf2hZQKXsvcJEPmbo0NI2VdMV+eKlmXb3KIXdCEKxmJj3ekav9FfBv7WxfEPjzFvYD io+nEhEMy/0/ecGc/WLuo89UDNErXxc+4z6/wCs+CZv+iKZ+tJIX/COUgb1up8WMwusRRdv4QcmW dupwX3kSa+SjB1oF7ydJzyGfikwJcGapJsErEU4z0g781mzSDjJkaP+tBXhfAx2o45CsJOAPQKdL rosot4LKGAfmt1t06SAZf7IbiVQ= -----END CERTIFICATE----- GeoTrust Primary Certification Authority - G3 ============================================= -----BEGIN CERTIFICATE----- MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UE BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChjKSAyMDA4IEdlb1RydXN0 IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFy eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIz NTk1OVowgZgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAo YykgMjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNVBAMT LUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZI hvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz+uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5j K/BGvESyiaHAKAxJcCGVn2TAppMSAmUmhsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdE c5IiaacDiGydY8hS2pgn5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3C IShwiP/WJmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exALDmKu dlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZChuOl1UcCAwEAAaNC MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMR5yo6hTgMdHNxr 2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IBAQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9 cr5HqQ6XErhK8WTTOd8lNNTBzU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbE Ap7aDHdlDkQNkv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUHSJsMC8tJP33s t/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2Gspki4cErx5z481+oghLrGREt -----END CERTIFICATE----- thawte Primary Root CA - G2 =========================== -----BEGIN CERTIFICATE----- MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDELMAkGA1UEBhMC VVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMpIDIwMDcgdGhhd3RlLCBJbmMu IC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3Qg Q0EgLSBHMjAeFw0wNzExMDUwMDAwMDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEV MBMGA1UEChMMdGhhd3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBG b3IgYXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAt IEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/BebfowJPDQfGAFG6DAJS LSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6papu+7qzcMBniKI11KOasf2twu8x+qi5 8/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU mtgAMADna3+FGO6Lts6KDPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUN G4k8VIZ3KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41oxXZ3K rr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg== -----END CERTIFICATE----- thawte Primary Root CA - G3 =========================== -----BEGIN CERTIFICATE----- MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCBrjELMAkGA1UE BhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2 aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIwMDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhv cml6ZWQgdXNlIG9ubHkxJDAiBgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0w ODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh d3RlLCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9uMTgwNgYD VQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTEkMCIG A1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEczMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A MIIBCgKCAQEAsr8nLPvb2FvdeHsbnndmgcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2At P0LMqmsywCPLLEHd5N/8YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC +BsUa0Lfb1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS99irY 7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2SzhkGcuYMXDhpxwTW vGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUkOQIDAQABo0IwQDAPBgNVHRMBAf8E BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJ KoZIhvcNAQELBQADggEBABpA2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweK A3rD6z8KLFIWoCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7cKUGRIjxpp7sC 8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fMm7v/OeZWYdMKp8RcTGB7BXcm er/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZuMdRAGmI0Nj81Aa6sY6A= -----END CERTIFICATE----- GeoTrust Primary Certification Authority - G2 ============================================= -----BEGIN CERTIFICATE----- MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDELMAkGA1UEBhMC VVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChjKSAyMDA3IEdlb1RydXN0IElu Yy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBD ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1 OVowgZgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg MjAwNyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNVBAMTLUdl b1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjB2MBAGByqGSM49AgEG BSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcLSo17VDs6bl8VAsBQps8lL33KSLjHUGMc KiEIfJo22Av+0SbFWDEwKCXzXV2juLaltJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYD VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+ EVXVMAoGCCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGTqQ7m ndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBuczrD6ogRLQy7rQkgu2 npaqBA+K -----END CERTIFICATE----- VeriSign Universal Root Certification Authority =============================================== -----BEGIN CERTIFICATE----- MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCBvTELMAkGA1UE BhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBO ZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVk IHVzZSBvbmx5MTgwNgYDVQQDEy9WZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9u IEF1dGhvcml0eTAeFw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJV UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv cmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl IG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0 aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj 1mCOkdeQmIN65lgZOIzF9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGP MiJhgsWHH26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+HLL72 9fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN/BMReYTtXlT2NJ8I AfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPTrJ9VAMf2CGqUuV/c4DPxhGD5WycR tPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0G CCsGAQUFBwEMBGEwX6FdoFswWTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2O a8PPgGrUSBgsexkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4sAPmLGd75JR3 Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+seQxIcaBlVZaDrHC1LGmWazx Y8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTx P/jgdFcrGJ2BtMQo2pSXpXDrrB2+BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+P wGZsY6rp2aQW9IHRlRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4 mJO37M2CYfE45k+XmCpajQ== -----END CERTIFICATE----- VeriSign Class 3 Public Primary Certification Authority - G4 ============================================================ -----BEGIN CERTIFICATE----- MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjELMAkGA1UEBhMC VVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3 b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVz ZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmlj YXRpb24gQXV0aG9yaXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjEL MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBU cnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRo b3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5 IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8 Utpkmw4tXNherJI9/gHmGUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGz rl0Bp3vefLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUwAwEB /zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEw HzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVyaXNpZ24u Y29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMWkf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMD A2gAMGUCMGYhDBgmYFo4e1ZC4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIx AJw9SDkjOVgaFRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA== -----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----- Staat der Nederlanden Root CA - G2 ================================== -----BEGIN CERTIFICATE----- MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJOTDEeMBwGA1UE CgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFhdCBkZXIgTmVkZXJsYW5kZW4g Um9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oXDTIwMDMyNTExMDMxMFowWjELMAkGA1UEBhMC TkwxHjAcBgNVBAoMFVN0YWF0IGRlciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5l ZGVybGFuZGVuIFJvb3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMVZ 5291qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZqhQlEq0i6ABtQ8SpuOUfiUtn vWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU254DBtvPUZ5uW6M7XxgpT0GtJlvOj CwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvEpMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiil e7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp5Rbn3JBV3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCR OME4HYYEhLoaJXhena/MUGDWE4dS7WMfbWV9whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpI CT0ugpTNGmXZK4iambwYfp/ufWZ8Pr2UuIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy5V65 48r6f1CGPqI0GAwJaCgRHOThuVw+R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv6q012iDTiIJh8BIi trzQ1aTsr1SIJSQ8p22xcik/Plemf1WvbibG/ufMQFxRRIEKeN5KzlW/HdXZt1bv8Hb/C3m1r737 qWmRRpdogBQ2HbN/uymYNqUg+oJgYjOk7Na6B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMB AAGjgZcwgZQwDwYDVR0TAQH/BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcC ARYxaHR0cDovL3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNV HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqGSIb3DQEBCwUA A4ICAQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLySCZa59sCrI2AGeYwRTlHSeYAz +51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUXvQwj f/ST7ZwaUb7dRUG/kSS0H4zpX897IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sUOlWDuYaN kqbG9AclVMwWVxJKgnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fMdWVSSt7wsKfk CpYL+63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2tUKRXCnxLvJxxcypF URmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkmbEgeqmiSBeGCc1qb3Adb CG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvkN1trSt8sV4pAWja63XVECDdCcAz+3F4h oKOKwJCcaNpQ5kUQR3i2TtJlycM33+FCY7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoV IPVVYpbtbZNQvOSqeK3Zywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm 66+KAQ== -----END CERTIFICATE----- CA Disig ======== -----BEGIN CERTIFICATE----- MIIEDzCCAvegAwIBAgIBATANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQGEwJTSzETMBEGA1UEBxMK QnJhdGlzbGF2YTETMBEGA1UEChMKRGlzaWcgYS5zLjERMA8GA1UEAxMIQ0EgRGlzaWcwHhcNMDYw MzIyMDEzOTM0WhcNMTYwMzIyMDEzOTM0WjBKMQswCQYDVQQGEwJTSzETMBEGA1UEBxMKQnJhdGlz bGF2YTETMBEGA1UEChMKRGlzaWcgYS5zLjERMA8GA1UEAxMIQ0EgRGlzaWcwggEiMA0GCSqGSIb3 DQEBAQUAA4IBDwAwggEKAoIBAQCS9jHBfYj9mQGp2HvycXXxMcbzdWb6UShGhJd4NLxs/LxFWYgm GErENx+hSkS943EE9UQX4j/8SFhvXJ56CbpRNyIjZkMhsDxkovhqFQ4/61HhVKndBpnXmjxUizkD Pw/Fzsbrg3ICqB9x8y34dQjbYkzo+s7552oftms1grrijxaSfQUMbEYDXcDtab86wYqg6I7ZuUUo hwjstMoVvoLdtUSLLa2GDGhibYVW8qwUYzrG0ZmsNHhWS8+2rT+MitcE5eN4TPWGqvWP+j1scaMt ymfraHtuM6kMgiioTGohQBUgDCZbg8KpFhXAJIJdKxatymP2dACw30PEEGBWZ2NFAgMBAAGjgf8w gfwwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUjbJJaJ1yCCW5wCf1UJNWSEZx+Y8wDgYDVR0P AQH/BAQDAgEGMDYGA1UdEQQvMC2BE2Nhb3BlcmF0b3JAZGlzaWcuc2uGFmh0dHA6Ly93d3cuZGlz aWcuc2svY2EwZgYDVR0fBF8wXTAtoCugKYYnaHR0cDovL3d3dy5kaXNpZy5zay9jYS9jcmwvY2Ff ZGlzaWcuY3JsMCygKqAohiZodHRwOi8vY2EuZGlzaWcuc2svY2EvY3JsL2NhX2Rpc2lnLmNybDAa BgNVHSAEEzARMA8GDSuBHpGT5goAAAABAQEwDQYJKoZIhvcNAQEFBQADggEBAF00dGFMrzvY/59t WDYcPQuBDRIrRhCA/ec8J9B6yKm2fnQwM6M6int0wHl5QpNt/7EpFIKrIYwvF/k/Ji/1WcbvgAa3 mkkp7M5+cTxqEEHA9tOasnxakZzArFvITV734VP/Q3f8nktnbNfzg9Gg4H8l37iYC5oyOGwwoPP/ CBUz91BKez6jPiCp3C9WgArtQVCwyfTssuMmRAAOb54GvCKWU3BlxFAKRmukLyeBEicTXxChds6K ezfqwzlhA5WYOudsiCUI/HloDYd9Yvi0X/vF2Ey9WLw/Q1vUHgFNPGO+I++MzVpQuGhU+QqZMxEA 4Z7CRneC9VkGjCFMhwnN5ag= -----END CERTIFICATE----- Juur-SK ======= -----BEGIN CERTIFICATE----- MIIE5jCCA86gAwIBAgIEO45L/DANBgkqhkiG9w0BAQUFADBdMRgwFgYJKoZIhvcNAQkBFglwa2lA c2suZWUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKExlBUyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMRAw DgYDVQQDEwdKdXVyLVNLMB4XDTAxMDgzMDE0MjMwMVoXDTE2MDgyNjE0MjMwMVowXTEYMBYGCSqG SIb3DQEJARYJcGtpQHNrLmVlMQswCQYDVQQGEwJFRTEiMCAGA1UEChMZQVMgU2VydGlmaXRzZWVy aW1pc2tlc2t1czEQMA4GA1UEAxMHSnV1ci1TSzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC ggEBAIFxNj4zB9bjMI0TfncyRsvPGbJgMUaXhvSYRqTCZUXP00B841oiqBB4M8yIsdOBSvZiF3tf TQou0M+LI+5PAk676w7KvRhj6IAcjeEcjT3g/1tf6mTll+g/mX8MCgkzABpTpyHhOEvWgxutr2TC +Rx6jGZITWYfGAriPrsfB2WThbkasLnE+w0R9vXW+RvHLCu3GFH+4Hv2qEivbDtPL+/40UceJlfw UR0zlv/vWT3aTdEVNMfqPxZIe5EcgEMPPbgFPtGzlc3Yyg/CQ2fbt5PgIoIuvvVoKIO5wTtpeyDa Tpxt4brNj3pssAki14sL2xzVWiZbDcDq5WDQn/413z8CAwEAAaOCAawwggGoMA8GA1UdEwEB/wQF MAMBAf8wggEWBgNVHSAEggENMIIBCTCCAQUGCisGAQQBzh8BAQEwgfYwgdAGCCsGAQUFBwICMIHD HoHAAFMAZQBlACAAcwBlAHIAdABpAGYAaQBrAGEAYQB0ACAAbwBuACAAdgDkAGwAagBhAHMAdABh AHQAdQBkACAAQQBTAC0AaQBzACAAUwBlAHIAdABpAGYAaQB0AHMAZQBlAHIAaQBtAGkAcwBrAGUA cwBrAHUAcwAgAGEAbABhAG0ALQBTAEsAIABzAGUAcgB0AGkAZgBpAGsAYQBhAHQAaQBkAGUAIABr AGkAbgBuAGkAdABhAG0AaQBzAGUAawBzMCEGCCsGAQUFBwIBFhVodHRwOi8vd3d3LnNrLmVlL2Nw cy8wKwYDVR0fBCQwIjAgoB6gHIYaaHR0cDovL3d3dy5zay5lZS9qdXVyL2NybC8wHQYDVR0OBBYE FASqekej5ImvGs8KQKcYP2/v6X2+MB8GA1UdIwQYMBaAFASqekej5ImvGs8KQKcYP2/v6X2+MA4G A1UdDwEB/wQEAwIB5jANBgkqhkiG9w0BAQUFAAOCAQEAe8EYlFOiCfP+JmeaUOTDBS8rNXiRTHyo ERF5TElZrMj3hWVcRrs7EKACr81Ptcw2Kuxd/u+gkcm2k298gFTsxwhwDY77guwqYHhpNjbRxZyL abVAyJRld/JXIWY7zoVAtjNjGr95HvxcHdMdkxuLDF2FvZkwMhgJkVLpfKG6/2SSmuz+Ne6ML678 IIbsSt4beDI3poHSna9aEhbKmVv8b20OxaAehsmR0FyYgl9jDIpaq9iVpszLita/ZEuOyoqysOkh Mp6qqIWYNIE5ITuoOlIyPfZrN4YGWhWY3PARZv40ILcD9EEQfTmEeZZyY7aWAuVrua0ZTbvGRNs2 yyqcjg== -----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----- ACEDICOM Root ============= -----BEGIN CERTIFICATE----- MIIFtTCCA52gAwIBAgIIYY3HhjsBggUwDQYJKoZIhvcNAQEFBQAwRDEWMBQGA1UEAwwNQUNFRElD T00gUm9vdDEMMAoGA1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00xCzAJBgNVBAYTAkVTMB4XDTA4 MDQxODE2MjQyMloXDTI4MDQxMzE2MjQyMlowRDEWMBQGA1UEAwwNQUNFRElDT00gUm9vdDEMMAoG A1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00xCzAJBgNVBAYTAkVTMIICIjANBgkqhkiG9w0BAQEF AAOCAg8AMIICCgKCAgEA/5KV4WgGdrQsyFhIyv2AVClVYyT/kGWbEHV7w2rbYgIB8hiGtXxaOLHk WLn709gtn70yN78sFW2+tfQh0hOR2QetAQXW8713zl9CgQr5auODAKgrLlUTY4HKRxx7XBZXehuD YAQ6PmXDzQHe3qTWDLqO3tkE7hdWIpuPY/1NFgu3e3eM+SW10W2ZEi5PGrjm6gSSrj0RuVFCPYew MYWveVqc/udOXpJPQ/yrOq2lEiZmueIM15jO1FillUAKt0SdE3QrwqXrIhWYENiLxQSfHY9g5QYb m8+5eaA9oiM/Qj9r+hwDezCNzmzAv+YbX79nuIQZ1RXve8uQNjFiybwCq0Zfm/4aaJQ0PZCOrfbk HQl/Sog4P75n/TSW9R28MHTLOO7VbKvU/PQAtwBbhTIWdjPp2KOZnQUAqhbm84F9b32qhm2tFXTT xKJxqvQUfecyuB+81fFOvW8XAjnXDpVCOscAPukmYxHqC9FK/xidstd7LzrZlvvoHpKuE1XI2Sf2 3EgbsCTBheN3nZqk8wwRHQ3ItBTutYJXCb8gWH8vIiPYcMt5bMlL8qkqyPyHK9caUPgn6C9D4zq9 2Fdx/c6mUlv53U3t5fZvie27k5x2IXXwkkwp9y+cAS7+UEaeZAwUswdbxcJzbPEHXEUkFDWug/Fq TYl6+rPYLWbwNof1K1MCAwEAAaOBqjCBpzAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKaz 4SsrSbbXc6GqlPUB53NlTKxQMA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUprPhKytJttdzoaqU 9QHnc2VMrFAwRAYDVR0gBD0wOzA5BgRVHSAAMDEwLwYIKwYBBQUHAgEWI2h0dHA6Ly9hY2VkaWNv bS5lZGljb21ncm91cC5jb20vZG9jMA0GCSqGSIb3DQEBBQUAA4ICAQDOLAtSUWImfQwng4/F9tqg aHtPkl7qpHMyEVNEskTLnewPeUKzEKbHDZ3Ltvo/Onzqv4hTGzz3gvoFNTPhNahXwOf9jU8/kzJP eGYDdwdY6ZXIfj7QeQCM8htRM5u8lOk6e25SLTKeI6RF+7YuE7CLGLHdztUdp0J/Vb77W7tH1Pwk zQSulgUV1qzOMPPKC8W64iLgpq0i5ALudBF/TP94HTXa5gI06xgSYXcGCRZj6hitoocf8seACQl1 ThCojz2GuHURwCRiipZ7SkXp7FnFvmuD5uHorLUwHv4FB4D54SMNUI8FmP8sX+g7tq3PgbUhh8oI KiMnMCArz+2UW6yyetLHKKGKC5tNSixthT8Jcjxn4tncB7rrZXtaAWPWkFtPF2Y9fwsZo5NjEFIq nxQWWOLcpfShFosOkYuByptZ+thrkQdlVV9SH686+5DdaaVbnG0OLLb6zqylfDJKZ0DcMDQj3dcE I2bw/FWAp/tmGYI1Z2JwOV5vx+qQQEQIHriy1tvuWacNGHk0vFQYXlPKNFHtRQrmjseCNj6nOGOp MCwXEGCSn1WHElkQwg9naRHMTh5+Spqtr0CodaxWkHS4oJyleW/c6RrIaQXpuvoDs3zk4E7Czp3o tkYNbn5XOmeUwssfnHdKZ05phkOTOPu220+DkdRgfks+KzgHVZhepA== -----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----- Certinomis - Autorité Racine ============================= -----BEGIN CERTIFICATE----- MIIFnDCCA4SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJGUjETMBEGA1UEChMK Q2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxJjAkBgNVBAMMHUNlcnRpbm9taXMg LSBBdXRvcml0w6kgUmFjaW5lMB4XDTA4MDkxNzA4Mjg1OVoXDTI4MDkxNzA4Mjg1OVowYzELMAkG A1UEBhMCRlIxEzARBgNVBAoTCkNlcnRpbm9taXMxFzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMSYw JAYDVQQDDB1DZXJ0aW5vbWlzIC0gQXV0b3JpdMOpIFJhY2luZTCCAiIwDQYJKoZIhvcNAQEBBQAD ggIPADCCAgoCggIBAJ2Fn4bT46/HsmtuM+Cet0I0VZ35gb5j2CN2DpdUzZlMGvE5x4jYF1AMnmHa wE5V3udauHpOd4cN5bjr+p5eex7Ezyh0x5P1FMYiKAT5kcOrJ3NqDi5N8y4oH3DfVS9O7cdxbwly Lu3VMpfQ8Vh30WC8Tl7bmoT2R2FFK/ZQpn9qcSdIhDWerP5pqZ56XjUl+rSnSTV3lqc2W+HN3yNw 2F1MpQiD8aYkOBOo7C+ooWfHpi2GR+6K/OybDnT0K0kCe5B1jPyZOQE51kqJ5Z52qz6WKDgmi92N jMD2AR5vpTESOH2VwnHu7XSu5DaiQ3XV8QCb4uTXzEIDS3h65X27uK4uIJPT5GHfceF2Z5c/tt9q c1pkIuVC28+BA5PY9OMQ4HL2AHCs8MF6DwV/zzRpRbWT5BnbUhYjBYkOjUjkJW+zeL9i9Qf6lSTC lrLooyPCXQP8w9PlfMl1I9f09bze5N/NgL+RiH2nE7Q5uiy6vdFrzPOlKO1Enn1So2+WLhl+HPNb xxaOu2B9d2ZHVIIAEWBsMsGoOBvrbpgT1u449fCfDu/+MYHB0iSVL1N6aaLwD4ZFjliCK0wi1F6g 530mJ0jfJUaNSih8hp75mxpZuWW/Bd22Ql095gBIgl4g9xGC3srYn+Y3RyYe63j3YcNBZFgCQfna 4NH4+ej9Uji29YnfAgMBAAGjWzBZMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G A1UdDgQWBBQNjLZh2kS40RR9w759XkjwzspqsDAXBgNVHSAEEDAOMAwGCiqBegFWAgIAAQEwDQYJ KoZIhvcNAQEFBQADggIBACQ+YAZ+He86PtvqrxyaLAEL9MW12Ukx9F1BjYkMTv9sov3/4gbIOZ/x WqndIlgVqIrTseYyCYIDbNc/CMf4uboAbbnW/FIyXaR/pDGUu7ZMOH8oMDX/nyNTt7buFHAAQCva R6s0fl6nVjBhK4tDrP22iCj1a7Y+YEq6QpA0Z43q619FVDsXrIvkxmUP7tCMXWY5zjKn2BCXwH40 nJ+U8/aGH88bc62UeYdocMMzpXDn2NU4lG9jeeu/Cg4I58UvD0KgKxRA/yHgBcUn4YQRE7rWhh1B CxMjidPJC+iKunqjo3M3NYB9Ergzd0A4wPpeMNLytqOx1qKVl4GbUu1pTP+A5FPbVFsDbVRfsbjv JL1vnxHDx2TCDyhihWZeGnuyt++uNckZM6i4J9szVb9o4XVIRFb7zdNIu0eJOqxp9YDG5ERQL1TE qkPFMTFYvZbF6nVsmnWxTfj3l/+WFvKXTej28xH5On2KOG4Ey+HTRRWqpdEdnV1j6CTmNhTih60b WfVEm/vXd3wfAXBioSAaosUaKPQhA+4u2cGA6rnZgtZbdsLLO7XSAPCjDuGtbkD326C00EauFddE wk01+dIL8hf2rGbVJLJP0RyZwG71fet0BLj5TXcJ17TPBzAJ8bgAVtkXFhYKK4bfjwEZGuW7gmP/ vgt2Fl43N+bYdJeimUV5 -----END CERTIFICATE----- Root CA Generalitat Valenciana ============================== -----BEGIN CERTIFICATE----- MIIGizCCBXOgAwIBAgIEO0XlaDANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJFUzEfMB0GA1UE ChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJR1ZBMScwJQYDVQQDEx5Sb290 IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwHhcNMDEwNzA2MTYyMjQ3WhcNMjEwNzAxMTUyMjQ3 WjBoMQswCQYDVQQGEwJFUzEfMB0GA1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UE CxMGUEtJR1ZBMScwJQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwggEiMA0G CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGKqtXETcvIorKA3Qdyu0togu8M1JAJke+WmmmO3I2 F0zo37i7L3bhQEZ0ZQKQUgi0/6iMweDHiVYQOTPvaLRfX9ptI6GJXiKjSgbwJ/BXufjpTjJ3Cj9B ZPPrZe52/lSqfR0grvPXdMIKX/UIKFIIzFVd0g/bmoGlu6GzwZTNVOAydTGRGmKy3nXiz0+J2ZGQ D0EbtFpKd71ng+CT516nDOeB0/RSrFOyA8dEJvt55cs0YFAQexvba9dHq198aMpunUEDEO5rmXte JajCq+TA81yc477OMUxkHl6AovWDfgzWyoxVjr7gvkkHD6MkQXpYHYTqWBLI4bft75PelAgxAgMB AAGjggM7MIIDNzAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUHMAGGFmh0dHA6Ly9vY3NwLnBraS5n dmEuZXMwEgYDVR0TAQH/BAgwBgEB/wIBAjCCAjQGA1UdIASCAiswggInMIICIwYKKwYBBAG/VQIB ADCCAhMwggHoBggrBgEFBQcCAjCCAdoeggHWAEEAdQB0AG8AcgBpAGQAYQBkACAAZABlACAAQwBl AHIAdABpAGYAaQBjAGEAYwBpAPMAbgAgAFIAYQDtAHoAIABkAGUAIABsAGEAIABHAGUAbgBlAHIA YQBsAGkAdABhAHQAIABWAGEAbABlAG4AYwBpAGEAbgBhAC4ADQAKAEwAYQAgAEQAZQBjAGwAYQBy AGEAYwBpAPMAbgAgAGQAZQAgAFAAcgDhAGMAdABpAGMAYQBzACAAZABlACAAQwBlAHIAdABpAGYA aQBjAGEAYwBpAPMAbgAgAHEAdQBlACAAcgBpAGcAZQAgAGUAbAAgAGYAdQBuAGMAaQBvAG4AYQBt AGkAZQBuAHQAbwAgAGQAZQAgAGwAYQAgAHAAcgBlAHMAZQBuAHQAZQAgAEEAdQB0AG8AcgBpAGQA YQBkACAAZABlACAAQwBlAHIAdABpAGYAaQBjAGEAYwBpAPMAbgAgAHMAZQAgAGUAbgBjAHUAZQBu AHQAcgBhACAAZQBuACAAbABhACAAZABpAHIAZQBjAGMAaQDzAG4AIAB3AGUAYgAgAGgAdAB0AHAA OgAvAC8AdwB3AHcALgBwAGsAaQAuAGcAdgBhAC4AZQBzAC8AYwBwAHMwJQYIKwYBBQUHAgEWGWh0 dHA6Ly93d3cucGtpLmd2YS5lcy9jcHMwHQYDVR0OBBYEFHs100DSHHgZZu90ECjcPk+yeAT8MIGV BgNVHSMEgY0wgYqAFHs100DSHHgZZu90ECjcPk+yeAT8oWykajBoMQswCQYDVQQGEwJFUzEfMB0G A1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJR1ZBMScwJQYDVQQDEx5S b290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmGCBDtF5WgwDQYJKoZIhvcNAQEFBQADggEBACRh TvW1yEICKrNcda3FbcrnlD+laJWIwVTAEGmiEi8YPyVQqHxK6sYJ2fR1xkDar1CdPaUWu20xxsdz Ckj+IHLtb8zog2EWRpABlUt9jppSCS/2bxzkoXHPjCpaF3ODR00PNvsETUlR4hTJZGH71BTg9J63 NI8KJr2XXPR5OkowGcytT6CYirQxlyric21+eLj4iIlPsSKRZEv1UN4D2+XFducTZnV+ZfsBn5OH iJ35Rld8TWCvmHMTI6QgkYH60GFmuH3Rr9ZvHmw96RH9qfmCIoaZM3Fa6hlXPZHNqcCjbgcTpsnt +GijnsNacgmHKNHEc8RzGF9QdRYxn7fofMM= -----END CERTIFICATE----- A-Trust-nQual-03 ================ -----BEGIN CERTIFICATE----- MIIDzzCCAregAwIBAgIDAWweMA0GCSqGSIb3DQEBBQUAMIGNMQswCQYDVQQGEwJBVDFIMEYGA1UE Cgw/QS1UcnVzdCBHZXMuIGYuIFNpY2hlcmhlaXRzc3lzdGVtZSBpbSBlbGVrdHIuIERhdGVudmVy a2VociBHbWJIMRkwFwYDVQQLDBBBLVRydXN0LW5RdWFsLTAzMRkwFwYDVQQDDBBBLVRydXN0LW5R dWFsLTAzMB4XDTA1MDgxNzIyMDAwMFoXDTE1MDgxNzIyMDAwMFowgY0xCzAJBgNVBAYTAkFUMUgw RgYDVQQKDD9BLVRydXN0IEdlcy4gZi4gU2ljaGVyaGVpdHNzeXN0ZW1lIGltIGVsZWt0ci4gRGF0 ZW52ZXJrZWhyIEdtYkgxGTAXBgNVBAsMEEEtVHJ1c3QtblF1YWwtMDMxGTAXBgNVBAMMEEEtVHJ1 c3QtblF1YWwtMDMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtPWFuA/OQO8BBC4SA zewqo51ru27CQoT3URThoKgtUaNR8t4j8DRE/5TrzAUjlUC5B3ilJfYKvUWG6Nm9wASOhURh73+n yfrBJcyFLGM/BWBzSQXgYHiVEEvc+RFZznF/QJuKqiTfC0Li21a8StKlDJu3Qz7dg9MmEALP6iPE SU7l0+m0iKsMrmKS1GWH2WrX9IWf5DMiJaXlyDO6w8dB3F/GaswADm0yqLaHNgBid5seHzTLkDx4 iHQF63n1k3Flyp3HaxgtPVxO59X4PzF9j4fsCiIvI+n+u33J4PTs63zEsMMtYrWacdaxaujs2e3V cuy+VwHOBVWf3tFgiBCzAgMBAAGjNjA0MA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0OBAoECERqlWdV eRFPMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAVdRU0VlIXLOThaq/Yy/kgM40 ozRiPvbY7meIMQQDbwvUB/tOdQ/TLtPAF8fGKOwGDREkDg6lXb+MshOWcdzUzg4NCmgybLlBMRmr sQd7TZjTXLDR8KdCoLXEjq/+8T/0709GAHbrAvv5ndJAlseIOrifEXnzgGWovR/TeIGgUUw3tKZd JXDRZslo+S4RFGjxVJgIrCaSD96JntT6s3kr0qN51OyLrIdTaEJMUVF0HhsnLuP1Hyl0Te2v9+GS mYHovjrHF1D2t8b8m7CKa9aIA5GPBnc6hQLdmNVDeD/GMBWsm2vLV7eJUYs66MmEDNuxUCAKGkq6 ahq97BvIxYSazQ== -----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----- Trustis FPS Root CA =================== -----BEGIN CERTIFICATE----- MIIDZzCCAk+gAwIBAgIQGx+ttiD5JNM2a/fH8YygWTANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQG EwJHQjEYMBYGA1UEChMPVHJ1c3RpcyBMaW1pdGVkMRwwGgYDVQQLExNUcnVzdGlzIEZQUyBSb290 IENBMB4XDTAzMTIyMzEyMTQwNloXDTI0MDEyMTExMzY1NFowRTELMAkGA1UEBhMCR0IxGDAWBgNV BAoTD1RydXN0aXMgTGltaXRlZDEcMBoGA1UECxMTVHJ1c3RpcyBGUFMgUm9vdCBDQTCCASIwDQYJ KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMVQe547NdDfxIzNjpvto8A2mfRC6qc+gIMPpqdZh8mQ RUN+AOqGeSoDvT03mYlmt+WKVoaTnGhLaASMk5MCPjDSNzoiYYkchU59j9WvezX2fihHiTHcDnlk H5nSW7r+f2C/revnPDgpai/lkQtV/+xvWNUtyd5MZnGPDNcE2gfmHhjjvSkCqPoc4Vu5g6hBSLwa cY3nYuUtsuvffM/bq1rKMfFMIvMFE/eC+XN5DL7XSxzA0RU8k0Fk0ea+IxciAIleH2ulrG6nS4zt o3Lmr2NNL4XSFDWaLk6M6jKYKIahkQlBOrTh4/L68MkKokHdqeMDx4gVOxzUGpTXn2RZEm0CAwEA AaNTMFEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS6+nEleYtXQSUhhgtx67JkDoshZzAd BgNVHQ4EFgQUuvpxJXmLV0ElIYYLceuyZA6LIWcwDQYJKoZIhvcNAQEFBQADggEBAH5Y//01GX2c GE+esCu8jowU/yyg2kdbw++BLa8F6nRIW/M+TgfHbcWzk88iNVy2P3UnXwmWzaD+vkAMXBJV+JOC yinpXj9WV4s4NvdFGkwozZ5BuO1WTISkQMi4sKUraXAEasP41BIy+Q7DsdwyhEQsb8tGD+pmQQ9P 8Vilpg0ND2HepZ5dfWWhPBfnqFVO76DH7cZEf1T1o+CP8HxVIo8ptoGj4W1OLBuAZ+ytIJ8MYmHV l/9D7S3B2l0pKoU/rGXuhg8FjZBf3+6f9L/uHfuY5H+QK4R4EA5sSVPvFVtlRkpdr7r7OnIdzfYl iB6XzCGcKQENZetX2fNXlrtIzYE= -----END CERTIFICATE----- StartCom Certification Authority ================================ -----BEGIN CERTIFICATE----- MIIHhzCCBW+gAwIBAgIBLTANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMN U3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmlu ZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0 NjM3WhcNMzYwOTE3MTk0NjM2WjB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRk LjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMg U3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw ggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZkpMyONvg45iPwbm2xPN1y o4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rfOQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/ Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/CJi/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/d eMotHweXMAEtcnn6RtYTKqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt 2PZE4XNiHzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMMAv+Z 6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w+2OqqGwaVLRcJXrJ osmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/ untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVc UjyJthkqcwEKDwOzEmDyei+B26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT 37uMdBNSSwIDAQABo4ICEDCCAgwwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD VR0OBBYEFE4L7xqkQFulF2mHMMo0aEPQQa7yMB8GA1UdIwQYMBaAFE4L7xqkQFulF2mHMMo0aEPQ Qa7yMIIBWgYDVR0gBIIBUTCCAU0wggFJBgsrBgEEAYG1NwEBATCCATgwLgYIKwYBBQUHAgEWImh0 dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwNAYIKwYBBQUHAgEWKGh0dHA6Ly93d3cu c3RhcnRzc2wuY29tL2ludGVybWVkaWF0ZS5wZGYwgc8GCCsGAQUFBwICMIHCMCcWIFN0YXJ0IENv bW1lcmNpYWwgKFN0YXJ0Q29tKSBMdGQuMAMCAQEagZZMaW1pdGVkIExpYWJpbGl0eSwgcmVhZCB0 aGUgc2VjdGlvbiAqTGVnYWwgTGltaXRhdGlvbnMqIG9mIHRoZSBTdGFydENvbSBDZXJ0aWZpY2F0 aW9uIEF1dGhvcml0eSBQb2xpY3kgYXZhaWxhYmxlIGF0IGh0dHA6Ly93d3cuc3RhcnRzc2wuY29t L3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBG cmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAgEAjo/n3JR5 fPGFf59Jb2vKXfuM/gTFwWLRfUKKvFO3lANmMD+x5wqnUCBVJX92ehQN6wQOQOY+2IirByeDqXWm N3PH/UvSTa0XQMhGvjt/UfzDtgUx3M2FIk5xt/JxXrAaxrqTi3iSSoX4eA+D/i+tLPfkpLst0OcN Org+zvZ49q5HJMqjNTbOx8aHmNrs++myziebiMMEofYLWWivydsQD032ZGNcpRJvkrKTlMeIFw6T tn5ii5B/q06f/ON1FE8qMt9bDeD1e5MNq6HPh+GlBEXoPBKlCcWw0bdT82AUuoVpaiF8H3VhFyAX e2w7QSlc4axa0c2Mm+tgHRns9+Ww2vl5GKVFP0lDV9LdJNUso/2RjSe15esUBppMeyG7Oq0wBhjA 2MFrLH9ZXF2RsXAiV+uKa0hK1Q8p7MZAwC+ITGgBF3f0JBlPvfrhsiAhS90a2Cl9qrjeVOwhVYBs HvUwyKMQ5bLmKhQxw4UtjJixhlpPiVktucf3HMiKf8CdBUrmQk9io20ppB+Fq9vlgcitKj1MXVuE JnHEhV5xJMqlG2zYYdMa4FTbzrqpMrUi9nNBCV24F10OD5mQ1kfabwo6YigUZ4LZ8dCAWZvLMdib D4x3TrVoivJs9iQOLWxwxXPR3hTQcY+203sC9uO41Alua551hDnmfyWl8kgAwKQB2j8= -----END CERTIFICATE----- StartCom Certification Authority G2 =================================== -----BEGIN CERTIFICATE----- MIIFYzCCA0ugAwIBAgIBOzANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJJTDEWMBQGA1UEChMN U3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg RzIwHhcNMTAwMTAxMDEwMDAxWhcNMzkxMjMxMjM1OTAxWjBTMQswCQYDVQQGEwJJTDEWMBQGA1UE ChMNU3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3Jp dHkgRzIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2iTZbB7cgNr2Cu+EWIAOVeq8O o1XJJZlKxdBWQYeQTSFgpBSHO839sj60ZwNq7eEPS8CRhXBF4EKe3ikj1AENoBB5uNsDvfOpL9HG 4A/LnooUCri99lZi8cVytjIl2bLzvWXFDSxu1ZJvGIsAQRSCb0AgJnooD/Uefyf3lLE3PbfHkffi Aez9lInhzG7TNtYKGXmu1zSCZf98Qru23QumNK9LYP5/Q0kGi4xDuFby2X8hQxfqp0iVAXV16iul Q5XqFYSdCI0mblWbq9zSOdIxHWDirMxWRST1HFSr7obdljKF+ExP6JV2tgXdNiNnvP8V4so75qbs O+wmETRIjfaAKxojAuuKHDp2KntWFhxyKrOq42ClAJ8Em+JvHhRYW6Vsi1g8w7pOOlz34ZYrPu8H vKTlXcxNnw3h3Kq74W4a7I/htkxNeXJdFzULHdfBR9qWJODQcqhaX2YtENwvKhOuJv4KHBnM0D4L nMgJLvlblnpHnOl68wVQdJVznjAJ85eCXuaPOQgeWeU1FEIT/wCc976qUM/iUUjXuG+v+E5+M5iS FGI6dWPPe/regjupuznixL0sAA7IF6wT700ljtizkC+p2il9Ha90OrInwMEePnWjFqmveiJdnxMa z6eg6+OGCtP95paV1yPIN93EfKo2rJgaErHgTuixO/XWb/Ew1wIDAQABo0IwQDAPBgNVHRMBAf8E BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUS8W0QGutHLOlHGVuRjaJhwUMDrYwDQYJ KoZIhvcNAQELBQADggIBAHNXPyzVlTJ+N9uWkusZXn5T50HsEbZH77Xe7XRcxfGOSeD8bpkTzZ+K 2s06Ctg6Wgk/XzTQLwPSZh0avZyQN8gMjgdalEVGKua+etqhqaRpEpKwfTbURIfXUfEpY9Z1zRbk J4kd+MIySP3bmdCPX1R0zKxnNBFi2QwKN4fRoxdIjtIXHfbX/dtl6/2o1PXWT6RbdejF0mCy2wl+ JYt7ulKSnj7oxXehPOBKc2thz4bcQ///If4jXSRK9dNtD2IEBVeC2m6kMyV5Sy5UGYvMLD0w6dEG /+gyRr61M3Z3qAFdlsHB1b6uJcDJHgoJIIihDsnzb02CVAAgp9KP5DlUFy6NHrgbuxu9mk47EDTc nIhT76IxW1hPkWLIwpqazRVdOKnWvvgTtZ8SafJQYqz7Fzf07rh1Z2AQ+4NQ+US1dZxAF7L+/Xld blhYXzD8AK6vM8EOTmy6p6ahfzLbOOCxchcKK5HsamMm7YnUeMx0HgX4a/6ManY5Ka5lIxKVCCIc l85bBu4M4ru8H0ST9tg4RQUh7eStqxK2A6RCLi3ECToDZ2mEmuFZkIoohdVddLHRDiBYmxOlsGOm 7XtH/UVVMKTumtTm4ofvmMkyghEpIrwACjFeLQ/Ajulrso8uBtjRkcfGEvRM/TAXw8HaOFvjqerm obp573PYtlNXLfbQ4ddI -----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----- EE Certification Centre Root CA =============================== -----BEGIN CERTIFICATE----- MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQG EwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1czEoMCYGA1UEAwwfRUUgQ2Vy dGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYGCSqGSIb3DQEJARYJcGtpQHNrLmVlMCIYDzIw MTAxMDMwMTAxMDMwWhgPMjAzMDEyMTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlB UyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRy ZSBSb290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEiMA0GCSqGSIb3DQEBAQUAA4IB DwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLqI9iroWUyeuuOF0+W2Ap7kaJjbMeM TC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvObntl8jixwKIy72KyaOBhU8E2lf/slLo2 rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIwWFv9zajmofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw 93X2PaRka9ZP585ArQ/dMtO8ihJTmMmJ+xAdTX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtN P2MbRMNE1CV2yreN1x5KZmTNXMWcg+HCCIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYDVR0T AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/zQas8fElyalL1BSZ MEUGA1UdJQQ+MDwGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYBBQUHAwMGCCsGAQUFBwMEBggrBgEF BQcDCAYIKwYBBQUHAwkwDQYJKoZIhvcNAQEFBQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+Rj xY6hUFaTlrg4wCQiZrxTFGGVv9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqM lIpPnTX/dqQGE5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5u uSlNDUmJEYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIWiAYLtqZLICjU 3j2LrTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/vGVCJYMzpJJUPwssd8m92kMfM dcGWxZ0= -----END CERTIFICATE----- TURKTRUST Certificate Services Provider Root 2007 ================================================= -----BEGIN CERTIFICATE----- MIIEPTCCAyWgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvzE/MD0GA1UEAww2VMOcUktUUlVTVCBF bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEP MA0GA1UEBwwGQW5rYXJhMV4wXAYDVQQKDFVUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUg QmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgQXJhbMSxayAyMDA3MB4X DTA3MTIyNTE4MzcxOVoXDTE3MTIyMjE4MzcxOVowgb8xPzA9BgNVBAMMNlTDnFJLVFJVU1QgRWxl a3Ryb25payBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTELMAkGA1UEBhMCVFIxDzAN BgNVBAcMBkFua2FyYTFeMFwGA1UECgxVVMOcUktUUlVTVCBCaWxnaSDEsGxldGnFn2ltIHZlIEJp bGnFn2ltIEfDvHZlbmxpxJ9pIEhpem1ldGxlcmkgQS7Fni4gKGMpIEFyYWzEsWsgMjAwNzCCASIw DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKu3PgqMyKVYFeaK7yc9SrToJdPNM8Ig3BnuiD9N YvDdE3ePYakqtdTyuTFYKTsvP2qcb3N2Je40IIDu6rfwxArNK4aUyeNgsURSsloptJGXg9i3phQv KUmi8wUG+7RP2qFsmmaf8EMJyupyj+sA1zU511YXRxcw9L6/P8JorzZAwan0qafoEGsIiveGHtya KhUG9qPw9ODHFNRRf8+0222vR5YXm3dx2KdxnSQM9pQ/hTEST7ruToK4uT6PIzdezKKqdfcYbwnT rqdUKDT74eA7YH2gvnmJhsifLfkKS8RQouf9eRbHegsYz85M733WB2+Y8a+xwXrXgTW4qhe04MsC AwEAAaNCMEAwHQYDVR0OBBYEFCnFkKslrxHkYb+j/4hhkeYO/pyBMA4GA1UdDwEB/wQEAwIBBjAP BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAQDdr4Ouwo0RSVgrESLFF6QSU2TJ/s Px+EnWVUXKgWAkD6bho3hO9ynYYKVZ1WKKxmLNA6VpM0ByWtCLCPyA8JWcqdmBzlVPi5RX9ql2+I aE1KBiY3iAIOtsbWcpnOa3faYjGkVh+uX4132l32iPwa2Z61gfAyuOOI0JzzaqC5mxRZNTZPz/OO Xl0XrRWV2N2y1RVuAE6zS89mlOTgzbUF2mNXi+WzqtvALhyQRNsaXRik7r4EW5nVcV9VZWRi1aKb BFmGyGJ353yCRWo9F7/snXUMrqNvWtMvmDb08PUZqxFdyKbjKlhqQgnDvZImZjINXQhVdP+MmNAK poRq0Tl9 -----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----- PSCProcert ========== -----BEGIN CERTIFICATE----- MIIJhjCCB26gAwIBAgIBCzANBgkqhkiG9w0BAQsFADCCAR4xPjA8BgNVBAMTNUF1dG9yaWRhZCBk ZSBDZXJ0aWZpY2FjaW9uIFJhaXogZGVsIEVzdGFkbyBWZW5lem9sYW5vMQswCQYDVQQGEwJWRTEQ MA4GA1UEBxMHQ2FyYWNhczEZMBcGA1UECBMQRGlzdHJpdG8gQ2FwaXRhbDE2MDQGA1UEChMtU2lz dGVtYSBOYWNpb25hbCBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9uaWNhMUMwQQYDVQQLEzpTdXBl cmludGVuZGVuY2lhIGRlIFNlcnZpY2lvcyBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9uaWNhMSUw IwYJKoZIhvcNAQkBFhZhY3JhaXpAc3VzY2VydGUuZ29iLnZlMB4XDTEwMTIyODE2NTEwMFoXDTIw MTIyNTIzNTk1OVowgdExJjAkBgkqhkiG9w0BCQEWF2NvbnRhY3RvQHByb2NlcnQubmV0LnZlMQ8w DQYDVQQHEwZDaGFjYW8xEDAOBgNVBAgTB01pcmFuZGExKjAoBgNVBAsTIVByb3ZlZWRvciBkZSBD ZXJ0aWZpY2Fkb3MgUFJPQ0VSVDE2MDQGA1UEChMtU2lzdGVtYSBOYWNpb25hbCBkZSBDZXJ0aWZp Y2FjaW9uIEVsZWN0cm9uaWNhMQswCQYDVQQGEwJWRTETMBEGA1UEAxMKUFNDUHJvY2VydDCCAiIw DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANW39KOUM6FGqVVhSQ2oh3NekS1wwQYalNo97BVC wfWMrmoX8Yqt/ICV6oNEolt6Vc5Pp6XVurgfoCfAUFM+jbnADrgV3NZs+J74BCXfgI8Qhd19L3uA 3VcAZCP4bsm+lU/hdezgfl6VzbHvvnpC2Mks0+saGiKLt38GieU89RLAu9MLmV+QfI4tL3czkkoh RqipCKzx9hEC2ZUWno0vluYC3XXCFCpa1sl9JcLB/KpnheLsvtF8PPqv1W7/U0HU9TI4seJfxPmO EO8GqQKJ/+MMbpfg353bIdD0PghpbNjU5Db4g7ayNo+c7zo3Fn2/omnXO1ty0K+qP1xmk6wKImG2 0qCZyFSTXai20b1dCl53lKItwIKOvMoDKjSuc/HUtQy9vmebVOvh+qBa7Dh+PsHMosdEMXXqP+UH 0quhJZb25uSgXTcYOWEAM11G1ADEtMo88aKjPvM6/2kwLkDd9p+cJsmWN63nOaK/6mnbVSKVUyqU td+tFjiBdWbjxywbk5yqjKPK2Ww8F22c3HxT4CAnQzb5EuE8XL1mv6JpIzi4mWCZDlZTOpx+FIyw Bm/xhnaQr/2v/pDGj59/i5IjnOcVdo/Vi5QTcmn7K2FjiO/mpF7moxdqWEfLcU8UC17IAggmosvp r2uKGcfLFFb14dq12fy/czja+eevbqQ34gcnAgMBAAGjggMXMIIDEzASBgNVHRMBAf8ECDAGAQH/ AgEBMDcGA1UdEgQwMC6CD3N1c2NlcnRlLmdvYi52ZaAbBgVghl4CAqASDBBSSUYtRy0yMDAwNDAz Ni0wMB0GA1UdDgQWBBRBDxk4qpl/Qguk1yeYVKIXTC1RVDCCAVAGA1UdIwSCAUcwggFDgBStuyId xuDSAaj9dlBSk+2YwU2u06GCASakggEiMIIBHjE+MDwGA1UEAxM1QXV0b3JpZGFkIGRlIENlcnRp ZmljYWNpb24gUmFpeiBkZWwgRXN0YWRvIFZlbmV6b2xhbm8xCzAJBgNVBAYTAlZFMRAwDgYDVQQH EwdDYXJhY2FzMRkwFwYDVQQIExBEaXN0cml0byBDYXBpdGFsMTYwNAYDVQQKEy1TaXN0ZW1hIE5h Y2lvbmFsIGRlIENlcnRpZmljYWNpb24gRWxlY3Ryb25pY2ExQzBBBgNVBAsTOlN1cGVyaW50ZW5k ZW5jaWEgZGUgU2VydmljaW9zIGRlIENlcnRpZmljYWNpb24gRWxlY3Ryb25pY2ExJTAjBgkqhkiG 9w0BCQEWFmFjcmFpekBzdXNjZXJ0ZS5nb2IudmWCAQowDgYDVR0PAQH/BAQDAgEGME0GA1UdEQRG MESCDnByb2NlcnQubmV0LnZloBUGBWCGXgIBoAwMClBTQy0wMDAwMDKgGwYFYIZeAgKgEgwQUklG LUotMzE2MzUzNzMtNzB2BgNVHR8EbzBtMEagRKBChkBodHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52 ZS9sY3IvQ0VSVElGSUNBRE8tUkFJWi1TSEEzODRDUkxERVIuY3JsMCOgIaAfhh1sZGFwOi8vYWNy YWl6LnN1c2NlcnRlLmdvYi52ZTA3BggrBgEFBQcBAQQrMCkwJwYIKwYBBQUHMAGGG2h0dHA6Ly9v Y3NwLnN1c2NlcnRlLmdvYi52ZTBBBgNVHSAEOjA4MDYGBmCGXgMBAjAsMCoGCCsGAQUFBwIBFh5o dHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52ZS9kcGMwDQYJKoZIhvcNAQELBQADggIBACtZ6yKZu4Sq T96QxtGGcSOeSwORR3C7wJJg7ODU523G0+1ng3dS1fLld6c2suNUvtm7CpsR72H0xpkzmfWvADmN g7+mvTV+LFwxNG9s2/NkAZiqlCxB3RWGymspThbASfzXg0gTB1GEMVKIu4YXx2sviiCtxQuPcD4q uxtxj7mkoP3YldmvWb8lK5jpY5MvYB7Eqvh39YtsL+1+LrVPQA3uvFd359m21D+VJzog1eWuq2w1 n8GhHVnchIHuTQfiSLaeS5UtQbHh6N5+LwUeaO6/u5BlOsju6rEYNxxik6SgMexxbJHmpHmJWhSn FFAFTKQAVzAswbVhltw+HoSvOULP5dAssSS830DD7X9jSr3hTxJkhpXzsOfIt+FTvZLm8wyWuevo 5pLtp4EJFAv8lXrPj9Y0TzYS3F7RNHXGRoAvlQSMx4bEqCaJqD8Zm4G7UaRKhqsLEQ+xrmNTbSjq 3TNWOByyrYDT13K9mmyZY+gAu0F2BbdbmRiKw7gSXFbPVgx96OLP7bx0R/vu0xdOIk9W/1DzLuY5 poLWccret9W6aAjtmcz9opLLabid+Qqkpj5PkygqYWwHJgD/ll9ohri4zspV4KuxPX+Y1zMOWj3Y eMLEYC/HYvBhkdI4sPaeVdtAgAUSM84dkpvRabP/v/GSCmE1P93+hvS84Bpxs2Km -----END CERTIFICATE----- China Internet Network Information Center EV Certificates Root ============================================================== -----BEGIN CERTIFICATE----- MIID9zCCAt+gAwIBAgIESJ8AATANBgkqhkiG9w0BAQUFADCBijELMAkGA1UEBhMCQ04xMjAwBgNV BAoMKUNoaW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24gQ2VudGVyMUcwRQYDVQQDDD5D aGluYSBJbnRlcm5ldCBOZXR3b3JrIEluZm9ybWF0aW9uIENlbnRlciBFViBDZXJ0aWZpY2F0ZXMg Um9vdDAeFw0xMDA4MzEwNzExMjVaFw0zMDA4MzEwNzExMjVaMIGKMQswCQYDVQQGEwJDTjEyMDAG A1UECgwpQ2hpbmEgSW50ZXJuZXQgTmV0d29yayBJbmZvcm1hdGlvbiBDZW50ZXIxRzBFBgNVBAMM PkNoaW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24gQ2VudGVyIEVWIENlcnRpZmljYXRl cyBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm35z7r07eKpkQ0H1UN+U8i6y jUqORlTSIRLIOTJCBumD1Z9S7eVnAztUwYyZmczpwA//DdmEEbK40ctb3B75aDFk4Zv6dOtouSCV 98YPjUesWgbdYavi7NifFy2cyjw1l1VxzUOFsUcW9SxTgHbP0wBkvUCZ3czY28Sf1hNfQYOL+Q2H klY0bBoQCxfVWhyXWIQ8hBouXJE0bhlffxdpxWXvayHG1VA6v2G5BY3vbzQ6sm8UY78WO5upKv23 KzhmBsUs4qpnHkWnjQRmQvaPK++IIGmPMowUc9orhpFjIpryp9vOiYurXccUwVswah+xt54ugQEC 7c+WXmPbqOY4twIDAQABo2MwYTAfBgNVHSMEGDAWgBR8cks5x8DbYqVPm6oYNJKiyoOCWTAPBgNV HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUfHJLOcfA22KlT5uqGDSSosqD glkwDQYJKoZIhvcNAQEFBQADggEBACrDx0M3j92tpLIM7twUbY8opJhJywyA6vPtI2Z1fcXTIWd5 0XPFtQO3WKwMVC/GVhMPMdoG52U7HW8228gd+f2ABsqjPWYWqJ1MFn3AlUa1UeTiH9fqBk1jjZaM 7+czV0I664zBechNdn3e9rG3geCg+aF4RhcaVpjwTj2rHO3sOdwHSPdj/gauwqRcalsyiMXHM4Ws ZkJHwlgkmeHlPuV1LI5D1l08eB6olYIpUNHRFrrvwb562bTYzB5MRuF3sTGrvSrIzo9uoV1/A3U0 5K2JRVRevq4opbs/eHnrc7MKDf2+yfdWrPa37S+bISnHOLaVxATywy39FCqQmbkHzJ8= -----END CERTIFICATE----- Swisscom Root CA 2 ================== -----BEGIN CERTIFICATE----- MIIF2TCCA8GgAwIBAgIQHp4o6Ejy5e/DfEoeWhhntjANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQG EwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0YWwgQ2VydGlmaWNhdGUgU2Vy dmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3QgQ0EgMjAeFw0xMTA2MjQwODM4MTRaFw0zMTA2 MjUwNzM4MTRaMGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGln aXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAyMIIC IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlUJOhJ1R5tMJ6HJaI2nbeHCOFvErjw0DzpPM LgAIe6szjPTpQOYXTKueuEcUMncy3SgM3hhLX3af+Dk7/E6J2HzFZ++r0rk0X2s682Q2zsKwzxNo ysjL67XiPS4h3+os1OD5cJZM/2pYmLcX5BtS5X4HAB1f2uY+lQS3aYg5oUFgJWFLlTloYhyxCwWJ wDaCFCE/rtuh/bxvHGCGtlOUSbkrRsVPACu/obvLP+DHVxxX6NZp+MEkUp2IVd3Chy50I9AU/SpH Wrumnf2U5NGKpV+GY3aFy6//SSj8gO1MedK75MDvAe5QQQg1I3ArqRa0jG6F6bYRzzHdUyYb3y1a SgJA/MTAtukxGggo5WDDH8SQjhBiYEQN7Aq+VRhxLKX0srwVYv8c474d2h5Xszx+zYIdkeNL6yxS NLCK/RJOlrDrcH+eOfdmQrGrrFLadkBXeyq96G4DsguAhYidDMfCd7Camlf0uPoTXGiTOmekl9Ab mbeGMktg2M7v0Ax/lZ9vh0+Hio5fCHyqW/xavqGRn1V9TrALacywlKinh/LTSlDcX3KwFnUey7QY Ypqwpzmqm59m2I2mbJYV4+by+PGDYmy7Velhk6M99bFXi08jsJvllGov34zflVEpYKELKeRcVVi3 qPyZ7iVNTA6z00yPhOgpD/0QVAKFyPnlw4vP5w8CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYw HQYDVR0hBBYwFDASBgdghXQBUwIBBgdghXQBUwIBMBIGA1UdEwEB/wQIMAYBAf8CAQcwHQYDVR0O BBYEFE0mICKJS9PVpAqhb97iEoHF8TwuMB8GA1UdIwQYMBaAFE0mICKJS9PVpAqhb97iEoHF8Twu MA0GCSqGSIb3DQEBCwUAA4ICAQAyCrKkG8t9voJXiblqf/P0wS4RfbgZPnm3qKhyN2abGu2sEzsO v2LwnN+ee6FTSA5BesogpxcbtnjsQJHzQq0Qw1zv/2BZf82Fo4s9SBwlAjxnffUy6S8w5X2lejjQ 82YqZh6NM4OKb3xuqFp1mrjX2lhIREeoTPpMSQpKwhI3qEAMw8jh0FcNlzKVxzqfl9NX+Ave5XLz o9v/tdhZsnPdTSpxsrpJ9csc1fV5yJmz/MFMdOO0vSk3FQQoHt5FRnDsr7p4DooqzgB53MBfGWcs a0vvaGgLQ+OswWIJ76bdZWGgr4RVSJFSHMYlkSrQwSIjYVmvRRGFHQEkNI/Ps/8XciATwoCqISxx OQ7Qj1zB09GOInJGTB2Wrk9xseEFKZZZ9LuedT3PDTcNYtsmjGOpI99nBjx8Oto0QuFmtEYE3saW mA9LSHokMnWRn6z3aOkquVVlzl1h0ydw2Df+n7mvoC5Wt6NlUe07qxS/TFED6F+KBZvuim6c779o +sjaC+NCydAXFJy3SuCvkychVSa1ZC+N8f+mQAWFBVzKBxlcCxMoTFh/wqXvRdpg065lYZ1Tg3TC rvJcwhbtkj6EPnNgiLx29CzP0H1907he0ZESEOnN3col49XtmS++dYFLJPlFRpTJKSFTnCZFqhMX 5OfNeOI5wSsSnqaeG8XmDtkx2Q== -----END CERTIFICATE----- Swisscom Root EV CA 2 ===================== -----BEGIN CERTIFICATE----- MIIF4DCCA8igAwIBAgIRAPL6ZOJ0Y9ON/RAdBB92ylgwDQYJKoZIhvcNAQELBQAwZzELMAkGA1UE BhMCY2gxETAPBgNVBAoTCFN3aXNzY29tMSUwIwYDVQQLExxEaWdpdGFsIENlcnRpZmljYXRlIFNl cnZpY2VzMR4wHAYDVQQDExVTd2lzc2NvbSBSb290IEVWIENBIDIwHhcNMTEwNjI0MDk0NTA4WhcN MzEwNjI1MDg0NTA4WjBnMQswCQYDVQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsT HERpZ2l0YWwgQ2VydGlmaWNhdGUgU2VydmljZXMxHjAcBgNVBAMTFVN3aXNzY29tIFJvb3QgRVYg Q0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMT3HS9X6lds93BdY7BxUglgRCgz o3pOCvrY6myLURYaVa5UJsTMRQdBTxB5f3HSek4/OE6zAMaVylvNwSqD1ycfMQ4jFrclyxy0uYAy Xhqdk/HoPGAsp15XGVhRXrwsVgu42O+LgrQ8uMIkqBPHoCE2G3pXKSinLr9xJZDzRINpUKTk4Rti GZQJo/PDvO/0vezbE53PnUgJUmfANykRHvvSEaeFGHR55E+FFOtSN+KxRdjMDUN/rhPSays/p8Li qG12W0OfvrSdsyaGOx9/5fLoZigWJdBLlzin5M8J0TbDC77aO0RYjb7xnglrPvMyxyuHxuxenPaH Za0zKcQvidm5y8kDnftslFGXEBuGCxobP/YCfnvUxVFkKJ3106yDgYjTdLRZncHrYTNaRdHLOdAG alNgHa/2+2m8atwBz735j9m9W8E6X47aD0upm50qKGsaCnw8qyIL5XctcfaCNYGu+HuB5ur+rPQa m3Rc6I8k9l2dRsQs0h4rIWqDJ2dVSqTjyDKXZpBy2uPUZC5f46Fq9mDU5zXNysRojddxyNMkM3Ox bPlq4SjbX8Y96L5V5jcb7STZDxmPX2MYWFCBUWVv8p9+agTnNCRxunZLWB4ZvRVgRaoMEkABnRDi xzgHcgplwLa7JSnaFp6LNYth7eVxV4O1PHGf40+/fh6Bn0GXAgMBAAGjgYYwgYMwDgYDVR0PAQH/ BAQDAgGGMB0GA1UdIQQWMBQwEgYHYIV0AVMCAgYHYIV0AVMCAjASBgNVHRMBAf8ECDAGAQH/AgED MB0GA1UdDgQWBBRF2aWBbj2ITY1x0kbBbkUe88SAnTAfBgNVHSMEGDAWgBRF2aWBbj2ITY1x0kbB bkUe88SAnTANBgkqhkiG9w0BAQsFAAOCAgEAlDpzBp9SSzBc1P6xXCX5145v9Ydkn+0UjrgEjihL j6p7jjm02Vj2e6E1CqGdivdj5eu9OYLU43otb98TPLr+flaYC/NUn81ETm484T4VvwYmneTwkLbU wp4wLh/vx3rEUMfqe9pQy3omywC0Wqu1kx+AiYQElY2NfwmTv9SoqORjbdlk5LgpWgi/UOGED1V7 XwgiG/W9mR4U9s70WBCCswo9GcG/W6uqmdjyMb3lOGbcWAXH7WMaLgqXfIeTK7KK4/HsGOV1timH 59yLGn602MnTihdsfSlEvoqq9X46Lmgxk7lq2prg2+kupYTNHAq4Sgj5nPFhJpiTt3tm7JFe3VE/ 23MPrQRYCd0EApUKPtN236YQHoA96M2kZNEzx5LH4k5E4wnJTsJdhw4Snr8PyQUQ3nqjsTzyP6Wq J3mtMX0f/fwZacXduT98zca0wjAefm6S139hdlqP65VNvBFuIXxZN5nQBrz5Bm0yFqXZaajh3DyA HmBR3NdUIR7KYndP+tiPsys6DXhyyWhBWkdKwqPrGtcKqzwyVcgKEZzfdNbwQBUdyLmPtTbFr/gi uMod89a2GQ+fYWVq6nTIfI/DT11lgh/ZDYnadXL77/FHZxOzyNEZiCcmmpl5fx7kLD977vHeTYuW l8PVP3wbI+2ksx0WckNLIOFZfsLorSa/ovc= -----END CERTIFICATE----- CA Disig Root R1 ================ -----BEGIN CERTIFICATE----- MIIFaTCCA1GgAwIBAgIJAMMDmu5QkG4oMA0GCSqGSIb3DQEBBQUAMFIxCzAJBgNVBAYTAlNLMRMw EQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNp ZyBSb290IFIxMB4XDTEyMDcxOTA5MDY1NloXDTQyMDcxOTA5MDY1NlowUjELMAkGA1UEBhMCU0sx EzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERp c2lnIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCqw3j33Jijp1pedxiy 3QRkD2P9m5YJgNXoqqXinCaUOuiZc4yd39ffg/N4T0Dhf9Kn0uXKE5Pn7cZ3Xza1lK/oOI7bm+V8 u8yN63Vz4STN5qctGS7Y1oprFOsIYgrY3LMATcMjfF9DCCMyEtztDK3AfQ+lekLZWnDZv6fXARz2 m6uOt0qGeKAeVjGu74IKgEH3G8muqzIm1Cxr7X1r5OJeIgpFy4QxTaz+29FHuvlglzmxZcfe+5nk CiKxLU3lSCZpq+Kq8/v8kiky6bM+TR8noc2OuRf7JT7JbvN32g0S9l3HuzYQ1VTW8+DiR0jm3hTa YVKvJrT1cU/J19IG32PK/yHoWQbgCNWEFVP3Q+V8xaCJmGtzxmjOZd69fwX3se72V6FglcXM6pM6 vpmumwKjrckWtc7dXpl4fho5frLABaTAgqWjR56M6ly2vGfb5ipN0gTco65F97yLnByn1tUD3AjL LhbKXEAz6GfDLuemROoRRRw1ZS0eRWEkG4IupZ0zXWX4Qfkuy5Q/H6MMMSRE7cderVC6xkGbrPAX ZcD4XW9boAo0PO7X6oifmPmvTiT6l7Jkdtqr9O3jw2Dv1fkCyC2fg69naQanMVXVz0tv/wQFx1is XxYb5dKj6zHbHzMVTdDypVP1y+E9Tmgt2BLdqvLmTZtJ5cUoobqwWsagtQIDAQABo0IwQDAPBgNV HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUiQq0OJMa5qvum5EY+fU8PjXQ 04IwDQYJKoZIhvcNAQEFBQADggIBADKL9p1Kyb4U5YysOMo6CdQbzoaz3evUuii+Eq5FLAR0rBNR xVgYZk2C2tXck8An4b58n1KeElb21Zyp9HWc+jcSjxyT7Ff+Bw+r1RL3D65hXlaASfX8MPWbTx9B LxyE04nH4toCdu0Jz2zBuByDHBb6lM19oMgY0sidbvW9adRtPTXoHqJPYNcHKfyyo6SdbhWSVhlM CrDpfNIZTUJG7L399ldb3Zh+pE3McgODWF3vkzpBemOqfDqo9ayk0d2iLbYq/J8BjuIQscTK5Gfb VSUZP/3oNn6z4eGBrxEWi1CXYBmCAMBrTXO40RMHPuq2MU/wQppt4hF05ZSsjYSVPCGvxdpHyN85 YmLLW1AL14FABZyb7bq2ix4Eb5YgOe2kfSnbSM6C3NQCjR0EMVrHS/BsYVLXtFHCgWzN4funodKS ds+xDzdYpPJScWc/DIh4gInByLUfkmO+p3qKViwaqKactV2zY9ATIKHrkWzQjX2v3wvkF7mGnjix lAxYjOBVqjtjbZqJYLhkKpLGN/R+Q0O3c+gB53+XD9fyexn9GtePyfqFa3qdnom2piiZk4hA9z7N UaPK6u95RyG1/jLix8NRb76AdPCkwzryT+lf3xkK8jsTQ6wxpLPn6/wY1gGp8yqPNg7rtLG8t0zJ a7+h89n07eLw4+1knj0vllJPgFOL -----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----- WoSign ====== -----BEGIN CERTIFICATE----- MIIFdjCCA16gAwIBAgIQXmjWEXGUY1BWAGjzPsnFkTANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQG EwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxKjAoBgNVBAMTIUNlcnRpZmljYXRpb24g QXV0aG9yaXR5IG9mIFdvU2lnbjAeFw0wOTA4MDgwMTAwMDFaFw0zOTA4MDgwMTAwMDFaMFUxCzAJ BgNVBAYTAkNOMRowGAYDVQQKExFXb1NpZ24gQ0EgTGltaXRlZDEqMCgGA1UEAxMhQ2VydGlmaWNh dGlvbiBBdXRob3JpdHkgb2YgV29TaWduMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA vcqNrLiRFVaXe2tcesLea9mhsMMQI/qnobLMMfo+2aYpbxY94Gv4uEBf2zmoAHqLoE1UfcIiePyO CbiohdfMlZdLdNiefvAA5A6JrkkoRBoQmTIPJYhTpA2zDxIIFgsDcSccf+Hb0v1naMQFXQoOXXDX 2JegvFNBmpGN9J42Znp+VsGQX+axaCA2pIwkLCxHC1l2ZjC1vt7tj/id07sBMOby8w7gLJKA84X5 KIq0VC6a7fd2/BVoFutKbOsuEo/Uz/4Mx1wdC34FMr5esAkqQtXJTpCzWQ27en7N1QhatH/YHGkR +ScPewavVIMYe+HdVHpRaG53/Ma/UkpmRqGyZxq7o093oL5d//xWC0Nyd5DKnvnyOfUNqfTq1+ez EC8wQjchzDBwyYaYD8xYTYO7feUapTeNtqwylwA6Y3EkHp43xP901DfA4v6IRmAR3Qg/UDaruHqk lWJqbrDKaiFaafPz+x1wOZXzp26mgYmhiMU7ccqjUu6Du/2gd/Tkb+dC221KmYo0SLwX3OSACCK2 8jHAPwQ+658geda4BmRkAjHXqc1S+4RFaQkAKtxVi8QGRkvASh0JWzko/amrzgD5LkhLJuYwTKVY yrREgk/nkR4zw7CT/xH8gdLKH3Ep3XZPkiWvHYG3Dy+MwwbMLyejSuQOmbp8HkUff6oZRZb9/D0C AwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFOFmzw7R 8bNLtwYgFP6HEtX2/vs+MA0GCSqGSIb3DQEBBQUAA4ICAQCoy3JAsnbBfnv8rWTjMnvMPLZdRtP1 LOJwXcgu2AZ9mNELIaCJWSQBnfmvCX0KI4I01fx8cpm5o9dU9OpScA7F9dY74ToJMuYhOZO9sxXq T2r09Ys/L3yNWC7F4TmgPsc9SnOeQHrAK2GpZ8nzJLmzbVUsWh2eJXLOC62qx1ViC777Y7NhRCOj y+EaDveaBk3e1CNOIZZbOVtXHS9dCF4Jef98l7VNg64N1uajeeAz0JmWAjCnPv/So0M/BVoG6kQC 2nz4SNAzqfkHx5Xh9T71XXG68pWpdIhhWeO/yloTunK0jF02h+mmxTwTv97QRCbut+wucPrXnbes 5cVAWubXbHssw1abR80LzvobtCHXt2a49CUwi1wNuepnsvRtrtWhnk/Yn+knArAdBtaP4/tIEp9/ EaEQPkxROpaw0RPxx9gmrjrKkcRpnd8BKWRRb2jaFOwIQZeQjdCygPLPwj2/kWjFgGcexGATVdVh mVd8upUPYUk6ynW8yQqTP2cOEvIo4jEbwFcW3wh8GcF+Dx+FHgo2fFt+J7x6v+Db9NpSvd4MVHAx kUOVyLzwPt0JfjBkUO1/AaQzZ01oT74V77D2AhGiGxMlOtzCWfHjXEa7ZywCRuoeSKbmW9m1vFGi kpbbqsY3Iqb+zCB0oy2pLmvLwIIRIbWTee5Ehr7XHuQe+w== -----END CERTIFICATE----- WoSign China ============ -----BEGIN CERTIFICATE----- MIIFWDCCA0CgAwIBAgIQUHBrzdgT/BtOOzNy0hFIjTANBgkqhkiG9w0BAQsFADBGMQswCQYDVQQG EwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxGzAZBgNVBAMMEkNBIOayg+mAmuagueiv geS5pjAeFw0wOTA4MDgwMTAwMDFaFw0zOTA4MDgwMTAwMDFaMEYxCzAJBgNVBAYTAkNOMRowGAYD VQQKExFXb1NpZ24gQ0EgTGltaXRlZDEbMBkGA1UEAwwSQ0Eg5rKD6YCa5qC56K+B5LmmMIICIjAN BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0EkhHiX8h8EqwqzbdoYGTufQdDTc7WU1/FDWiD+k 8H/rD195L4mx/bxjWDeTmzj4t1up+thxx7S8gJeNbEvxUNUqKaqoGXqW5pWOdO2XCld19AXbbQs5 uQF/qvbW2mzmBeCkTVL829B0txGMe41P/4eDrv8FAxNXUDf+jJZSEExfv5RxadmWPgxDT74wwJ85 dE8GRV2j1lY5aAfMh09Qd5Nx2UQIsYo06Yms25tO4dnkUkWMLhQfkWsZHWgpLFbE4h4TV2TwYeO5 Ed+w4VegG63XX9Gv2ystP9Bojg/qnw+LNVgbExz03jWhCl3W6t8Sb8D7aQdGctyB9gQjF+BNdeFy b7Ao65vh4YOhn0pdr8yb+gIgthhid5E7o9Vlrdx8kHccREGkSovrlXLp9glk3Kgtn3R46MGiCWOc 76DbT52VqyBPt7D3h1ymoOQ3OMdc4zUPLK2jgKLsLl3Az+2LBcLmc272idX10kaO6m1jGx6KyX2m +Jzr5dVjhU1zZmkR/sgO9MHHZklTfuQZa/HpelmjbX7FF+Ynxu8b22/8DU0GAbQOXDBGVWCvOGU6 yke6rCzMRh+yRpY/8+0mBe53oWprfi1tWFxK1I5nuPHa1UaKJ/kR8slC/k7e3x9cxKSGhxYzoacX GKUN5AXlK8IrC6KVkLn9YDxOiT7nnO4fuwECAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1Ud EwEB/wQFMAMBAf8wHQYDVR0OBBYEFOBNv9ybQV0T6GTwp+kVpOGBwboxMA0GCSqGSIb3DQEBCwUA A4ICAQBqinA4WbbaixjIvirTthnVZil6Xc1bL3McJk6jfW+rtylNpumlEYOnOXOvEESS5iVdT2H6 yAa+Tkvv/vMx/sZ8cApBWNromUuWyXi8mHwCKe0JgOYKOoICKuLJL8hWGSbueBwj/feTZU7n85iY r83d2Z5AiDEoOqsuC7CsDCT6eiaY8xJhEPRdF/d+4niXVOKM6Cm6jBAyvd0zaziGfjk9DgNyp115 j0WKWa5bIW4xRtVZjc8VX90xJc/bYNaBRHIpAlf2ltTW/+op2znFuCyKGo3Oy+dCMYYFaA6eFN0A kLppRQjbbpCBhqcqBT/mhDn4t/lXX0ykeVoQDF7Va/81XwVRHmyjdanPUIPTfPRm94KNPQx96N97 qA4bLJyuQHCH2u2nFoJavjVsIE4iYdm8UXrNemHcSxH5/mc0zy4EZmFcV5cjjPOGG0jfKq+nwf/Y jj4Du9gqsPoUJbJRa4ZDhS4HIxaAjUz7tGM7zMN07RujHv41D198HRaG9Q7DlfEvr10lO1Hm13ZB ONFLAzkopR6RctR9q5czxNM+4Gm2KHmgCY0c0f9BckgG/Jou5yD5m6Leie2uPAmvylezkolwQOQv T8Jwg0DXJCxr5wkf09XHwQj02w47HAcLQxGEIYbpgNR12KvxAmLBsX5VYc8T1yaw15zLKYs4SgsO kI26oQ== -----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----- proftpd-mod_proxy-0.8/config.guess000077500000000000000000001364711402074030700174060ustar00rootroot00000000000000#! /bin/sh # Attempt to guess a canonical system name. # Copyright 1992-2020 Free Software Foundation, Inc. timestamp='2020-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/gitweb/?p=config.git;a=blob_plain;f=config.guess # # Please send patches to . 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-2020 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 # 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 { 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/*) # If the system lacks a compiler, then just pick glibc. # We could probably try harder. LIBC=gnu set_cc_for_build cat <<-EOF > "$dummy.c" #include #if defined(__UCLIBC__) LIBC=uclibc #elif defined(__dietlibc__) LIBC=dietlibc #else LIBC=gnu #endif EOF eval "`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^LIBC' | sed 's, ,,g'`" # If ldd exists, use it to detect musl libc. if command -v ldd >/dev/null && \ ldd --version 2>&1 | grep -q ^musl then LIBC=musl 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". sysctl="sysctl -n hw.machine_arch" UNAME_MACHINE_ARCH=`(uname -p 2>/dev/null || \ "/sbin/$sysctl" 2>/dev/null || \ "/usr/sbin/$sysctl" 2>/dev/null || \ echo unknown)` case "$UNAME_MACHINE_ARCH" in 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. echo "$machine-${os}${release}${abi-}" exit ;; *:Bitrig:*:*) UNAME_MACHINE_ARCH=`arch | sed 's/Bitrig.//'` echo "$UNAME_MACHINE_ARCH"-unknown-bitrig"$UNAME_RELEASE" exit ;; *:OpenBSD:*:*) UNAME_MACHINE_ARCH=`arch | sed 's/OpenBSD.//'` echo "$UNAME_MACHINE_ARCH"-unknown-openbsd"$UNAME_RELEASE" exit ;; *:LibertyBSD:*:*) UNAME_MACHINE_ARCH=`arch | sed 's/^.*BSD\.//'` echo "$UNAME_MACHINE_ARCH"-unknown-libertybsd"$UNAME_RELEASE" exit ;; *:MidnightBSD:*:*) echo "$UNAME_MACHINE"-unknown-midnightbsd"$UNAME_RELEASE" exit ;; *:ekkoBSD:*:*) echo "$UNAME_MACHINE"-unknown-ekkobsd"$UNAME_RELEASE" exit ;; *:SolidBSD:*:*) echo "$UNAME_MACHINE"-unknown-solidbsd"$UNAME_RELEASE" exit ;; *:OS108:*:*) echo "$UNAME_MACHINE"-unknown-os108_"$UNAME_RELEASE" exit ;; macppc:MirBSD:*:*) echo powerpc-unknown-mirbsd"$UNAME_RELEASE" exit ;; *:MirBSD:*:*) echo "$UNAME_MACHINE"-unknown-mirbsd"$UNAME_RELEASE" exit ;; *:Sortix:*:*) echo "$UNAME_MACHINE"-unknown-sortix exit ;; *:Twizzler:*:*) echo "$UNAME_MACHINE"-unknown-twizzler exit ;; *:Redox:*:*) echo "$UNAME_MACHINE"-unknown-redox exit ;; mips:OSF1:*.*) echo mips-dec-osf1 exit ;; alpha:OSF1:*:*) 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. echo "$UNAME_MACHINE"-dec-osf"`echo "$UNAME_RELEASE" | sed -e 's/^[PVTX]//' | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz`" # Reset EXIT trap before exiting to avoid spurious non-zero exit code. exitcode=$? trap '' 0 exit $exitcode ;; Amiga*:UNIX_System_V:4.0:*) echo m68k-unknown-sysv4 exit ;; *:[Aa]miga[Oo][Ss]:*:*) echo "$UNAME_MACHINE"-unknown-amigaos exit ;; *:[Mm]orph[Oo][Ss]:*:*) echo "$UNAME_MACHINE"-unknown-morphos exit ;; *:OS/390:*:*) echo i370-ibm-openedition exit ;; *:z/VM:*:*) echo s390-ibm-zvmoe exit ;; *:OS400:*:*) echo powerpc-ibm-os400 exit ;; arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) echo arm-acorn-riscix"$UNAME_RELEASE" exit ;; arm*:riscos:*:*|arm*:RISCOS:*:*) echo arm-unknown-riscos exit ;; SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) echo hppa1.1-hitachi-hiuxmpp exit ;; Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. if test "`(/bin/universe) 2>/dev/null`" = att ; then echo pyramid-pyramid-sysv3 else echo pyramid-pyramid-bsd fi exit ;; NILE*:*:*:dcosx) echo pyramid-pyramid-svr4 exit ;; DRS?6000:unix:4.0:6*) echo sparc-icl-nx6 exit ;; DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*) case `/usr/bin/uname -p` in sparc) echo sparc-icl-nx7; exit ;; esac ;; s390x:SunOS:*:*) echo "$UNAME_MACHINE"-ibm-solaris2"`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'`" exit ;; sun4H:SunOS:5.*:*) echo sparc-hal-solaris2"`echo "$UNAME_RELEASE"|sed -e 's/[^.]*//'`" exit ;; sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) echo sparc-sun-solaris2"`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'`" exit ;; i86pc:AuroraUX:5.*:* | i86xen:AuroraUX:5.*:*) echo i386-pc-auroraux"$UNAME_RELEASE" exit ;; 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 [ "$CC_FOR_BUILD" != no_compiler_found ]; then if (echo '#ifdef __amd64'; echo IS_64BIT_ARCH; echo '#endif') | \ (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \ grep IS_64BIT_ARCH >/dev/null then SUN_ARCH=x86_64 fi fi echo "$SUN_ARCH"-pc-solaris2"`echo "$UNAME_RELEASE"|sed -e 's/[^.]*//'`" exit ;; 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. echo sparc-sun-solaris3"`echo "$UNAME_RELEASE"|sed -e 's/[^.]*//'`" exit ;; 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'. echo sparc-sun-sunos"`echo "$UNAME_RELEASE"|sed -e 's/-/_/'`" exit ;; sun3*:SunOS:*:*) echo m68k-sun-sunos"$UNAME_RELEASE" exit ;; 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) echo m68k-sun-sunos"$UNAME_RELEASE" ;; sun4) echo sparc-sun-sunos"$UNAME_RELEASE" ;; esac exit ;; aushp:SunOS:*:*) echo sparc-auspex-sunos"$UNAME_RELEASE" exit ;; # 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:*:*) echo m68k-atari-mint"$UNAME_RELEASE" exit ;; atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) echo m68k-atari-mint"$UNAME_RELEASE" exit ;; *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) echo m68k-atari-mint"$UNAME_RELEASE" exit ;; milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) echo m68k-milan-mint"$UNAME_RELEASE" exit ;; hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) echo m68k-hades-mint"$UNAME_RELEASE" exit ;; *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) echo m68k-unknown-mint"$UNAME_RELEASE" exit ;; m68k:machten:*:*) echo m68k-apple-machten"$UNAME_RELEASE" exit ;; powerpc:machten:*:*) echo powerpc-apple-machten"$UNAME_RELEASE" exit ;; RISC*:Mach:*:*) echo mips-dec-mach_bsd4.3 exit ;; RISC*:ULTRIX:*:*) echo mips-dec-ultrix"$UNAME_RELEASE" exit ;; VAX*:ULTRIX*:*:*) echo vax-dec-ultrix"$UNAME_RELEASE" exit ;; 2020:CLIX:*:* | 2430:CLIX:*:*) echo clipper-intergraph-clix"$UNAME_RELEASE" exit ;; 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; } echo mips-mips-riscos"$UNAME_RELEASE" exit ;; Motorola:PowerMAX_OS:*:*) echo powerpc-motorola-powermax exit ;; Motorola:*:4.3:PL8-*) echo powerpc-harris-powermax exit ;; Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*) echo powerpc-harris-powermax exit ;; Night_Hawk:Power_UNIX:*:*) echo powerpc-harris-powerunix exit ;; m88k:CX/UX:7*:*) echo m88k-harris-cxux7 exit ;; m88k:*:4*:R4*) echo m88k-motorola-sysv4 exit ;; m88k:*:3*:R3*) echo m88k-motorola-sysv3 exit ;; AViiON:dgux:*:*) # DG/UX returns AViiON for all architectures UNAME_PROCESSOR=`/usr/bin/uname -p` if [ "$UNAME_PROCESSOR" = mc88100 ] || [ "$UNAME_PROCESSOR" = mc88110 ] then if [ "$TARGET_BINARY_INTERFACE"x = m88kdguxelfx ] || \ [ "$TARGET_BINARY_INTERFACE"x = x ] then echo m88k-dg-dgux"$UNAME_RELEASE" else echo m88k-dg-dguxbcs"$UNAME_RELEASE" fi else echo i586-dg-dgux"$UNAME_RELEASE" fi exit ;; M88*:DolphinOS:*:*) # DolphinOS (SVR3) echo m88k-dolphin-sysv3 exit ;; M88*:*:R3*:*) # Delta 88k system running SVR3 echo m88k-motorola-sysv3 exit ;; XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) echo m88k-tektronix-sysv3 exit ;; Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) echo m68k-tektronix-bsd exit ;; *:IRIX*:*:*) echo mips-sgi-irix"`echo "$UNAME_RELEASE"|sed -e 's/-/_/g'`" exit ;; ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id exit ;; # Note that: echo "'`uname -s`'" gives 'AIX ' i*86:AIX:*:*) echo i386-ibm-aix exit ;; ia64:AIX:*:*) if [ -x /usr/bin/oslevel ] ; then IBM_REV=`/usr/bin/oslevel` else IBM_REV="$UNAME_VERSION.$UNAME_RELEASE" fi echo "$UNAME_MACHINE"-ibm-aix"$IBM_REV" exit ;; *: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 echo "$SYSTEM_NAME" else echo rs6000-ibm-aix3.2.5 fi elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then echo rs6000-ibm-aix3.2.4 else echo rs6000-ibm-aix3.2 fi exit ;; *: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 [ -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 echo "$IBM_ARCH"-ibm-aix"$IBM_REV" exit ;; *:AIX:*:*) echo rs6000-ibm-aix exit ;; ibmrt:4.4BSD:*|romp-ibm:4.4BSD:*) echo romp-ibm-bsd4.4 exit ;; ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and echo romp-ibm-bsd"$UNAME_RELEASE" # 4.3 with uname added to exit ;; # report: romp-ibm BSD 4.3 *:BOSX:*:*) echo rs6000-bull-bosx exit ;; DPX/2?00:B.O.S.:*:*) echo m68k-bull-sysv3 exit ;; 9000/[34]??:4.3bsd:1.*:*) echo m68k-hp-bsd exit ;; hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) echo m68k-hp-bsd4.4 exit ;; 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 [ -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 [ "$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 [ "$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 echo "$HP_ARCH"-hp-hpux"$HPUX_REV" exit ;; ia64:HP-UX:*:*) HPUX_REV=`echo "$UNAME_RELEASE"|sed -e 's/[^.]*.[0B]*//'` echo ia64-hp-hpux"$HPUX_REV" exit ;; 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; } echo unknown-hitachi-hiuxwe2 exit ;; 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:*) echo hppa1.1-hp-bsd exit ;; 9000/8??:4.3bsd:*:*) echo hppa1.0-hp-bsd exit ;; *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*) echo hppa1.0-hp-mpeix exit ;; hp7??:OSF1:*:* | hp8?[79]:OSF1:*:*) echo hppa1.1-hp-osf exit ;; hp8??:OSF1:*:*) echo hppa1.0-hp-osf exit ;; i*86:OSF1:*:*) if [ -x /usr/sbin/sysversion ] ; then echo "$UNAME_MACHINE"-unknown-osf1mk else echo "$UNAME_MACHINE"-unknown-osf1 fi exit ;; parisc*:Lites*:*:*) echo hppa1.1-hp-lites exit ;; C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) echo c1-convex-bsd exit ;; 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*:*) echo c34-convex-bsd exit ;; C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) echo c38-convex-bsd exit ;; C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) echo c4-convex-bsd exit ;; CRAY*Y-MP:*:*:*) echo ymp-cray-unicos"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' exit ;; 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:*:*:*) echo t90-cray-unicos"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' exit ;; CRAY*T3E:*:*:*) echo alphaev5-cray-unicosmk"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' exit ;; CRAY*SV1:*:*:*) echo sv1-cray-unicos"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' exit ;; *:UNICOS/mp:*:*) echo craynv-cray-unicosmp"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' exit ;; 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/ /_/'` echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" exit ;; 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/ /_/'` echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" exit ;; i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) echo "$UNAME_MACHINE"-pc-bsdi"$UNAME_RELEASE" exit ;; sparc*:BSD/OS:*:*) echo sparc-unknown-bsdi"$UNAME_RELEASE" exit ;; *:BSD/OS:*:*) echo "$UNAME_MACHINE"-unknown-bsdi"$UNAME_RELEASE" exit ;; 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 echo "${UNAME_PROCESSOR}"-unknown-freebsd"`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`"-gnueabi else echo "${UNAME_PROCESSOR}"-unknown-freebsd"`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`"-gnueabihf fi exit ;; *:FreeBSD:*:*) UNAME_PROCESSOR=`/usr/bin/uname -p` case "$UNAME_PROCESSOR" in amd64) UNAME_PROCESSOR=x86_64 ;; i386) UNAME_PROCESSOR=i586 ;; esac echo "$UNAME_PROCESSOR"-unknown-freebsd"`echo "$UNAME_RELEASE"|sed -e 's/[-(].*//'`" exit ;; i*:CYGWIN*:*) echo "$UNAME_MACHINE"-pc-cygwin exit ;; *:MINGW64*:*) echo "$UNAME_MACHINE"-pc-mingw64 exit ;; *:MINGW*:*) echo "$UNAME_MACHINE"-pc-mingw32 exit ;; *:MSYS*:*) echo "$UNAME_MACHINE"-pc-msys exit ;; i*:PW*:*) echo "$UNAME_MACHINE"-pc-pw32 exit ;; *:Interix*:*) case "$UNAME_MACHINE" in x86) echo i586-pc-interix"$UNAME_RELEASE" exit ;; authenticamd | genuineintel | EM64T) echo x86_64-unknown-interix"$UNAME_RELEASE" exit ;; IA64) echo ia64-unknown-interix"$UNAME_RELEASE" exit ;; esac ;; i*:UWIN*:*) echo "$UNAME_MACHINE"-pc-uwin exit ;; amd64:CYGWIN*:*:* | x86_64:CYGWIN*:*:*) echo x86_64-pc-cygwin exit ;; prep*:SunOS:5.*:*) echo powerpcle-unknown-solaris2"`echo "$UNAME_RELEASE"|sed -e 's/[^.]*//'`" exit ;; *:GNU:*:*) # the GNU system echo "`echo "$UNAME_MACHINE"|sed -e 's,[-/].*$,,'`-unknown-$LIBC`echo "$UNAME_RELEASE"|sed -e 's,/.*$,,'`" exit ;; *:GNU/*:*:*) # other systems with GNU libc and userland echo "$UNAME_MACHINE-unknown-`echo "$UNAME_SYSTEM" | sed 's,^[^/]*/,,' | tr "[:upper:]" "[:lower:]"``echo "$UNAME_RELEASE"|sed -e 's/[-(].*//'`-$LIBC" exit ;; *:Minix:*:*) echo "$UNAME_MACHINE"-unknown-minix exit ;; aarch64:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; aarch64_be:Linux:*:*) UNAME_MACHINE=aarch64_be echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; 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 echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; arc:Linux:*:* | arceb:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; arm*:Linux:*:*) set_cc_for_build if echo __ARM_EABI__ | $CC_FOR_BUILD -E - 2>/dev/null \ | grep -q __ARM_EABI__ then echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" else if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \ | grep -q __ARM_PCS_VFP then echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"eabi else echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"eabihf fi fi exit ;; avr32*:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; cris:Linux:*:*) echo "$UNAME_MACHINE"-axis-linux-"$LIBC" exit ;; crisv32:Linux:*:*) echo "$UNAME_MACHINE"-axis-linux-"$LIBC" exit ;; e2k:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; frv:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; hexagon:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; i*86:Linux:*:*) echo "$UNAME_MACHINE"-pc-linux-"$LIBC" exit ;; ia64:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; k1om:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; m32r*:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; m68*:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; 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 eval "`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^CPU\|^MIPS_ENDIAN\|^LIBCABI'`" test "x$CPU" != x && { echo "$CPU${MIPS_ENDIAN}-unknown-linux-$LIBCABI"; exit; } ;; mips64el:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; openrisc*:Linux:*:*) echo or1k-unknown-linux-"$LIBC" exit ;; or32:Linux:*:* | or1k*:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; padre:Linux:*:*) echo sparc-unknown-linux-"$LIBC" exit ;; parisc64:Linux:*:* | hppa64:Linux:*:*) echo hppa64-unknown-linux-"$LIBC" exit ;; parisc:Linux:*:* | hppa:Linux:*:*) # Look for CPU level case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in PA7*) echo hppa1.1-unknown-linux-"$LIBC" ;; PA8*) echo hppa2.0-unknown-linux-"$LIBC" ;; *) echo hppa-unknown-linux-"$LIBC" ;; esac exit ;; ppc64:Linux:*:*) echo powerpc64-unknown-linux-"$LIBC" exit ;; ppc:Linux:*:*) echo powerpc-unknown-linux-"$LIBC" exit ;; ppc64le:Linux:*:*) echo powerpc64le-unknown-linux-"$LIBC" exit ;; ppcle:Linux:*:*) echo powerpcle-unknown-linux-"$LIBC" exit ;; riscv32:Linux:*:* | riscv64:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; s390:Linux:*:* | s390x:Linux:*:*) echo "$UNAME_MACHINE"-ibm-linux-"$LIBC" exit ;; sh64*:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; sh*:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; sparc:Linux:*:* | sparc64:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; tile*:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; vax:Linux:*:*) echo "$UNAME_MACHINE"-dec-linux-"$LIBC" exit ;; x86_64:Linux:*:*) echo "$UNAME_MACHINE"-pc-linux-"$LIBC" exit ;; xtensa*:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; 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. echo i386-sequent-sysv4 exit ;; 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. echo "$UNAME_MACHINE"-pc-sysv4.2uw"$UNAME_VERSION" exit ;; i*86:OS/2:*:*) # If we were able to find `uname', then EMX Unix compatibility # is probably installed. echo "$UNAME_MACHINE"-pc-os2-emx exit ;; i*86:XTS-300:*:STOP) echo "$UNAME_MACHINE"-unknown-stop exit ;; i*86:atheos:*:*) echo "$UNAME_MACHINE"-unknown-atheos exit ;; i*86:syllable:*:*) echo "$UNAME_MACHINE"-pc-syllable exit ;; i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.[02]*:*) echo i386-unknown-lynxos"$UNAME_RELEASE" exit ;; i*86:*DOS:*:*) echo "$UNAME_MACHINE"-pc-msdosdjgpp exit ;; i*86:*:4.*:*) UNAME_REL=`echo "$UNAME_RELEASE" | sed 's/\/MP$//'` if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then echo "$UNAME_MACHINE"-univel-sysv"$UNAME_REL" else echo "$UNAME_MACHINE"-pc-sysv"$UNAME_REL" fi exit ;; 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 echo "$UNAME_MACHINE-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION}" exit ;; 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 echo "$UNAME_MACHINE"-pc-sco"$UNAME_REL" else echo "$UNAME_MACHINE"-pc-sysv32 fi exit ;; 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. echo i586-pc-msdosdjgpp exit ;; Intel:Mach:3*:*) echo i386-pc-mach3 exit ;; paragon:*:*:*) echo i860-intel-osf1 exit ;; i860:*:4.*:*) # i860-SVR4 if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then echo i860-stardent-sysv"$UNAME_RELEASE" # Stardent Vistra i860-SVR4 else # Add other i860-SVR4 vendors below as they are discovered. echo i860-unknown-sysv"$UNAME_RELEASE" # Unknown i860-SVR4 fi exit ;; mini*:CTIX:SYS*5:*) # "miniframe" echo m68010-convergent-sysv exit ;; mc68k:UNIX:SYSTEM5:3.51m) echo m68k-convergent-sysv exit ;; M680?0:D-NIX:5.3:*) echo m68k-diab-dnix exit ;; 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*:*) echo m68k-unknown-lynxos"$UNAME_RELEASE" exit ;; mc68030:UNIX_System_V:4.*:*) echo m68k-atari-sysv4 exit ;; TSUNAMI:LynxOS:2.*:*) echo sparc-unknown-lynxos"$UNAME_RELEASE" exit ;; rs6000:LynxOS:2.*:*) echo rs6000-unknown-lynxos"$UNAME_RELEASE" exit ;; PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.[02]*:*) echo powerpc-unknown-lynxos"$UNAME_RELEASE" exit ;; SM[BE]S:UNIX_SV:*:*) echo mips-dde-sysv"$UNAME_RELEASE" exit ;; RM*:ReliantUNIX-*:*:*) echo mips-sni-sysv4 exit ;; RM*:SINIX-*:*:*) echo mips-sni-sysv4 exit ;; *:SINIX-*:*:*) if uname -p 2>/dev/null >/dev/null ; then UNAME_MACHINE=`(uname -p) 2>/dev/null` echo "$UNAME_MACHINE"-sni-sysv4 else echo ns32k-sni-sysv fi exit ;; PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort # says echo i586-unisys-sysv4 exit ;; *:UNIX_System_V:4*:FTX*) # From Gerald Hewes . # How about differentiating between stratus architectures? -djm echo hppa1.1-stratus-sysv4 exit ;; *:*:*:FTX*) # From seanf@swdc.stratus.com. echo i860-stratus-sysv4 exit ;; i*86:VOS:*:*) # From Paul.Green@stratus.com. echo "$UNAME_MACHINE"-stratus-vos exit ;; *:VOS:*:*) # From Paul.Green@stratus.com. echo hppa1.1-stratus-vos exit ;; mc68*:A/UX:*:*) echo m68k-apple-aux"$UNAME_RELEASE" exit ;; news*:NEWS-OS:6*:*) echo mips-sony-newsos6 exit ;; R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) if [ -d /usr/nec ]; then echo mips-nec-sysv"$UNAME_RELEASE" else echo mips-unknown-sysv"$UNAME_RELEASE" fi exit ;; BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. echo powerpc-be-beos exit ;; BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. echo powerpc-apple-beos exit ;; BePC:BeOS:*:*) # BeOS running on Intel PC compatible. echo i586-pc-beos exit ;; BePC:Haiku:*:*) # Haiku running on Intel PC compatible. echo i586-pc-haiku exit ;; x86_64:Haiku:*:*) echo x86_64-unknown-haiku exit ;; SX-4:SUPER-UX:*:*) echo sx4-nec-superux"$UNAME_RELEASE" exit ;; SX-5:SUPER-UX:*:*) echo sx5-nec-superux"$UNAME_RELEASE" exit ;; SX-6:SUPER-UX:*:*) echo sx6-nec-superux"$UNAME_RELEASE" exit ;; SX-7:SUPER-UX:*:*) echo sx7-nec-superux"$UNAME_RELEASE" exit ;; SX-8:SUPER-UX:*:*) echo sx8-nec-superux"$UNAME_RELEASE" exit ;; SX-8R:SUPER-UX:*:*) echo sx8r-nec-superux"$UNAME_RELEASE" exit ;; SX-ACE:SUPER-UX:*:*) echo sxace-nec-superux"$UNAME_RELEASE" exit ;; Power*:Rhapsody:*:*) echo powerpc-apple-rhapsody"$UNAME_RELEASE" exit ;; *:Rhapsody:*:*) echo "$UNAME_MACHINE"-apple-rhapsody"$UNAME_RELEASE" exit ;; *: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 [ "$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 echo "$UNAME_PROCESSOR"-apple-darwin"$UNAME_RELEASE" exit ;; *:procnto*:*:* | *:QNX:[0123456789]*:*) UNAME_PROCESSOR=`uname -p` if test "$UNAME_PROCESSOR" = x86; then UNAME_PROCESSOR=i386 UNAME_MACHINE=pc fi echo "$UNAME_PROCESSOR"-"$UNAME_MACHINE"-nto-qnx"$UNAME_RELEASE" exit ;; *:QNX:*:4*) echo i386-pc-qnx exit ;; NEO-*:NONSTOP_KERNEL:*:*) echo neo-tandem-nsk"$UNAME_RELEASE" exit ;; NSE-*:NONSTOP_KERNEL:*:*) echo nse-tandem-nsk"$UNAME_RELEASE" exit ;; NSR-*:NONSTOP_KERNEL:*:*) echo nsr-tandem-nsk"$UNAME_RELEASE" exit ;; NSV-*:NONSTOP_KERNEL:*:*) echo nsv-tandem-nsk"$UNAME_RELEASE" exit ;; NSX-*:NONSTOP_KERNEL:*:*) echo nsx-tandem-nsk"$UNAME_RELEASE" exit ;; *:NonStop-UX:*:*) echo mips-compaq-nonstopux exit ;; BS2000:POSIX*:*:*) echo bs2000-siemens-sysv exit ;; DS/*:UNIX_System_V:*:*) echo "$UNAME_MACHINE"-"$UNAME_SYSTEM"-"$UNAME_RELEASE" exit ;; *:Plan9:*:*) # "uname -m" is not consistent, so use $cputype instead. 386 # is converted to i386 for consistency with other x86 # operating systems. # shellcheck disable=SC2154 if test "$cputype" = 386; then UNAME_MACHINE=i386 else UNAME_MACHINE="$cputype" fi echo "$UNAME_MACHINE"-unknown-plan9 exit ;; *:TOPS-10:*:*) echo pdp10-unknown-tops10 exit ;; *:TENEX:*:*) echo pdp10-unknown-tenex exit ;; KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) echo pdp10-dec-tops20 exit ;; XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) echo pdp10-xkl-tops20 exit ;; *:TOPS-20:*:*) echo pdp10-unknown-tops20 exit ;; *:ITS:*:*) echo pdp10-unknown-its exit ;; SEI:*:*:SEIUX) echo mips-sei-seiux"$UNAME_RELEASE" exit ;; *:DragonFly:*:*) echo "$UNAME_MACHINE"-unknown-dragonfly"`echo "$UNAME_RELEASE"|sed -e 's/[-(].*//'`" exit ;; *:*VMS:*:*) UNAME_MACHINE=`(uname -p) 2>/dev/null` case "$UNAME_MACHINE" in A*) echo alpha-dec-vms ; exit ;; I*) echo ia64-dec-vms ; exit ;; V*) echo vax-dec-vms ; exit ;; esac ;; *:XENIX:*:SysV) echo i386-pc-xenix exit ;; i*86:skyos:*:*) echo "$UNAME_MACHINE"-pc-skyos"`echo "$UNAME_RELEASE" | sed -e 's/ .*$//'`" exit ;; i*86:rdos:*:*) echo "$UNAME_MACHINE"-pc-rdos exit ;; i*86:AROS:*:*) echo "$UNAME_MACHINE"-pc-aros exit ;; x86_64:VMkernel:*:*) echo "$UNAME_MACHINE"-unknown-esx exit ;; amd64:Isilon\ OneFS:*:*) echo x86_64-unknown-onefs exit ;; *:Unleashed:*:*) echo "$UNAME_MACHINE"-unknown-unleashed"$UNAME_RELEASE" exit ;; esac # 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 </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 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.8/config.sub000077500000000000000000000755711402074030700170540ustar00rootroot00000000000000#! /bin/sh # Configuration validation subroutine script. # Copyright 1992-2020 Free Software Foundation, Inc. timestamp='2020-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"). # 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/gitweb/?p=config.git;a=blob_plain;f=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. 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-2020 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 IFS="-" read field1 field2 field3 field4 <&2 exit 1 ;; *-*-*-*) basic_machine=$field1-$field2 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-gnu* | linux-android* | linux-dietlibc \ | linux-newlib* | linux-musl* | linux-uclibc* | uclinux-uclibc* \ | uclinux-gnu* | kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* \ | netbsd*-eabi* | kopensolaris*-gnu* | cloudabi*-eabi* \ | storm-chaos* | os2-emx* | rtmk-nova*) basic_machine=$field1 os=$maybe_os ;; android-linux) basic_machine=$field1-unknown os=linux-android ;; *) basic_machine=$field1-$field2 os=$field3 ;; esac ;; *-*) # A lone config we happen to match not fitting any pattern case $field1-$field2 in decstation-3100) basic_machine=mips-dec 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 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 os= ;; *) basic_machine=$field1 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 os=bsd ;; a29khif) basic_machine=a29k-amd os=udi ;; adobe68k) basic_machine=m68010-adobe os=scout ;; alliant) basic_machine=fx80-alliant os= ;; altos | altos3068) basic_machine=m68k-altos os= ;; am29k) basic_machine=a29k-none os=bsd ;; amdahl) basic_machine=580-amdahl os=sysv ;; amiga) basic_machine=m68k-unknown os= ;; amigaos | amigados) basic_machine=m68k-unknown os=amigaos ;; amigaunix | amix) basic_machine=m68k-unknown os=sysv4 ;; apollo68) basic_machine=m68k-apollo os=sysv ;; apollo68bsd) basic_machine=m68k-apollo os=bsd ;; aros) basic_machine=i386-pc os=aros ;; aux) basic_machine=m68k-apple os=aux ;; balance) basic_machine=ns32k-sequent os=dynix ;; blackfin) basic_machine=bfin-unknown os=linux ;; cegcc) basic_machine=arm-unknown os=cegcc ;; convex-c1) basic_machine=c1-convex os=bsd ;; convex-c2) basic_machine=c2-convex os=bsd ;; convex-c32) basic_machine=c32-convex os=bsd ;; convex-c34) basic_machine=c34-convex os=bsd ;; convex-c38) basic_machine=c38-convex os=bsd ;; cray) basic_machine=j90-cray os=unicos ;; crds | unos) basic_machine=m68k-crds os= ;; da30) basic_machine=m68k-da30 os= ;; decstation | pmax | pmin | dec3100 | decstatn) basic_machine=mips-dec os= ;; delta88) basic_machine=m88k-motorola os=sysv3 ;; dicos) basic_machine=i686-pc os=dicos ;; djgpp) basic_machine=i586-pc os=msdosdjgpp ;; ebmon29k) basic_machine=a29k-amd os=ebmon ;; es1800 | OSE68k | ose68k | ose | OSE) basic_machine=m68k-ericsson os=ose ;; gmicro) basic_machine=tron-gmicro os=sysv ;; go32) basic_machine=i386-pc os=go32 ;; h8300hms) basic_machine=h8300-hitachi os=hms ;; h8300xray) basic_machine=h8300-hitachi os=xray ;; h8500hms) basic_machine=h8500-hitachi os=hms ;; harris) basic_machine=m88k-harris os=sysv3 ;; hp300 | hp300hpux) basic_machine=m68k-hp os=hpux ;; hp300bsd) basic_machine=m68k-hp os=bsd ;; hppaosf) basic_machine=hppa1.1-hp os=osf ;; hppro) basic_machine=hppa1.1-hp os=proelf ;; i386mach) basic_machine=i386-mach os=mach ;; isi68 | isi) basic_machine=m68k-isi os=sysv ;; m68knommu) basic_machine=m68k-unknown os=linux ;; magnum | m3230) basic_machine=mips-mips os=sysv ;; merlin) basic_machine=ns32k-utek os=sysv ;; mingw64) basic_machine=x86_64-pc os=mingw64 ;; mingw32) basic_machine=i686-pc os=mingw32 ;; mingw32ce) basic_machine=arm-unknown os=mingw32ce ;; monitor) basic_machine=m68k-rom68k os=coff ;; morphos) basic_machine=powerpc-unknown os=morphos ;; moxiebox) basic_machine=moxie-unknown os=moxiebox ;; msdos) basic_machine=i386-pc os=msdos ;; msys) basic_machine=i686-pc os=msys ;; mvs) basic_machine=i370-ibm os=mvs ;; nacl) basic_machine=le32-unknown os=nacl ;; ncr3000) basic_machine=i486-ncr os=sysv4 ;; netbsd386) basic_machine=i386-pc os=netbsd ;; netwinder) basic_machine=armv4l-rebel os=linux ;; news | news700 | news800 | news900) basic_machine=m68k-sony os=newsos ;; news1000) basic_machine=m68030-sony os=newsos ;; necv70) basic_machine=v70-nec os=sysv ;; nh3000) basic_machine=m68k-harris os=cxux ;; nh[45]000) basic_machine=m88k-harris os=cxux ;; nindy960) basic_machine=i960-intel os=nindy ;; mon960) basic_machine=i960-intel os=mon960 ;; nonstopux) basic_machine=mips-compaq os=nonstopux ;; os400) basic_machine=powerpc-ibm os=os400 ;; OSE68000 | ose68000) basic_machine=m68000-ericsson os=ose ;; os68k) basic_machine=m68k-none os=os68k ;; paragon) basic_machine=i860-intel os=osf ;; parisc) basic_machine=hppa-unknown os=linux ;; pw32) basic_machine=i586-unknown os=pw32 ;; rdos | rdos64) basic_machine=x86_64-pc os=rdos ;; rdos32) basic_machine=i386-pc os=rdos ;; rom68k) basic_machine=m68k-rom68k os=coff ;; sa29200) basic_machine=a29k-amd os=udi ;; sei) basic_machine=mips-sei os=seiux ;; sequent) basic_machine=i386-sequent os= ;; sps7) basic_machine=m68k-bull os=sysv2 ;; st2000) basic_machine=m68k-tandem os= ;; stratus) basic_machine=i860-stratus os=sysv4 ;; sun2) basic_machine=m68000-sun os= ;; sun2os3) basic_machine=m68000-sun os=sunos3 ;; sun2os4) basic_machine=m68000-sun os=sunos4 ;; sun3) basic_machine=m68k-sun os= ;; sun3os3) basic_machine=m68k-sun os=sunos3 ;; sun3os4) basic_machine=m68k-sun os=sunos4 ;; sun4) basic_machine=sparc-sun os= ;; sun4os3) basic_machine=sparc-sun os=sunos3 ;; sun4os4) basic_machine=sparc-sun os=sunos4 ;; sun4sol2) basic_machine=sparc-sun os=solaris2 ;; sun386 | sun386i | roadrunner) basic_machine=i386-sun os= ;; sv1) basic_machine=sv1-cray os=unicos ;; symmetry) basic_machine=i386-sequent os=dynix ;; t3e) basic_machine=alphaev5-cray os=unicos ;; t90) basic_machine=t90-cray os=unicos ;; toad1) basic_machine=pdp10-xkl os=tops20 ;; tpf) basic_machine=s390x-ibm os=tpf ;; udi29k) basic_machine=a29k-amd os=udi ;; ultra3) basic_machine=a29k-nyu os=sym1 ;; v810 | necv810) basic_machine=v810-nec os=none ;; vaxv) basic_machine=vax-dec os=sysv ;; vms) basic_machine=vax-dec os=vms ;; vsta) basic_machine=i386-pc os=vsta ;; vxworks960) basic_machine=i960-wrs os=vxworks ;; vxworks68) basic_machine=m68k-wrs os=vxworks ;; vxworks29k) basic_machine=a29k-wrs os=vxworks ;; xbox) basic_machine=i686-pc os=mingw32 ;; ymp) basic_machine=ymp-cray os=unicos ;; *) basic_machine=$1 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 os=cnk ;; decsystem10* | dec10*) cpu=pdp10 vendor=dec os=tops10 ;; decsystem20* | dec20*) cpu=pdp10 vendor=dec os=tops20 ;; delta | 3300 | motorola-3300 | motorola-delta \ | 3300-motorola | delta-motorola) cpu=m68k vendor=motorola ;; dpx2*) cpu=m68k vendor=bull os=sysv3 ;; encore | umax | mmax) cpu=ns32k vendor=encore ;; elxsi) cpu=elxsi vendor=elxsi os=${os:-bsd} ;; fx2800) cpu=i860 vendor=alliant ;; genix) cpu=ns32k vendor=ns ;; h3050r* | hiux*) cpu=hppa1.1 vendor=hitachi 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 os=sysv32 ;; i*86v4*) cpu=`echo "$1" | sed -e 's/86.*/86/'` vendor=pc os=sysv4 ;; i*86v) cpu=`echo "$1" | sed -e 's/86.*/86/'` vendor=pc os=sysv ;; i*86sol2) cpu=`echo "$1" | sed -e 's/86.*/86/'` vendor=pc os=solaris2 ;; j90 | j90-cray) cpu=j90 vendor=cray os=${os:-unicos} ;; iris | iris4d) cpu=mips vendor=sgi case $os in irix*) ;; *) os=irix4 ;; esac ;; miniframe) cpu=m68000 vendor=convergent ;; *mint | mint[0-9]* | *MiNT | *MiNT[0-9]*) cpu=m68k vendor=atari os=mint ;; news-3600 | risc-news) cpu=mips vendor=sony os=newsos ;; next | m*-next) cpu=m68k vendor=next case $os in openstep*) ;; nextstep*) ;; ns2*) os=nextstep2 ;; *) os=nextstep3 ;; esac ;; np1) cpu=np1 vendor=gould ;; op50n-* | op60c-*) cpu=hppa1.1 vendor=oki os=proelf ;; pa-hitachi) cpu=hppa1.1 vendor=hitachi 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 os=${os:-elf} ;; simso-wrs) cpu=sparclite vendor=wrs 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 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 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 [ x$os != x ] then case $os in # First match some system type aliases that might get confused # with valid system types. # solaris* is a basic system type, with this one exception. auroraux) os=auroraux ;; bluegene*) os=cnk ;; solaris1 | solaris1.*) os=`echo $os | sed -e 's|solaris1|sunos4|'` ;; solaris) os=solaris2 ;; unixware*) os=sysv4.2uw ;; gnu/linux*) os=`echo $os | sed -e 's|gnu/linux|linux-gnu|'` ;; # es1800 is here to avoid being matched by es* (a different OS) es1800*) os=ose ;; # Some version numbers need modification chorusos*) os=chorusos ;; isc) os=isc2.2 ;; sco6) os=sco5v6 ;; sco5) os=sco3.2v5 ;; sco4) os=sco3.2v4 ;; sco3.2.[4-9]*) os=`echo $os | sed -e 's/sco3.2./sco3.2v/'` ;; sco3.2v[4-9]* | sco5v6*) # Don't forget version if it is 3.2v4 or newer. ;; scout) # Don't match below ;; sco*) os=sco3.2v2 ;; psos*) os=psos ;; # Now accept the basic system types. # The portable systems comes first. # Each alternative MUST end in a * to match a version number. # sysv* is not here because it comes later, after sysvr4. gnu* | bsd* | mach* | minix* | genix* | ultrix* | irix* \ | *vms* | esix* | aix* | cnk* | sunos | sunos[34]*\ | hpux* | unos* | osf* | luna* | dgux* | auroraux* | solaris* \ | sym* | kopensolaris* | plan9* \ | amigaos* | amigados* | msdos* | newsos* | unicos* | aof* \ | aos* | aros* | cloudabi* | sortix* | twizzler* \ | nindy* | vxsim* | vxworks* | ebmon* | hms* | mvs* \ | clix* | riscos* | uniplus* | iris* | isc* | rtu* | xenix* \ | knetbsd* | mirbsd* | netbsd* \ | bitrig* | openbsd* | solidbsd* | libertybsd* | os108* \ | ekkobsd* | kfreebsd* | freebsd* | riscix* | lynxos* \ | bosx* | nextstep* | cxux* | aout* | elf* | oabi* \ | ptx* | coff* | ecoff* | winnt* | domain* | vsta* \ | udi* | eabi* | lites* | ieee* | go32* | aux* | hcos* \ | chorusrdb* | cegcc* | glidix* \ | cygwin* | msys* | pe* | moss* | proelf* | rtems* \ | midipix* | mingw32* | mingw64* | linux-gnu* | linux-android* \ | linux-newlib* | linux-musl* | linux-uclibc* \ | uxpv* | beos* | mpeix* | udk* | moxiebox* \ | interix* | uwin* | mks* | rhapsody* | darwin* \ | openstep* | oskit* | conix* | pw32* | nonstopux* \ | storm-chaos* | tops10* | tenex* | tops20* | its* \ | os2* | vos* | palmos* | uclinux* | nucleus* \ | morphos* | superux* | rtmk* | windiss* \ | powermax* | dnix* | nx6 | nx7 | sei* | dragonfly* \ | skyos* | haiku* | rdos* | toppers* | drops* | es* \ | onefs* | tirtos* | phoenix* | fuchsia* | redox* | bme* \ | midnightbsd* | amdhsa* | unleashed* | emscripten* | wasi* \ | nsk* | powerunix) # Remember, each alternative MUST END IN *, to match a version number. ;; qnx*) case $cpu in x86 | i*86) ;; *) os=nto-$os ;; esac ;; hiux*) os=hiuxwe2 ;; nto-qnx*) ;; nto*) os=`echo $os | sed -e 's|nto|nto-qnx|'` ;; sim | xray | os68k* | v88r* \ | windows* | osx | abug | netware* | os9* \ | macos* | mpw* | magic* | mmixware* | mon960* | lnews*) ;; linux-dietlibc) os=linux-dietlibc ;; linux*) os=`echo $os | sed -e 's|linux|linux-gnu|'` ;; lynx*178) os=lynxos178 ;; lynx*5) os=lynxos5 ;; lynx*) os=lynxos ;; mac*) os=`echo "$os" | sed -e 's|mac|macos|'` ;; opened*) os=openedition ;; os400*) os=os400 ;; sunos5*) os=`echo "$os" | sed -e 's|sunos5|solaris2|'` ;; sunos6*) os=`echo "$os" | sed -e 's|sunos6|solaris3|'` ;; wince*) os=wince ;; utek*) os=bsd ;; dynix*) os=bsd ;; acis*) os=aos ;; atheos*) os=atheos ;; syllable*) os=syllable ;; 386bsd) os=bsd ;; ctix* | uts*) os=sysv ;; nova*) os=rtmk-nova ;; ns2) os=nextstep2 ;; # Preserve the version number of sinix5. sinix5.*) os=`echo $os | sed -e 's|sinix|sysv|'` ;; sinix*) os=sysv4 ;; tpf*) os=tpf ;; triton*) os=sysv3 ;; oss*) os=sysv3 ;; svr4*) os=sysv4 ;; svr3) os=sysv3 ;; sysvr4) os=sysv4 ;; # This must come after sysvr4. sysv*) ;; ose*) os=ose ;; *mint | mint[0-9]* | *MiNT | MiNT[0-9]*) os=mint ;; zvmoe) os=zvmoe ;; dicos*) os=dicos ;; pikeos*) # Until real need of OS specific support for # particular features comes up, bare metal # configurations are quite functional. case $cpu in arm*) os=eabi ;; *) os=elf ;; esac ;; nacl*) ;; ios) ;; none) ;; *-eabi) ;; *) echo Invalid configuration \`"$1"\': system \`"$os"\' not recognized 1>&2 exit 1 ;; esac else # Here we handle the default operating systems that come with various machines. # The value should be what the vendor currently ships out the door with their # machine or put another way, the most popular os provided with the machine. # Note that if you're going to try to match "-MANUFACTURER" here (say, # "-sun"), then you have to tell the case statement up towards the top # that MANUFACTURER isn't an operating system. Otherwise, code above # will signal an error saying that MANUFACTURER isn't an operating # system, and we'll never get to this point. case $cpu-$vendor in score-*) os=elf ;; spu-*) os=elf ;; *-acorn) os=riscix1.2 ;; arm*-rebel) os=linux ;; arm*-semi) os=aout ;; c4x-* | tic4x-*) os=coff ;; c8051-*) os=elf ;; clipper-intergraph) os=clix ;; hexagon-*) os=elf ;; tic54x-*) os=coff ;; tic55x-*) os=coff ;; tic6x-*) os=coff ;; # This must come before the *-dec entry. pdp10-*) os=tops20 ;; pdp11-*) os=none ;; *-dec | vax-*) os=ultrix4.2 ;; m68*-apollo) os=domain ;; i386-sun) os=sunos4.0.2 ;; m68000-sun) os=sunos3 ;; m68*-cisco) os=aout ;; mep-*) os=elf ;; mips*-cisco) os=elf ;; mips*-*) os=elf ;; or32-*) os=coff ;; *-tti) # must be before sparc entry or we get the wrong os. os=sysv3 ;; sparc-* | *-sun) os=sunos4.1.1 ;; pru-*) os=elf ;; *-be) os=beos ;; *-ibm) os=aix ;; *-knuth) os=mmixware ;; *-wec) os=proelf ;; *-winbond) os=proelf ;; *-oki) os=proelf ;; *-hp) os=hpux ;; *-hitachi) os=hiux ;; i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent) os=sysv ;; *-cbm) os=amigaos ;; *-dg) os=dgux ;; *-dolphin) os=sysv3 ;; m68k-ccur) os=rtu ;; m88k-omron*) os=luna ;; *-next) os=nextstep ;; *-sequent) os=ptx ;; *-crds) os=unos ;; *-ns) os=genix ;; i370-*) os=mvs ;; *-gould) os=sysv ;; *-highlevel) os=bsd ;; *-encore) os=bsd ;; *-sgi) os=irix ;; *-siemens) os=sysv4 ;; *-masscomp) os=rtu ;; f30[01]-fujitsu | f700-fujitsu) os=uxpv ;; *-rom68k) os=coff ;; *-*bug) os=coff ;; *-apple) os=macos ;; *-atari*) os=mint ;; *-wrs) os=vxworks ;; *) os=none ;; esac fi # 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 $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 ;; 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-$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.8/configure000077500000000000000000004533361402074030700167770ustar00rootroot00000000000000#! /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 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 ' 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 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 { $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 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 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="$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.8/configure.in000066400000000000000000000162351402074030700173720ustar00rootroot00000000000000dnl ProFTPD - mod_proxy dnl Copyright (c) 2012-2020 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 and --with-libraries 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" ]) AC_HEADER_STDC AC_CHECK_HEADERS(sqlite3.h stdlib.h unistd.h limits.h fcntl.h sys/sysctl.h sys/sysinfo.h) 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 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="$ac_dns_libs" 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.8/doc/000077500000000000000000000000001402074030700156175ustar00rootroot00000000000000proftpd-mod_proxy-0.8/doc/NOTES000066400000000000000000000150601402074030700164340ustar00rootroot00000000000000 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.8/doc/NOTES.dirlist-parsing000066400000000000000000000013761402074030700215530ustar00rootroot00000000000000 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.8/doc/NOTES.dns-srv000066400000000000000000000267231402074030700200370ustar00rootroot00000000000000 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.8/doc/NOTES.forward-proxy000066400000000000000000000052551402074030700212630ustar00rootroot00000000000000 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.8/doc/NOTES.health-checks000066400000000000000000000075071402074030700211450ustar00rootroot00000000000000 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: TCP connect errors TLS handshake errors FTP connect/login errors (ignoring bad credentials!) Specifically, we want to track errors that indicate that that server is unavailable for service. Thus probably NOT TLS handshake errors, or FTP data transfer errors. That leaves TCP connect errors, and FTP non-200 responses on connect. 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 proftpd-mod_proxy-0.8/doc/NOTES.history000066400000000000000000000010671402074030700201360ustar00rootroot00000000000000 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.8/doc/NOTES.load-balancing000066400000000000000000000076311402074030700212730ustar00rootroot00000000000000 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.8/doc/NOTES.protocol-translation000066400000000000000000000003011402074030700226200ustar00rootroot00000000000000 FTP to SFTP: http://www.bitvise.com/ftp-bridge http://www.enterprisedt.com/products/completeftp/doc/guide/html/gateway.html mindterm source (Java, FTPOverSFTP bridge code for mapping) proftpd-mod_proxy-0.8/doc/NOTES.proxy-datatransfer-policy000066400000000000000000000056621402074030700235740ustar00rootroot00000000000000 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.8/doc/NOTES.reverse-proxy000066400000000000000000000074351402074030700212740ustar00rootroot00000000000000Selection 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.8/doc/NOTES.selection000066400000000000000000000013101402074030700204110ustar00rootroot00000000000000 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.8/doc/NOTES.selection-roundrobin000066400000000000000000000156431402074030700226060ustar00rootroot00000000000000 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.8/doc/NOTES.tests000066400000000000000000000004551402074030700175770ustar00rootroot00000000000000 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.8/doc/NOTES.tls000066400000000000000000000001051402074030700172270ustar00rootroot00000000000000 ProxyTLSOptions IgnoreFEAT UseCCC Passphrase provider support? proftpd-mod_proxy-0.8/doc/NOTES.todo000066400000000000000000000002201402074030700173700ustar00rootroot00000000000000 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.8/doc/REFERENCES000066400000000000000000000065261402074030700171740ustar00rootroot00000000000000 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.8/include/000077500000000000000000000000001402074030700164755ustar00rootroot00000000000000proftpd-mod_proxy-0.8/include/proxy/000077500000000000000000000000001402074030700176565ustar00rootroot00000000000000proftpd-mod_proxy-0.8/include/proxy/conn.h000066400000000000000000000047261402074030700207750ustar00rootroot00000000000000/* * 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.8/include/proxy/db.h000066400000000000000000000060431402074030700204170ustar00rootroot00000000000000/* * ProFTPD - mod_proxy database API * Copyright (c) 2015-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_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); #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_NULL 4 /* 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.8/include/proxy/dns.h000066400000000000000000000035261402074030700206210ustar00rootroot00000000000000/* * 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.8/include/proxy/forward.h000066400000000000000000000043621402074030700215000ustar00rootroot00000000000000/* * ProFTPD - mod_proxy forward-proxy 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_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 /* 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.8/include/proxy/ftp/000077500000000000000000000000001402074030700204475ustar00rootroot00000000000000proftpd-mod_proxy-0.8/include/proxy/ftp/conn.h000066400000000000000000000027341402074030700215630ustar00rootroot00000000000000/* * 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.8/include/proxy/ftp/ctrl.h000066400000000000000000000033121402074030700215630ustar00rootroot00000000000000/* * ProFTPD - mod_proxy FTP control 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_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 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.8/include/proxy/ftp/data.h000066400000000000000000000025011402074030700215270ustar00rootroot00000000000000/* * 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.8/include/proxy/ftp/dirlist.h000066400000000000000000000047201402074030700222750ustar00rootroot00000000000000/* * 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.8/include/proxy/ftp/facts.h000066400000000000000000000034151402074030700217230ustar00rootroot00000000000000/* * 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.8/include/proxy/ftp/msg.h000066400000000000000000000036301402074030700214100ustar00rootroot00000000000000/* * ProFTPD - mod_proxy FTP message 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_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.8/include/proxy/ftp/sess.h000066400000000000000000000027741402074030700216070ustar00rootroot00000000000000/* * ProFTPD - mod_proxy FTP session 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_FTP_SESS_H #define MOD_PROXY_FTP_SESS_H #include "mod_proxy.h" #include "proxy/session.h" 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.8/include/proxy/ftp/xfer.h000066400000000000000000000026151402074030700215700ustar00rootroot00000000000000/* * 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.8/include/proxy/inet.h000066400000000000000000000032201402074030700207630ustar00rootroot00000000000000/* * 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.8/include/proxy/netio.h000066400000000000000000000046431402074030700211540ustar00rootroot00000000000000/* * 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.8/include/proxy/random.h000066400000000000000000000024641402074030700213150ustar00rootroot00000000000000/* * 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.8/include/proxy/reverse.h000066400000000000000000000076121402074030700215100ustar00rootroot00000000000000/* * ProFTPD - mod_proxy reverse-proxy 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_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 /* Return the policy ID for the given string, or -1 if the given policy * is not recognized/supported. */ int proxy_reverse_connect_get_policy(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); /* 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.8/include/proxy/reverse/000077500000000000000000000000001402074030700213315ustar00rootroot00000000000000proftpd-mod_proxy-0.8/include/proxy/reverse/db.h000066400000000000000000000024461402074030700220750ustar00rootroot00000000000000/* * 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.8/include/proxy/reverse/redis.h000066400000000000000000000025251402074030700226140ustar00rootroot00000000000000/* * 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.8/include/proxy/session.h000066400000000000000000000053571402074030700215240ustar00rootroot00000000000000/* * ProFTPD - mod_proxy sessions * 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_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; /* Features supported by backend server */ pr_table_t *backend_features; /* 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_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.8/include/proxy/str.h000066400000000000000000000023031402074030700206350ustar00rootroot00000000000000/* * 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.8/include/proxy/tls.h000066400000000000000000000100031402074030700206230ustar00rootroot00000000000000/* * ProFTPD - mod_proxy TLS API * Copyright (c) 2015-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_TLS_H #define MOD_PROXY_TLS_H #include "mod_proxy.h" #ifdef PR_USE_OPENSSL # include # include # include # include # include # include # include # include # include # if OPENSSL_VERSION_NUMBER > 0x000907000L # include # 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_IMPLICIT_FTPS_PORT 990 /* ProxyTLSOptions values */ #define PROXY_TLS_OPT_ENABLE_DIAGS 0x0001 #define PROXY_TLS_OPT_NO_SESSION_CACHE 0x0002 #define PROXY_TLS_OPT_NO_SESSION_TICKETS 0x0004 #define PROXY_TLS_OPT_ALLOW_WEAK_SECURITY 0x0008 /* 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, 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); /* 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.8/include/proxy/tls/000077500000000000000000000000001402074030700204605ustar00rootroot00000000000000proftpd-mod_proxy-0.8/include/proxy/tls/db.h000066400000000000000000000024121402074030700212150ustar00rootroot00000000000000/* * 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.8/include/proxy/tls/redis.h000066400000000000000000000024231402074030700217400ustar00rootroot00000000000000/* * 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.8/include/proxy/uri.h000066400000000000000000000024071402074030700206310ustar00rootroot00000000000000/* * 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.8/install-sh000077500000000000000000000324641402074030700170670ustar00rootroot00000000000000#!/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.8/lib/000077500000000000000000000000001402074030700156205ustar00rootroot00000000000000proftpd-mod_proxy-0.8/lib/proxy/000077500000000000000000000000001402074030700170015ustar00rootroot00000000000000proftpd-mod_proxy-0.8/lib/proxy/conn.c000066400000000000000000000755771402074030700201270ustar00rootroot00000000000000/* * ProFTPD - mod_proxy conn implementation * 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. */ #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"; 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); (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; } 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; 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 (local_addr == NULL) { local_addr = session.c->local_addr; } } bind_addr = proxy_sess->src_addr; if (bind_addr == NULL) { bind_addr = local_addr; } /* 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 (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; #ifdef 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); if (server_conn == NULL) { int xerrno = errno; (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) { int xerrno = errno; (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "error starting connect to %s#%u: %s", remote_ipstr, remote_port, strerror(xerrno)); 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) { int 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. */ int 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 */ int 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) { int 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) { int 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; } 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_hdr[8]; pool *sub_pool = NULL; char *proto; const pr_netaddr_t *src_addr = NULL, *dst_addr = NULL; if (p == NULL || conn == NULL) { errno = EINVAL; return -1; } v2_hdr[0].iov_base = (void *) proxy_protocol_v2_sig; v2_hdr[0].iov_len = PROXY_PROTOCOL_V2_SIGLEN; /* PROXY protocol v2 + PROXY command */ ver_cmd = (0x20|0x01); v2_hdr[1].iov_base = (void *) &ver_cmd; v2_hdr[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_hdr[4].iov_base = (void *) &src_ipv4; v2_hdr[4].iov_len = sizeof(src_ipv4); saddr = (struct sockaddr_in *) pr_netaddr_get_sockaddr(dst_addr); dst_ipv4 = saddr->sin_addr.s_addr; v2_hdr[5].iov_base = (void *) &dst_ipv4; v2_hdr[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_hdr[4].iov_base = (void *) &src_ipv6; v2_hdr[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_hdr[5].iov_base = (void *) &dst_ipv6; v2_hdr[5].iov_len = sizeof(dst_ipv6); /* Quell compiler warnings about unused variables. */ (void) src_ipv4; (void) dst_ipv4; } v2_hdr[2].iov_base = (void *) &trans_fam; v2_hdr[2].iov_len = sizeof(trans_fam); v2_len = htons(v2_len); v2_hdr[3].iov_base = (void *) &v2_len; v2_hdr[3].iov_len = sizeof(v2_len); src_port = htons(session.c->remote_port); v2_hdr[6].iov_base = (void *) &src_port; v2_hdr[6].iov_len = sizeof(src_port); dst_port = htons(session.c->local_port); v2_hdr[7].iov_base = (void *) &dst_port; v2_hdr[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_hdr, 8); xerrno = errno; if (sub_pool != NULL) { destroy_pool(sub_pool); } errno = xerrno; return res; } proftpd-mod_proxy-0.8/lib/proxy/db.c000066400000000000000000000657421402074030700175500ustar00rootroot00000000000000/* * ProFTPD - mod_proxy database implementation * Copyright (c) 2015-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/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 4 #define PROXY_DB_SQLITE_MAX_RETRY_DELAY_MS 250 #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); return retry; } #ifdef 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 */ #ifdef 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; const char *expanded_sql = NULL; pstmt = ptr; ns = *((int64_t *) ptr_data); 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); } break; } case SQLITE_TRACE_ROW: { sqlite3_stmt *pstmt; const char *expanded_sql = NULL; pstmt = ptr; 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); } 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; } if (ptr != NULL) { sqlite3_free(ptr); } current_schema = NULL; 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_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) { 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, -1, 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_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. */ #ifdef 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) { *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; pr_signals_handle(); /* 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; } 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; #ifdef 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; } if (pr_trace_get_level(trace_channel) >= PROXY_DB_SQLITE_TRACE_LEVEL) { sqlite3_busy_handler(db, db_busy, (void *) schema_name); #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); 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); if (res < 0) { return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_INT, (void *) &schema_version); 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; } #ifdef SQLITE_CONFIG_LOG /* Register an error logging callback with SQLite3. */ sqlite3_config(SQLITE_CONFIG_LOG, db_err, NULL); #endif /* SQLITE_CONFIG_LOG */ #ifdef 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.8/lib/proxy/dns.c000066400000000000000000000427111402074030700177360ustar00rootroot00000000000000/* * ProFTPD - mod_proxy DNS resolution * 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/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 * 2]; 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)); 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 * 2]; 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)); 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.8/lib/proxy/forward.c000066400000000000000000000616151402074030700206220ustar00rootroot00000000000000/* * ProFTPD - mod_proxy forward proxy 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/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 static const char *trace_channel = "proxy.forward"; int proxy_forward_use_proxy_auth(void) { if (proxy_method == PROXY_FORWARD_METHOD_USER_NO_PROXY_AUTH) { return FALSE; } 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 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 theses default 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_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) { const struct proxy_conn *pconn = NULL; const pr_netaddr_t *remote_addr = NULL; array_header *other_addrs = NULL; res = forward_cmd_parse_dst(cmd->tmp_pool, cmd->arg, &user, &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_host(pconn), strerror(EPERM)); return 1; } proxy_sess->dst_addr = remote_addr; proxy_sess->other_addrs = other_addrs; proxy_sess->dst_pconn = pconn; /* 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; } 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_host(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_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: 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; } errno = ENOENT; return -1; } proftpd-mod_proxy-0.8/lib/proxy/ftp/000077500000000000000000000000001402074030700175725ustar00rootroot00000000000000proftpd-mod_proxy-0.8/lib/proxy/ftp/conn.c000066400000000000000000000235071402074030700207020ustar00rootroot00000000000000/* * ProFTPD - mod_proxy FTP connection 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/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 res, reverse_dns; if (p == NULL || remote_addr == NULL) { errno = EINVAL; return NULL; } conn = pr_inet_create_conn(session.pool, -1, bind_addr, INPORT_ANY, TRUE); 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) { 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) { int 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) { 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) { 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) { int xerrno = errno; if (!frontend_data) { 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) { 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.8/lib/proxy/ftp/ctrl.c000066400000000000000000000373711402074030700207150ustar00rootroot00000000000000/* * ProFTPD - mod_proxy FTP control 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/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 multiline = 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 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 multiline 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 multiline response -- and the * character after the numerics MUST be a space. * * Unfortunately, some FTP servers (IIS, for instance) will use multiline * responses whose continuation lines do NOT start with the mandated * space (as for a multiline STAT response on a file, for example). Sigh. */ if (resp == NULL) { /* First line of a possibly multiline 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 multiline 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] == '-') { multiline = 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 * multiline 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 multiline 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 multiline response uses the same * response code. */ if (strncmp(resp->num, buf, 3) != 0) { pr_trace_msg(trace_channel, 1, "invalid multiline 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 (multiline) { *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 (multiline) { *ptr = c; /* This the last line of a multiline 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, multiline ? "" : " ", 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", 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.8/lib/proxy/ftp/data.c000066400000000000000000000111701402074030700206470ustar00rootroot00000000000000/* * 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.8/lib/proxy/ftp/dirlist.c000066400000000000000000001026041402074030700214130ustar00rootroot00000000000000/* * ProFTPD - mod_proxy FTP dirlist 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/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. */ ctx->input_textsz = (PR_TUNABLE_PATH_MAX * 2) + 128; 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); 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; 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; } pdf->tm = pcalloc(p, sizeof(struct tm)); buflen = 17; buf = pstrndup(p, text, buflen); pr_trace_msg(trace_channel, 19, "parsing Windows-style timestamp: '%*s'", (int) buflen, buf); if (strptime(buf, "%m-%d-%y %I:%M%p", 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? */ 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.8/lib/proxy/ftp/facts.c000066400000000000000000000066021402074030700210420ustar00rootroot00000000000000/* * 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.8/lib/proxy/ftp/msg.c000066400000000000000000000305071402074030700205310ustar00rootroot00000000000000/* * 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 pool *ftp_msg_pool = NULL; 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. */ for (ptr = msg; *ptr; ptr++) { 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 */ /* Use a specific memory pool for these objects, since they cannot be * destroyed (they have no pools of their own), so they will just clutter up * the session pool. */ if (ftp_msg_pool != NULL) { destroy_pool(ftp_msg_pool); } ftp_msg_pool = make_sub_pool(proxy_pool); pr_pool_tag(ftp_msg_pool, "Proxy FTP Message Pool"); addr = (pr_netaddr_t *) pr_netaddr_get_addr(ftp_msg_pool, 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; } /* Use a specific memory pool for these objects, since they cannot be * destroyed (they have no pools of their own), so they will just clutter up * the session pool. */ if (ftp_msg_pool != NULL) { destroy_pool(ftp_msg_pool); } ftp_msg_pool = make_sub_pool(proxy_pool); pr_pool_tag(ftp_msg_pool, "Proxy FTP Message Pool"); res = pr_netaddr_dup(ftp_msg_pool, &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.8/lib/proxy/ftp/sess.c000066400000000000000000000344261402074030700207240ustar00rootroot00000000000000/* * ProFTPD - mod_proxy FTP session routines * 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/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 res, xerrno = 0; cmd_rec *cmd; pr_response_t *resp; unsigned int resp_nlines = 0; char *feats, *token; size_t 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; } resp = proxy_ftp_ctrl_recv_resp(tmp_pool, proxy_sess->backend_ctrl_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)); 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 = (char *) 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(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; 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(); 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]); } destroy_pool(tmp_pool); return 0; } int proxy_ftp_sess_send_pbsz_prot(pool *p, const struct proxy_session *proxy_sess) { int use_tls; 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; } if (pr_table_get(proxy_sess->backend_features, C_PBSZ, NULL) != NULL) { int xerrno; pool *tmp_pool; cmd_rec *cmd; pr_response_t *resp; unsigned int resp_nlines = 0; tmp_pool = make_sub_pool(p); cmd = pr_cmd_alloc(tmp_pool, 2, C_PBSZ, "0"); cmd->arg = pstrdup(tmp_pool, "0"); 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); } if (tls_xfer_prot_policy != 0 && pr_table_get(proxy_sess->backend_features, C_PROT, NULL) != NULL) { int xerrno; pool *tmp_pool; cmd_rec *cmd; const char *prot; pr_response_t *resp; unsigned int resp_nlines = 0; tmp_pool = make_sub_pool(p); switch (tls_xfer_prot_policy) { case -1: prot = "C"; break; case 1: default: prot = "P"; break; } cmd = pr_cmd_alloc(tmp_pool, 2, C_PROT, prot); cmd->arg = pstrdup(tmp_pool, prot); 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; } proftpd-mod_proxy-0.8/lib/proxy/ftp/xfer.c000066400000000000000000000451731402074030700207140ustar00rootroot00000000000000/* * ProFTPD - mod_proxy FTP data transfer 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/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 = TRUE; 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(cmd->tmp_pool, resp->msg, remote_family); break; } case PR_CMD_EPSV_ID: remote_addr = proxy_ftp_msg_parse_ext_addr(cmd->tmp_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)); /* Make sure that 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) { (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.8/lib/proxy/inet.c000066400000000000000000000133351402074030700201110ustar00rootroot00000000000000/* * ProFTPD - mod_proxy Inet implementation * Copyright (c) 2015-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/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) { pr_netio_t *in_netio = NULL, *out_netio = NULL; int instrm_type = -1, outstrm_type = -1; if (conn->instrm != NULL) { instrm_type = conn->instrm->strm_type; in_netio = proxy_netio_unset(instrm_type, "inet_close"); } if (conn->outstrm != NULL) { outstrm_type = conn->outstrm->strm_type; /* Note: it is IMPORTANT that we only call proxy_netio_unset() IFF * the stream types are different. Otherwise, we risk popping too * many NetIOs off the stack, as it were. */ if (outstrm_type != instrm_type) { out_netio = proxy_netio_unset(outstrm_type, "inet_close"); } } /* 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; } if (in_netio != NULL) { proxy_netio_set(instrm_type, in_netio); } if (out_netio != NULL) { proxy_netio_set(outstrm_type, out_netio); } } } 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.8/lib/proxy/netio.c000066400000000000000000000207311402074030700202660ustar00rootroot00000000000000/* * ProFTPD - mod_proxy NetIO implementation * 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. */ #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 (nstrm != NULL) { 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.8/lib/proxy/random.c000066400000000000000000000036341402074030700204330ustar00rootroot00000000000000/* * 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.8/lib/proxy/reverse.c000066400000000000000000001470331402074030700206300ustar00rootroot00000000000000/* * ProFTPD - mod_proxy reverse proxy 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 "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; 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; 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; } } 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)); } /* 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); } 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; } static int reverse_connect(pool *p, struct proxy_session *proxy_sess) { register int i; int res; for (i = 0; i < reverse_retry_count; i++) { pr_signals_handle(); res = reverse_try_connect(p, proxy_sess, NULL); 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 = reverse_connect(p, proxy_sess); 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_connect_get_policy(const char *policy) { if (policy == NULL) { errno = EINVAL; return -1; } if (strncasecmp(policy, "Random", 7) == 0) { return PROXY_REVERSE_CONNECT_POLICY_RANDOM; } else if (strncasecmp(policy, "RoundRobin", 11) == 0) { return PROXY_REVERSE_CONNECT_POLICY_ROUND_ROBIN; } else if (strncasecmp(policy, "Shuffle", 8) == 0) { return PROXY_REVERSE_CONNECT_POLICY_SHUFFLE; } else if (strncasecmp(policy, "LeastConns", 11) == 0) { return PROXY_REVERSE_CONNECT_POLICY_LEAST_CONNS; } else if (strncasecmp(policy, "PerUser", 8) == 0) { return PROXY_REVERSE_CONNECT_POLICY_PER_USER; } else if (strncasecmp(policy, "PerGroup", 9) == 0) { return PROXY_REVERSE_CONNECT_POLICY_PER_GROUP; } else if (strncasecmp(policy, "PerHost", 8) == 0) { return PROXY_REVERSE_CONNECT_POLICY_PER_HOST; } else if (strncasecmp(policy, "LeastResponseTime", 18) == 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) { register int i; int connected = FALSE, xerrno = 0; for (i = 0; i < reverse_retry_count; i++) { pr_signals_handle(); res = reverse_try_connect(proxy_pool, proxy_sess, cmd->arg); if (res == 0) { connected = TRUE; break; } xerrno = errno; } pr_response_block(FALSE); if (connected == FALSE) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "ProxyRetryCount %d reached with no successful connection, failing", reverse_retry_count); *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) { pr_log_debug(DEBUG2, "user '%s' authenticated by %s", user, session.auth_mech); } } if (!(proxy_sess_state & PROXY_SESS_STATE_CONNECTED)) { register int i; 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; } } } for (i = 0; i < reverse_retry_count; i++) { pr_signals_handle(); res = reverse_try_connect(proxy_pool, proxy_sess, connect_name); if (res == 0) { connected = TRUE; break; } xerrno = errno; } pr_response_block(FALSE); if (connected == FALSE) { (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "ProxyRetryCount %d reached with no successful connection, failing", reverse_retry_count); *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.8/lib/proxy/reverse/000077500000000000000000000000001402074030700204545ustar00rootroot00000000000000proftpd-mod_proxy-0.8/lib/proxy/reverse/db.c000066400000000000000000001545161402074030700212210ustar00rootroot00000000000000/* * 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 "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)); if (res < 0) { return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT, (void *) s->ServerName); 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); if (res < 0) { return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT, (void *) backend_uri); if (res < 0) { return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 3, PROXY_DB_BIND_TYPE_INT, (void *) &backend_id); 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); if (res < 0) { return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_INT, (void *) &backend_id); 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); 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); if (res < 0) { return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_INT, (void *) &backend_id); 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); if (res < 0) { return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_INT, (void *) &vhost_id); 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); if (res < 0) { return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_INT, (void *) &backend_id); 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); 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); 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); 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); if (res < 0) { return NULL; } res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT, (void *) user); 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); if (res < 0) { return NULL; } res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT, (void *) user); 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); 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); if (res < 0) { return NULL; } res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT, (void *) group); 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); if (res < 0) { return NULL; } res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT, (void *) group); 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); 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); 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); 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); if (res < 0) { return NULL; } res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT, (void *) ip); 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); 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 && default_backends != NULL && db_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: 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); 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); if (res < 0) { return -1; } idx++; } res = proxy_db_bind_stmt(p, dbh, stmt, idx, PROXY_DB_BIND_TYPE_INT, (void *) &vhost_id); if (res < 0) { return -1; } idx++; res = proxy_db_bind_stmt(p, dbh, stmt, idx, PROXY_DB_BIND_TYPE_INT, (void *) &backend_id); 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.8/lib/proxy/reverse/redis.c000066400000000000000000000773301402074030700217400ustar00rootroot00000000000000/* * 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.8/lib/proxy/session.c000066400000000000000000000205161402074030700206340ustar00rootroot00000000000000/* * ProFTPD - mod_proxy session 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/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; /* 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; 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_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 (strncasecmp(xferlog, "none", 5) == 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.disable_id_switching = TRUE; 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.8/lib/proxy/str.c000066400000000000000000000034661402074030700177660ustar00rootroot00000000000000/* * 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.8/lib/proxy/tls.c000066400000000000000000004221201402074030700177500ustar00rootroot00000000000000/* * ProFTPD - mod_proxy TLS implementation * Copyright (c) 2015-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/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 #ifdef 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_required_on_frontend_data = FALSE; 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 *trace_channel = "proxy.tls"; 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 #ifdef 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, 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: 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); (*adaptive_bytes_written_count) += count; wbio = SSL_get_wbio(ssl); 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; #ifdef 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; #ifdef 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); 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); if (tls_data_netio != NULL && tls_required_on_frontend_data == FALSE) { pr_netio_t *using_netio = NULL; if (proxy_netio_using(PR_NETIO_STRM_DATA, &using_netio) == 0) { if (using_netio == tls_data_netio) { proxy_netio_use(PR_NETIO_STRM_DATA, 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); /* 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 %) 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. */ #ifdef 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; #ifdef 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) { #ifdef 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) { #ifdef PR_USE_OPENSSL return tls_engine; #else return PROXY_TLS_ENGINE_OFF; #endif /* PR_USE_OPENSSL */ } int proxy_tls_init(pool *p, const char *tables_path, int flags) { #ifdef 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_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; } #ifdef 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; } #ifdef 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); # ifdef SSL_OP_NO_TLSv1_1 disabled_protocols |= SSL_OP_NO_TLSv1_1; # endif # ifdef SSL_OP_NO_TLSv1_2 disabled_protocols |= SSL_OP_NO_TLSv1_2; # endif # ifdef 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 */ 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) { # ifdef 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; # ifdef 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); #ifdef 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); 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); 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); 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); 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: # ifdef 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; } } # ifdef 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, int flags) { #ifdef 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; 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 } /* We look up the TLSEngine setting here, to know how to properly deal * with frontend data connections, i.e. do we need to listen for TLS * data connections on the frontend or not. */ c = find_config(main_server->conf, CONF_PARAM, "TLSEngine", FALSE); if (c != NULL) { int engine; engine = *((int *) c->argv[0]); if (engine == TRUE) { tls_required_on_frontend_data = TRUE; } } 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; } #ifdef 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.8/lib/proxy/tls/000077500000000000000000000000001402074030700176035ustar00rootroot00000000000000proftpd-mod_proxy-0.8/lib/proxy/tls/db.c000066400000000000000000000324241402074030700203410ustar00rootroot00000000000000/* * ProFTPD - mod_proxy TLS Database 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 "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); 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); 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); 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); if (res < 0) { return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT, (void *) key); 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); if (res < 0) { return NULL; } res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT, (void *) key); 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)); if (res < 0) { return -1; } res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT, (void *) s->ServerName); 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.8/lib/proxy/tls/redis.c000066400000000000000000000227251402074030700210650ustar00rootroot00000000000000/* * 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.8/lib/proxy/uri.c000066400000000000000000000240441402074030700177500ustar00rootroot00000000000000/* * 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.8/mod_proxy.c000066400000000000000000004521631402074030700172510ustar00rootroot00000000000000/* * ProFTPD - mod_proxy * 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. * * -----DO NOT EDIT BELOW THIS LINE----- * $Archive: mod_proxy.a $ * $Libraries: -lsqlite3$ */ #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/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/xfer.h" /* 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 = 1; 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 void proxy_log_xfer(cmd_rec *cmd, char abort_flag) { struct timeval end_time; char direction, *path = NULL; switch (cmd->cmd_id) { case PR_CMD_APPE_ID: case PR_CMD_STOR_ID: case PR_CMD_STOU_ID: direction = 'i'; break; case PR_CMD_RETR_ID: direction = 'o'; break; default: pr_trace_msg(trace_channel, 3, "unable to write TransferLog for non-transfer command '%s'", (char *) cmd->argv[0]); return; } memset(&end_time, '\0', sizeof(end_time)); if (session.xfer.start_time.tv_sec != 0) { gettimeofday(&end_time, NULL); end_time.tv_sec -= session.xfer.start_time.tv_sec; if (end_time.tv_usec >= session.xfer.start_time.tv_usec) { end_time.tv_usec -= session.xfer.start_time.tv_usec; } else { end_time.tv_usec = 1000000L - (session.xfer.start_time.tv_usec - end_time.tv_usec); end_time.tv_sec--; } } path = cmd->arg; /* XXX Are mod_proxy and mod_xfer both writing this TransferLog entry? */ xferlog_write(end_time.tv_sec, pr_netaddr_get_sess_remote_name(), session.xfer.total_bytes, path, (session.sf_flags & SF_ASCII ? 'a' : 'b'), direction, 'r', session.user, abort_flag, "_"); } 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 (strncasecmp(xferlog, "none", 5) == 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 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; const char *ds_name; void *ds_data; size_t ds_datasz; 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; 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], "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 { 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; 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(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); } 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: 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) { #ifdef 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]; } 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 { 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; 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 */ 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 { 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) { #ifdef 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; 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 = 1; } else if (strcasecmp(policy, "client") == 0) { require_prot = 0; } else if (strcasecmp(policy, "clear") == 0) { require_prot = -1; } else { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unsupported TLSTransferProtectionPolicy: ", 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) { /* 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_conns(struct proxy_session *proxy_sess, cmd_rec *cmd, conn_t **frontend, conn_t **backend) { int res, xerrno = 0; conn_t *frontend_conn = NULL, *backend_conn = NULL; 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; } /* XXX Should handle EPSV_ALL here, too. */ if (proxy_sess->backend_sess_flags & SF_PASSIVE) { const pr_netaddr_t *bind_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; 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) { 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; #ifdef 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) { 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; } 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; } res = proxy_data_handle_resp(cmd->tmp_pool, proxy_sess, cmd); if (res < 0) { 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); } /* Now establish a data connection with the frontend client. */ 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; *backend = backend_conn; 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; if (data_eof == TRUE || xfer_ok == FALSE) { tv.tv_sec = proxy_sess->linger_timeout; } else { tv.tv_sec = 15; } tv.tv_usec = 0; pr_signals_handle(); 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) { /* 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); } pr_throttle_pause(bytes_transferred, TRUE); 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; pr_throttle_pause(bytes_transferred, FALSE); /* 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) && backend_ctrlfd >= 0 && FD_ISSET(backend_ctrlfd, &rfds)) { /* Some data arrived on the ctrl connection... */ 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); } pr_throttle_pause(bytes_transferred, TRUE); 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; unsigned short remote_port; unsigned char *allow_foreign_addr = NULL; 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(cmd->tmp_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. */ allow_foreign_addr = get_param_ptr(main_server->conf, "AllowForeignAddress", FALSE); if (allow_foreign_addr == NULL || *allow_foreign_addr == 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 = 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 = 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; unsigned char *allow_foreign_addr = NULL; 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(cmd->tmp_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. */ allow_foreign_addr = get_param_ptr(TOPLEVEL_CONF, "AllowForeignAddress", FALSE); if (allow_foreign_addr == NULL || *allow_foreign_addr == 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; /* 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) { 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) { 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 (strncmp(type, "A", 2) == 0 || (cmd->argc == 3 && strncmp(type, "L", 2) == 0 && strncmp(cmd->argv[2], "7", 2) == 0)) { /* TYPE A(SCII) or TYPE L 7. */ session.sf_flags |= SF_ASCII; } else if (strncmp(type, "I", 2) == 0 || (cmd->argc == 3 && strncmp(type, "L", 2) == 0 && strncmp(cmd->argv[2], "8", 2) == 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); /* 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; } 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; } 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; } 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; } 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); } if (MODRET_ISHANDLED(mr)) { proxy_log_xfer(cmd, 'c'); } 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: if (proxy_tls_xfer_prot_policy != 0) { 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_prot(cmd_rec *cmd) { if (proxy_engine == FALSE) { return PR_DECLINED(cmd); } if (proxy_tls_xfer_prot_policy != 0) { return PR_DECLINED(cmd); } 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); } 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_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) { proxy_forward_free(proxy_pool); proxy_reverse_free(proxy_pool); 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_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 = 1; 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; proxy_forward_free(proxy_pool); proxy_reverse_free(proxy_pool); 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)); } proxy_db_free(); destroy_pool(proxy_pool); proxy_pool = NULL; if (proxy_logfd >= 0) { (void) close(proxy_logfd); proxy_logfd = -1; } } 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. */ pr_response_block(FALSE); } 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. */ pr_response_block(FALSE); } 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. */ pr_response_block(FALSE); } /* 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 (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); } } } } 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); if (pr_table_add(session.notes, "mod_proxy.proxy-session", proxy_sess, sizeof(struct proxy_session)) < 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]); } /* 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_tls_sess_init(proxy_pool, 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 }, /* 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_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.8/mod_proxy.h.in000066400000000000000000000060331402074030700176520ustar00rootroot00000000000000/* * ProFTPD - mod_proxy * 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_H #define MOD_PROXY_H #include "conf.h" #include "privs.h" #include #if HAVE_SYS_MMAN_H # include #endif /* 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.8" /* 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 /* 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 #ifndef PROXY_DEFAULT_RETRY_COUNT # define PROXY_DEFAULT_RETRY_COUNT 5 #endif #endif /* MOD_PROXY_H */ proftpd-mod_proxy-0.8/mod_proxy.html000066400000000000000000002163121402074030700177650ustar00rootroot00000000000000 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.

    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.


    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:

    • 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.

    • 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: None
    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-chosed 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.


    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
    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. 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.


    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.

    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

    SFTP/SCP Support
    The mod_proxy module only works for FTP/FTPS sessions; it does not currently support/handle SFTP/SCP sessions.

    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.forward
    • proxy.ftp.conn
    • proxy.ftp.ctrl
    • proxy.ftp.data
    • proxy.ftp.dirlist
    • 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.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

    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
    • SFTP/SCP 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.


    © Copyright 2015-2020 TJ Saunders
    All Rights Reserved

    proftpd-mod_proxy-0.8/t/000077500000000000000000000000001402074030700153155ustar00rootroot00000000000000proftpd-mod_proxy-0.8/t/Makefile.in000066400000000000000000000055161402074030700173710ustar00rootroot00000000000000CC=@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@ CPPFLAGS= $(ADDL_CPPFLAGS) -DHAVE_CONFIG_H $(DEFAULT_PATHS) $(PLATFORM) $(INCLUDES) LDFLAGS=-L$(top_srcdir)/lib @LIBDIRS@ EXEEXT=@EXEEXT@ TEST_API_DEPS=\ $(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/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) $(CFLAGS) -c $< api-tests$(EXEEXT): $(TEST_API_OBJS) $(TEST_API_DEPS) $(LIBTOOL) --mode=link --tag=CC $(CC) $(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.8/t/api/000077500000000000000000000000001402074030700160665ustar00rootroot00000000000000proftpd-mod_proxy-0.8/t/api/conn.c000066400000000000000000000650021402074030700171720ustar00rootroot00000000000000/* * ProFTPD - mod_proxy testsuite * 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. */ /* 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); fail_unless(pconn == NULL, "Failed to handle null arguments"); fail_unless(errno == EINVAL, "Failed to set errno to EINVAL"); mark_point(); url = "ftp://127.0.0.1:21"; pconn = proxy_conn_create(NULL, url, 0); fail_unless(pconn == NULL, "Failed to handle null pool argument"); fail_unless(errno == EINVAL, "Failed to set errno to EINVAL"); mark_point(); pconn = proxy_conn_create(p, NULL, 0); fail_unless(pconn == NULL, "Failed to handle null URL argument"); fail_unless(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); fail_unless(pconn == NULL, "Failed to handle unsupported protocol/scheme"); fail_unless(errno == EPERM, "Failed to set errno to EPERM"); mark_point(); url = "ftp://foo.bar.baz"; pconn = proxy_conn_create(p, url, 0); fail_unless(pconn == NULL, "Failed to handle unresolvable host"); fail_unless(errno == EINVAL, "Failed to set errno to EINVAL"); mark_point(); url = "ftp://127.0.0.1:21"; pconn = proxy_conn_create(p, url, 0); fail_if(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); fail_unless(pconn_addr == NULL, "Failed to handle null argument"); fail_unless(errno == EINVAL, "Failed to set errno to EINVAL"); url = "ftp://127.0.0.1:21"; pconn = proxy_conn_create(p, url, 0); fail_if(pconn == NULL, "Failed to create pconn for URL '%s' as expected", url); pconn_addr = proxy_conn_get_addr(pconn, &other_addrs); fail_if(pconn_addr == NULL, "Failed to get address for pconn"); ipstr = pr_netaddr_get_ipstr(pconn_addr); fail_unless(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); fail_unless(host == NULL, "Got host from null pconn unexpectedly"); fail_unless(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); fail_if(pconn == NULL, "Failed to create pconn for URL '%s' as expected", url); host = proxy_conn_get_host(pconn); fail_unless(host != NULL, "Failed to get host from conn: %s", strerror(errno)); expected = "127.0.0.1"; fail_unless(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); fail_unless(port < 0, "Got port from null pconn unexpectedly"); fail_unless(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); fail_if(pconn == NULL, "Failed to create pconn for URL '%s' as expected", url); port = proxy_conn_get_port(pconn); fail_unless(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); fail_unless(hostport == NULL, "Failed to handle null argument"); fail_unless(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); fail_if(pconn == NULL, "Failed to create pconn for URL '%s' as expected", url); hostport = proxy_conn_get_hostport(pconn); fail_if(hostport == NULL, "Failed to get host/port for pconn"); fail_unless(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); fail_if(pconn == NULL, "Failed to create pconn for URL '%s' as expected", url); hostport = proxy_conn_get_hostport(pconn); fail_if(hostport == NULL, "Failed to get host/port for pconn"); fail_unless(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); fail_unless(pconn_url == NULL, "Failed to handle null argument"); fail_unless(errno == EINVAL, "Failed to set errno to EINVAL"); url = "ftp://127.0.0.1:21"; pconn = proxy_conn_create(p, url, 0); fail_if(pconn == NULL, "Failed to create pconn for URL '%s' as expected", url); pconn_url = proxy_conn_get_uri(pconn); fail_if(pconn_url == NULL, "Failed to get URL for pconn"); fail_unless(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); fail_unless(username == NULL, "Got username from null pconn unexpectedly"); fail_unless(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); fail_if(pconn == NULL, "Failed to create pconn for URL '%s' as expected", url); username = proxy_conn_get_username(pconn); fail_unless(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); fail_if(pconn == NULL, "Failed to create pconn for URL '%s' as expected", url); username = proxy_conn_get_username(pconn); fail_unless(username != NULL, "Expected username from conn"); expected = "user"; fail_unless(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); fail_unless(passwd == NULL, "Got password from null pconn unexpectedly"); fail_unless(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); fail_if(pconn == NULL, "Failed to create pconn for URL '%s' as expected", url); passwd = proxy_conn_get_password(pconn); fail_unless(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); fail_if(pconn == NULL, "Failed to create pconn for URL '%s' as expected", url); passwd = proxy_conn_get_password(pconn); fail_unless(passwd != NULL, "Expected password from conn"); expected = "passwd"; fail_unless(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); fail_if(pconn == NULL, "Failed to create pconn for URL '%s' as expected", url); passwd = proxy_conn_get_password(pconn); fail_unless(passwd != NULL, "Expected password from conn"); expected = ""; fail_unless(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); fail_unless(tls < 0, "Got TLS from null pconn unexpectedly"); fail_unless(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); fail_if(pconn == NULL, "Failed to create pconn for URL '%s' as expected", url); mark_point(); tls = proxy_conn_get_tls(pconn); fail_unless(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); fail_if(pconn == NULL, "Failed to create pconn for URL '%s' as expected", url); mark_point(); tls = proxy_conn_get_tls(pconn); fail_unless(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); fail_if(pconn == NULL, "Failed to create pconn for URL '%s' as expected", url); mark_point(); tls = proxy_conn_get_tls(pconn); fail_unless(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); fail_if(pconn == NULL, "Failed to create pconn for URL '%s' as expected", url); tls = proxy_conn_get_tls(pconn); fail_unless(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); fail_if(pconn == NULL, "Failed to create pconn for URL '%s' as expected", url); tls = proxy_conn_get_tls(pconn); fail_unless(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); fail_if(pconn == NULL, "Failed to create pconn for URL '%s' as expected", url); tls = proxy_conn_get_tls(pconn); fail_unless(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); fail_if(pconn == NULL, "Failed to create pconn for URL '%s' as expected", url); tls = proxy_conn_get_tls(pconn); fail_unless(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); fail_if(pconn == NULL, "Failed to create pconn for URL '%s' as expected", url); tls = proxy_conn_get_tls(pconn); fail_unless(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); fail_if(pconn == NULL, "Failed to create pconn for URL '%s' as expected", url); tls = proxy_conn_get_tls(pconn); fail_unless(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); fail_unless(use_dns_srv < 0, "Got DNS SRV from null pconn unexpectedly"); fail_unless(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); fail_if(pconn == NULL, "Failed to create pconn for URL '%s' as expected", url); mark_point(); use_dns_srv = proxy_conn_use_dns_srv(pconn); fail_unless(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); fail_if(pconn == NULL, "Failed to create pconn for URL '%s' as expected", url); mark_point(); use_dns_srv = proxy_conn_use_dns_srv(pconn); fail_unless(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); fail_if(pconn == NULL, "Failed to create pconn for URL '%s' as expected", url); use_dns_srv = proxy_conn_use_dns_srv(pconn); fail_unless(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); fail_if(pconn == NULL, "Failed to create pconn for URL '%s' as expected", url); use_dns_srv = proxy_conn_use_dns_srv(pconn); fail_unless(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); fail_unless(use_dns_txt < 0, "Got DNS TXT from null pconn unexpectedly"); fail_unless(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); fail_if(pconn == NULL, "Failed to create pconn for URL '%s' as expected", url); mark_point(); use_dns_txt = proxy_conn_use_dns_txt(pconn); fail_unless(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); fail_if(pconn == NULL, "Failed to create pconn for URL '%s' as expected", url); mark_point(); use_dns_txt = proxy_conn_use_dns_txt(pconn); fail_unless(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); fail_if(pconn == NULL, "Failed to create pconn for URL '%s' as expected", url); use_dns_txt = proxy_conn_use_dns_txt(pconn); fail_unless(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); fail_if(pconn == NULL, "Failed to create pconn for URL '%s' as expected", url); use_dns_txt = proxy_conn_use_dns_txt(pconn); fail_unless(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); fail_unless(res < 0, "Failed to handle null argument"); fail_unless(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); fail_if(pconn == NULL, "Failed to create pconn for URL '%s' as expected", url); res = proxy_conn_get_dns_ttl(pconn); fail_unless(res < 0, "Failed to handle non-TTL URL"); fail_unless(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); fail_if(pconn == NULL, "Failed to create pconn for URL '%s' as expected", url); res = proxy_conn_get_dns_ttl(pconn); fail_unless(res < 0, "Failed to handle SRV URL"); fail_unless(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); fail_if(pconn == NULL, "Failed to create pconn for URL '%s' as expected", url); res = proxy_conn_get_dns_ttl(pconn); fail_unless(res < 0, "Failed to handle TXT URL"); fail_unless(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); fail_if(pconn == NULL, "Failed to create pconn for URL '%s' as expected", url); username = proxy_conn_get_username(pconn); fail_unless(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); fail_if(pconn == NULL, "Failed to create pconn for URL '%s' as expected", url); username = proxy_conn_get_username(pconn); fail_unless(username != NULL, "Expected username from conn"); expected = "user"; fail_unless(strcmp(username, expected) == 0, "Expected username '%s', got '%s'", expected, username); mark_point(); proxy_conn_clear_username(pconn); username = proxy_conn_get_username(pconn); fail_unless(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); fail_if(pconn == NULL, "Failed to create pconn for URL '%s' as expected", url); passwd = proxy_conn_get_password(pconn); fail_unless(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); fail_if(pconn == NULL, "Failed to create pconn for URL '%s' as expected", url); passwd = proxy_conn_get_password(pconn); fail_unless(passwd != NULL, "Expected password from conn"); expected = "passwd"; fail_unless(strcmp(passwd, expected) == 0, "Expected password '%s', got '%s'", expected, passwd); mark_point(); proxy_conn_clear_password(pconn); passwd = proxy_conn_get_password(pconn); fail_unless(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); fail_unless(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); fail_unless(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); fail_unless(res == 0, "Failed to handle timeout: %s", strerror(errno)); proxy_sess->connect_timeout = 2; res = proxy_conn_connect_timeout_cb(0, 0, 0, NULL); fail_unless(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); fail_unless(res < 0, "Failed to handle null pool"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); res = proxy_conn_send_proxy_v1(p, NULL); fail_unless(res < 0, "Failed to handle null conn"); fail_unless(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); fail_unless(res < 0, "Failed to handle invalid conn"); fail_unless(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); fail_unless(res < 0, "Failed to handle invalid conn"); fail_unless(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); fail_unless(res < 0, "Failed to handle invalid conn"); fail_unless(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); fail_unless(res < 0, "Failed to handle invalid conn"); fail_unless(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); fail_unless(res < 0, "Failed to handle invalid conn"); fail_unless(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); fail_unless(res < 0, "Failed to handle null pool"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); res = proxy_conn_send_proxy_v2(p, NULL); fail_unless(res < 0, "Failed to handle null conn"); fail_unless(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); fail_unless(res < 0, "Failed to handle invalid conn"); fail_unless(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); fail_unless(res < 0, "Failed to handle invalid conn"); fail_unless(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); fail_unless(res < 0, "Failed to handle invalid conn"); fail_unless(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); fail_unless(res < 0, "Failed to handle invalid conn"); fail_unless(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); fail_unless(res < 0, "Failed to handle invalid conn"); fail_unless(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); suite_add_tcase(suite, testcase); return suite; } proftpd-mod_proxy-0.8/t/api/db.c000066400000000000000000000512751402074030700166310ustar00rootroot00000000000000/* * ProFTPD - mod_proxy testsuite * Copyright (c) 2015-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. */ /* 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); fail_unless(res < 0, "Failed to handle null pool"); fail_unless(errno == EINVAL, "Failed to set errno to EINVAL, got %s (%d)", strerror(errno), errno); res = proxy_db_close(p, NULL); fail_unless(res < 0, "Failed to handle null dbh"); fail_unless(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); fail_unless(dbh == NULL, "Failed to handle null pool"); fail_unless(errno == EINVAL, "Failed to set errno to EINVAL, got %s (%d)", strerror(errno), errno); dbh = proxy_db_open(p, NULL, NULL); fail_unless(dbh == NULL, "Failed to handle null table path"); fail_unless(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); fail_unless(dbh == NULL, "Failed to handle null schema name"); fail_unless(errno == EINVAL, "Failed to set errno to EINVAL, got %s (%d)", strerror(errno), errno); dbh = proxy_db_open(p, table_path, schema_name); fail_unless(dbh != NULL, "Failed to open table '%s': %s", table_path, strerror(errno)); res = proxy_db_close(p, dbh); fail_unless(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); fail_unless(dbh == NULL, "Failed to handle null pool"); fail_unless(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); fail_unless(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); fail_unless(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); fail_unless(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); fail_unless(res == 0, "Failed to close database: %s", strerror(errno)); if (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); fail_unless(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); fail_unless(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); fail_unless(dbh == NULL, "Opened table with version skew unexpectedly"); fail_unless(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); fail_unless(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); fail_unless(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); fail_unless(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); fail_unless(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); fail_unless(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); fail_unless(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); fail_unless(res < 0, "Failed to handle null pool"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_db_exec_stmt(p, NULL, NULL, NULL); fail_unless(res < 0, "Failed to handle null dbh"); fail_unless(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); fail_unless(dbh != NULL, "Failed to open table '%s': %s", table_path, strerror(errno)); res = proxy_db_exec_stmt(p, dbh, NULL, NULL); fail_unless(res < 0, "Failed to handle null statement"); fail_unless(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); fail_unless(res < 0, "Failed to execute statement '%s'", stmt); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_db_close(p, dbh); fail_unless(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); fail_unless(res < 0, "Failed to handle null pool"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_db_prepare_stmt(p, NULL, NULL); fail_unless(res < 0, "Failed to handle null dbh"); fail_unless(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); fail_unless(dbh != NULL, "Failed to open table '%s': %s", table_path, strerror(errno)); res = proxy_db_prepare_stmt(p, dbh, NULL); fail_unless(res < 0, "Failed to handle null statement"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); stmt = "foo bar baz?"; res = proxy_db_prepare_stmt(p, dbh, stmt); fail_unless(res < 0, "Prepared invalid statement '%s' unexpectedly", stmt); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = create_table(p, dbh, "foo"); fail_unless(res == 0, "Failed to create table 'foo': %s", strerror(errno)); stmt = "SELECT COUNT(*) FROM foo;"; res = proxy_db_prepare_stmt(p, dbh, stmt); fail_unless(res == 0, "Failed to prepare statement '%s': %s", stmt, strerror(errno)); res = proxy_db_prepare_stmt(p, dbh, stmt); fail_unless(res == 0, "Failed to prepare statement '%s': %s", stmt, strerror(errno)); res = create_table(p, dbh, "bar"); fail_unless(res == 0, "Failed to create table 'bar': %s", strerror(errno)); stmt = "SELECT COUNT(*) FROM bar;"; res = proxy_db_prepare_stmt(p, dbh, stmt); fail_unless(res == 0, "Failed to prepare statement '%s': %s", stmt, strerror(errno)); res = proxy_db_close(p, dbh); fail_unless(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); fail_unless(res < 0, "Failed to handle null arguments"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_db_finish_stmt(p, NULL, NULL); fail_unless(res < 0, "Failed to handle null dbh"); fail_unless(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); fail_unless(dbh != NULL, "Failed to open table '%s': %s", table_path, strerror(errno)); res = proxy_db_finish_stmt(p, dbh, NULL); fail_unless(res < 0, "Failed to handle null statement"); fail_unless(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); fail_unless(res < 0, "Failed to handle unprepared statement"); fail_unless(errno == ENOENT, "Expected ENOENT (%d), got '%s' (%d)", ENOENT, strerror(errno), errno); res = create_table(p, dbh, "foo"); fail_unless(res == 0, "Failed to create table 'foo': %s", strerror(errno)); res = proxy_db_prepare_stmt(p, dbh, stmt); fail_unless(res == 0, "Failed to prepare statement '%s': %s", stmt, strerror(errno)); res = proxy_db_finish_stmt(p, dbh, stmt); fail_unless(res == 0, "Failed to finish statement '%s': %s", stmt, strerror(errno)); res = proxy_db_finish_stmt(p, dbh, stmt); fail_unless(res < 0, "Failed to handle unprepared statement"); fail_unless(errno == ENOENT, "Expected ENOENT (%d), got '%s' (%d)", ENOENT, strerror(errno), errno); res = proxy_db_close(p, dbh); fail_unless(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; res = proxy_db_bind_stmt(NULL, NULL, NULL, -1, -1, NULL); fail_unless(res < 0, "Failed to handle null pool"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_db_bind_stmt(p, NULL, NULL, -1, -1, NULL); fail_unless(res < 0, "Failed to handle null dbh"); fail_unless(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); fail_unless(dbh != NULL, "Failed to open table '%s': %s", table_path, strerror(errno)); res = proxy_db_bind_stmt(p, dbh, NULL, -1, -1, NULL); fail_unless(res < 0, "Failed to handle null statement"); fail_unless(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); fail_unless(res < 0, "Failed to handle invalid index %d", idx); fail_unless(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); fail_unless(res < 0, "Failed to handle unprepared statement"); fail_unless(errno == ENOENT, "Expected ENOENT (%d), got '%s' (%d)", ENOENT, strerror(errno), errno); res = create_table(p, dbh, "foo"); fail_unless(res == 0, "Failed to create table 'foo': %s", strerror(errno)); stmt = "SELECT COUNT(*) FROM foo;"; res = proxy_db_prepare_stmt(p, dbh, stmt); fail_unless(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); fail_unless(res < 0, "Failed to handle missing INT value"); fail_unless(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); fail_unless(res < 0, "Failed to handle invalid index value"); fail_unless(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); fail_unless(res < 0, "Failed to handle missing LONG value"); fail_unless(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); fail_unless(res < 0, "Failed to handle invalid index value"); fail_unless(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); fail_unless(res < 0, "Failed to handle missing TEXT value"); fail_unless(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); fail_unless(res < 0, "Failed to handle invalid index value"); fail_unless(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); fail_unless(res < 0, "Failed to handle invalid NULL value"); fail_unless(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); fail_unless(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); fail_unless(res == 0, "Failed to bind INT value: %s", strerror(errno)); res = proxy_db_close(p, dbh); fail_unless(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); fail_unless(results == NULL, "Failed to handle null pool"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); results = proxy_db_exec_prepared_stmt(p, NULL, NULL, NULL); fail_unless(results == NULL, "Failed to handle null dbh"); fail_unless(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); fail_unless(dbh != NULL, "Failed to open table '%s': %s", table_path, strerror(errno)); results = proxy_db_exec_prepared_stmt(p, dbh, NULL, NULL); fail_unless(results == NULL, "Failed to handle null statement"); fail_unless(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); fail_unless(results == NULL, "Failed to handle unprepared statement"); fail_unless(errno == ENOENT, "Expected ENOENT (%d), got '%s' (%d)", ENOENT, strerror(errno), errno); res = create_table(p, dbh, "foo"); fail_unless(res == 0, "Failed to create table 'foo': %s", strerror(errno)); res = proxy_db_prepare_stmt(p, dbh, stmt); fail_unless(res == 0, "Failed to prepare statement '%s': %s", stmt, strerror(errno)); results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr); fail_unless(results != NULL, "Failed to execute prepared statement '%s': %s (%s)", stmt, errstr, strerror(errno)); res = proxy_db_close(p, dbh); fail_unless(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); fail_unless(res < 0, "Failed to handle null pool"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_db_reindex(p, NULL, NULL, NULL); fail_unless(res < 0, "Failed to handle null dbh"); fail_unless(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); fail_unless(dbh != NULL, "Failed to open table '%s': %s", table_path, strerror(errno)); res = proxy_db_reindex(p, dbh, NULL, NULL); fail_unless(res < 0, "Failed to handle null index name"); fail_unless(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); fail_unless(res < 0, "Failed to handle invalid index"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); fail_unless(errstr != NULL, "Failed to provide error string"); res = proxy_db_close(p, dbh); fail_unless(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.8/t/api/dns.c000066400000000000000000000174441402074030700170300ustar00rootroot00000000000000/* * 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. */ /* 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); fail_unless(res < 0, "Failed to handle null pool argument"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_dns_resolve(p, NULL, dns_type, NULL, NULL); fail_unless(res < 0, "Failed to handle null name argument"); fail_unless(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); fail_unless(res < 0, "Failed to handle null resp argument"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_dns_resolve(p, name, dns_type, &resp, NULL); fail_unless(res < 0, "Failed to handle UNKNOWN type argument"); fail_unless(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); fail_unless(res < 0, "Failed to handle no SRV records for '%s'", name); fail_unless(errno == ENOENT, "Expected ENOENT (%d), got %s (%d)", ENOENT, strerror(errno), errno); fail_unless(resp == NULL, "Expected null responses"); mark_point(); name = " "; res = proxy_dns_resolve(p, name, dns_type, &resp, NULL); fail_unless(res < 0, "Failed to handle no SRV records for '%s'", name); fail_unless(errno == ENOENT, "Expected ENOENT (%d), got %s (%d)", ENOENT, strerror(errno), errno); fail_unless(resp == NULL, "Expected null responses"); mark_point(); name = "."; res = proxy_dns_resolve(p, name, dns_type, &resp, NULL); fail_unless(res < 0, "Failed to handle no SRV records for '%s'", name); fail_unless(errno == ENOENT || errno == EPERM, "Expected EPERM (%d) or ENOENT (%d), got %s (%d)", EPERM, ENOENT, strerror(errno), errno); fail_unless(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); fail_unless(res < 0, "Failed to handle no TXT records for '%s'", name); fail_unless(errno == ENOENT, "Expected ENOENT (%d), got %s (%d)", ENOENT, strerror(errno), errno); fail_unless(resp == NULL, "Expected null responses"); mark_point(); name = " "; res = proxy_dns_resolve(p, name, dns_type, &resp, NULL); fail_unless(res < 0, "Failed to handle no TXT records for '%s'", name); fail_unless(errno == ENOENT, "Expected ENOENT (%d), got %s (%d)", ENOENT, strerror(errno), errno); fail_unless(resp == NULL, "Expected null responses"); mark_point(); name = "."; res = proxy_dns_resolve(p, name, dns_type, &resp, NULL); fail_unless(res < 0, "Failed to handle no TXT records for '%s'", name); fail_unless(errno == ENOENT, "Expected ENOENT (%d), got %s (%d)", ENOENT, strerror(errno), errno); fail_unless(resp == NULL, "Expected null responses"); mark_point(); name = "+"; res = proxy_dns_resolve(p, name, dns_type, &resp, NULL); fail_unless(res < 0, "Failed to handle no TXT records for '%s'", name); fail_unless(errno == ENOENT, "Expected ENOENT (%d), got %s (%d)", ENOENT, strerror(errno), errno); fail_unless(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); fail_unless(res < 0, "Failed to handle no SRV records for '%s'", name); fail_unless(errno == ENOENT, "Expected ENOENT (%d), got %s (%d)", ENOENT, strerror(errno), errno); fail_unless(resp == NULL, "Expected null responses"); mark_point(); name = "_imap._tcp.gmail.com"; res = proxy_dns_resolve(p, name, dns_type, &resp, &ttl); fail_unless(res < 0, "Failed to handle explicit 'no service' for '%s'", name); fail_unless(errno == ENOENT, "Expected ENOENT (%d), got %s (%d)", ENOENT, strerror(errno), errno); fail_unless(resp == NULL, "Expected null responses"); mark_point(); name = "_imaps._tcp.gmail.com"; res = proxy_dns_resolve(p, name, dns_type, &resp, &ttl); fail_unless(res > 0, "Failed to resolve SRV records for '%s': %s", name, strerror(errno)); fail_unless(resp != NULL, "Expected non-null responses"); mark_point(); name = "_ldap._tcp.ru.ac.za"; res = proxy_dns_resolve(p, name, dns_type, &resp, &ttl); fail_unless(res > 0, "Failed to resolve SRV records for '%s': %s", name, strerror(errno)); fail_unless(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; mark_point(); name = "google.com"; res = proxy_dns_resolve(p, name, dns_type, &resp, &ttl); fail_unless(res > 0, "Failed to resolve TXT records for '%s': %s", name, strerror(errno)); fail_unless(resp != NULL, "Expected non-null responses"); mark_point(); name = "amazon.com"; res = proxy_dns_resolve(p, name, dns_type, &resp, &ttl); fail_unless(res > 0, "Failed to resolve TXT records for '%s': %s", name, strerror(errno)); fail_unless(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.8/t/api/forward.c000066400000000000000000000522351402074030700177050ustar00rootroot00000000000000/* * ProFTPD - mod_proxy testsuite * Copyright (c) 2016-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. */ /* 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); fail_unless(res == 0, "Failed to create tmp directory '%s': %s", test_dir, strerror(errno)); res = chmod(test_dir, perms); fail_unless(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); fail_unless(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); fail_unless(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); fail_unless(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); fail_unless(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); fail_unless(session.c->remote_addr != NULL, "Failed to get address: %s", strerror(errno)); mark_point(); res = proxy_forward_sess_init(p, test_dir, NULL); fail_unless(res < 0, "Initialized Forward API session resources unexpectedly"); fail_unless(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); fail_unless(session.c->remote_addr != NULL, "Failed to get address: %s", strerror(errno)); mark_point(); res = proxy_forward_sess_init(p, test_dir, NULL); fail_unless(res == 0, "Failed to init Forward API session resources: %s", strerror(errno)); res = proxy_forward_sess_free(p, NULL); fail_unless(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); fail_unless(res < 0, "Failed to handle null argument"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); method = "foo"; res = proxy_forward_get_method(method); fail_unless(res < 0, "Failed to handle unsupported method '%s'", method); fail_unless(errno == ENOENT, "Expected ENOENT (%d), got '%s' (%d)", ENOENT, strerror(errno), errno); method = "proxyuser,user@host"; res = proxy_forward_get_method(method); fail_unless(res == PROXY_FORWARD_METHOD_USER_WITH_PROXY_AUTH, "Failed to handle method '%s'", method); method = "user@host"; res = proxy_forward_get_method(method); fail_unless(res == PROXY_FORWARD_METHOD_USER_NO_PROXY_AUTH, "Failed to handle method '%s'", method); method = "proxyuser@host,user"; res = proxy_forward_get_method(method); fail_unless(res == PROXY_FORWARD_METHOD_PROXY_USER_WITH_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(); fail_unless(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); fail_unless(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); fail_unless(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); fail_unless(res < 0, "Handled USER command unexpectedly"); fail_unless(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); fail_unless(res < 0, "Handled USER command unexpectedly"); fail_unless(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); fail_unless(res == 1, "Failed to handle USER command: %s", strerror(errno)); fail_unless(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); fail_unless(res == 1, "Failed to handle USER command: %s", strerror(errno)); fail_unless(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); fail_unless(res < 0, "Handled USER command unexpectedly"); fail_unless(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); fail_unless(res < 0, "Handled USER command unexpectedly"); fail_unless(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); fail_unless(res == 1, "Failed to handle USER command: %s", strerror(errno)); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); /* 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"); mark_point(); res = proxy_forward_handle_user(cmd, proxy_sess, &successful, &block_responses); fail_unless(res == 1, "Failed to handle USER command: %s", strerror(errno)); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); res = proxy_forward_sess_free(p, NULL); fail_unless(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); fail_unless(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); fail_unless(res == 0, "Failed to handle USER command: %s", strerror(errno)); fail_unless(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); fail_unless(res == 1, "Failed to handle USER command: %s", strerror(errno)); fail_unless(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); fail_unless(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); fail_unless(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); fail_unless(res == 0, "Failed to handle USER command: %s", strerror(errno)); fail_unless(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); fail_unless(res == 1, "Failed to handle USER command: %s", strerror(errno)); fail_unless(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); fail_unless(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 on travis, for now. It fails unexpectedly. */ if (getenv("TRAVIS_CI") != NULL) { return; } res = forward_sess_init(PROXY_FORWARD_METHOD_USER_NO_PROXY_AUTH); fail_unless(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); fail_unless(res < 0, "Handled PASS command unexpectedly"); fail_unless(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); fail_unless(res == 1, "Failed to handle USER command: %s", strerror(errno)); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); if (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); fail_unless(res == 1, "Failed to handle PASS command: %s", strerror(errno)); fail_unless(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); fail_unless(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_DB_OPEN_FL_SKIP_VACUUM); fail_unless(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("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. */ fail_if(res == 1, "Handled USER command unexpectedly"); fail_unless(errno == EPERM, "Expected EPERM (%d), got %s (%d)", EPERM, strerror(errno), errno); } mark_point(); res = proxy_tls_sess_free(p); fail_unless(res == 0, "Failed to free TLS API session resources: %s", strerror(errno)); mark_point(); res = proxy_tls_free(p); fail_unless(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); fail_unless(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); fail_unless(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); fail_unless(res < 0, "Handled PASS command unexpectedly"); fail_unless(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); fail_unless(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); fail_unless(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); fail_unless(res < 0, "Handled PASS command unexpectedly"); fail_unless(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); fail_unless(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.8/t/api/ftp/000077500000000000000000000000001402074030700166575ustar00rootroot00000000000000proftpd-mod_proxy-0.8/t/api/ftp/conn.c000066400000000000000000000220341402074030700177610ustar00rootroot00000000000000/* * ProFTPD - mod_proxy testsuite * Copyright (c) 2016-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. */ /* 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); fail_unless(res == NULL, "Failed to handle null pool"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_ftp_conn_accept(p, NULL, NULL, FALSE); fail_unless(res == NULL, "Failed to handle null data conn"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); data_conn = pr_inet_create_conn(p, -2, NULL, INPORT_ANY, FALSE); fail_unless(data_conn != NULL, "Failed to create conn: %s", strerror(errno)); mark_point(); res = proxy_ftp_conn_accept(p, data_conn, NULL, FALSE); fail_unless(res == NULL, "Failed to handle null ctrl conn"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); ctrl_conn = pr_inet_create_conn(p, -2, NULL, INPORT_ANY, FALSE); fail_unless(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); fail_unless(res == NULL, "Failed to handle null ctrl conn"); fail_unless(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); fail_unless(res == NULL, "Failed to handle null ctrl conn"); fail_unless(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); fail_unless(res == NULL, "Failed to handle null ctrl conn"); fail_unless(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); fail_unless(res == NULL, "Failed to handle null ctrl conn"); fail_unless(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); fail_unless(res == NULL, "Failed to handle null pool"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_ftp_conn_connect(p, NULL, NULL, FALSE); fail_unless(res == NULL, "Failed to handle null remote addr"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); remote_addr = pr_netaddr_get_addr(p, "127.0.0.1", NULL); fail_unless(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); fail_unless(res == NULL, "Failed to handle bad address family"); fail_unless(errno == ECONNREFUSED, "Expected ECONNREFUSED (%d), got %s (%d)", ECONNREFUSED, strerror(errno), errno); mark_point(); res = proxy_ftp_conn_connect(p, NULL, remote_addr, TRUE); fail_unless(res == NULL, "Failed to handle bad address family"); fail_unless(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); fail_unless(res == NULL, "Failed to handle bad address family"); fail_unless(errno == ECONNREFUSED, "Expected ECONNREFUSED (%d), got %s (%d)", ECONNREFUSED, strerror(errno), errno); mark_point(); res = proxy_ftp_conn_connect(p, NULL, remote_addr, TRUE); fail_unless(res == NULL, "Failed to handle bad address family"); fail_unless(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); fail_unless(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); fail_unless(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); fail_unless(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); fail_unless(res == NULL, "Failed to handle null pool"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_ftp_conn_listen(p, NULL, FALSE); fail_unless(res == NULL, "Failed to handle null bind address"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); bind_addr = pr_netaddr_get_addr(p, "127.0.0.1", NULL); fail_unless(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); fail_unless(res != NULL, "Failed to listen: %s", strerror(errno)); pr_inet_close(p, res); mark_point(); res = proxy_ftp_conn_listen(p, bind_addr, TRUE); fail_unless(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.8/t/api/ftp/ctrl.c000066400000000000000000000302361402074030700177730ustar00rootroot00000000000000/* * ProFTPD - mod_proxy testsuite * Copyright (c) 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. */ /* 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); fail_unless(res < 0, "Failed to handle null pool"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_ftp_ctrl_handle_async(p, NULL, NULL, flags); fail_unless(res < 0, "Failed to handle null backend conn"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); backend_conn = pr_inet_create_conn(p, -2, NULL, INPORT_ANY, FALSE); fail_unless(backend_conn != NULL, "Failed to create conn: %s", strerror(errno)); mark_point(); res = proxy_ftp_ctrl_handle_async(p, backend_conn, NULL, flags); fail_unless(res < 0, "Failed to handle null frontend conn"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); frontend_conn = pr_inet_create_conn(p, -2, NULL, INPORT_ANY, FALSE); fail_unless(frontend_conn != NULL, "Failed to create conn: %s", strerror(errno)); mark_point(); res = proxy_ftp_ctrl_handle_async(p, backend_conn, frontend_conn, flags); fail_unless(res < 0, "Failed to handle null backend conn stream"); fail_unless(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); fail_unless(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); fail_unless(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); fail_unless(resp == NULL, "Failed to handle null pool"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); mark_point(); resp = proxy_ftp_ctrl_recv_resp(p, NULL, NULL, flags); fail_unless(resp == NULL, "Failed to handle null ctrl conn"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); ctrl_conn = pr_inet_create_conn(p, -2, NULL, INPORT_ANY, FALSE); fail_unless(ctrl_conn != NULL, "Failed to create conn: %s", strerror(errno)); mark_point(); resp = proxy_ftp_ctrl_recv_resp(p, ctrl_conn, NULL, flags); fail_unless(resp == NULL, "Failed to handle null response nlines"); fail_unless(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); fail_unless(resp == NULL, "Failed to handle EOF"); fail_unless(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); fail_unless(nstrm != NULL, "Failed to open ctrl stream: %s", strerror(errno)); pbuf = pr_netio_buffer_alloc(nstrm); fail_unless(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); fail_unless(resp == NULL, "Failed to handle invalid response"); fail_unless(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); fail_unless(resp == NULL, "Failed to handle invalid response"); fail_unless(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); fail_unless(resp == NULL, "Failed to handle invalid response"); fail_unless(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); fail_unless(resp == NULL, "Failed to handle invalid response"); fail_unless(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); fail_unless(resp == NULL, "Failed to handle invalid response"); fail_unless(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); fail_unless(resp == NULL, "Failed to handle invalid response"); fail_unless(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); fail_unless(resp == NULL, "Failed to handle invalid response"); fail_unless(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); fail_unless(resp == NULL, "Failed to handle invalid response"); fail_unless(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); fail_unless(resp == NULL, "Failed to handle invalid response"); fail_unless(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); fail_unless(resp != NULL, "Failed to receive response: %s", strerror(errno)); fail_unless(strcmp(resp->num, R_200) == 0, "Expected '%s', got '%s'", R_200, resp->num); fail_unless(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); fail_unless(res < 0, "Failed to handle null pool"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_ftp_ctrl_send_cmd(p, NULL, NULL); fail_unless(res < 0, "Failed to handle null conn"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); ctrl_conn = pr_inet_create_conn(p, -2, NULL, INPORT_ANY, FALSE); fail_unless(ctrl_conn != NULL, "Failed to create conn: %s", strerror(errno)); res = proxy_ftp_ctrl_send_cmd(p, ctrl_conn, NULL); fail_unless(res < 0, "Failed to handle null command"); fail_unless(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); fail_unless(res < 0, "Failed to handle command without stream"); fail_unless(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); fail_unless(res < 0, "Failed to handle command without stream"); fail_unless(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); fail_unless(res < 0, "Failed to handle null pool"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_ftp_ctrl_send_resp(p, NULL, NULL, 0); fail_unless(res < 0, "Failed to handle null response"); fail_unless(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); fail_unless(res == 0, "Failed to handle response: %s", strerror(errno)); res = proxy_ftp_ctrl_send_resp(p, NULL, resp, 3); fail_unless(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.8/t/api/ftp/data.c000066400000000000000000000130111402074030700177300ustar00rootroot00000000000000/* * ProFTPD - mod_proxy testsuite * Copyright (c) 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. */ /* 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); fail_unless(pbuf == NULL, "Failed to handle null pool"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); mark_point(); pbuf = proxy_ftp_data_recv(p, NULL, FALSE); fail_unless(pbuf == NULL, "Failed to handle null conn"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); conn = pr_inet_create_conn(p, -2, NULL, INPORT_ANY, FALSE); fail_unless(conn != NULL, "Failed to create conn: %s", strerror(errno)); mark_point(); pbuf = proxy_ftp_data_recv(p, conn, FALSE); fail_unless(pbuf == NULL, "Failed to handle missing instream"); fail_unless(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); fail_unless(conn->instrm != NULL, "Failed open data stream: %s", strerror(errno)); mark_point(); pbuf = proxy_ftp_data_recv(p, conn, FALSE); fail_unless(pbuf == NULL, "Failed to handle bad instream fd"); fail_unless(errno == EBADF, "Expected EBADF (%d), got %s (%d)", EBADF, strerror(errno), errno); mark_point(); pbuf = proxy_ftp_data_recv(p, conn, TRUE); fail_unless(pbuf == NULL, "Failed to handle bad instream fd"); fail_unless(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); fail_unless(res < 0, "Failed to handle null pool"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_ftp_data_send(p, NULL, NULL, FALSE); fail_unless(res < 0, "Failed to handle null conn"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); conn = pr_inet_create_conn(p, -2, NULL, INPORT_ANY, FALSE); fail_unless(conn != NULL, "Failed to create conn: %s", strerror(errno)); mark_point(); res = proxy_ftp_data_send(p, conn, NULL, FALSE); fail_unless(res < 0, "Failed to handle null buffer"); fail_unless(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); fail_unless(conn->outstrm != NULL, "Failed open data stream: %s", strerror(errno)); mark_point(); res = proxy_ftp_data_send(p, conn, NULL, FALSE); fail_unless(res < 0, "Failed to handle null buffer"); fail_unless(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); fail_unless(res < 0, "Sent data unexpectedly"); fail_unless(errno == EBADF, "Expected EBADF (%d), got %s (%d)", EBADF, strerror(errno), errno); mark_point(); res = proxy_ftp_data_send(p, conn, pbuf, TRUE); fail_unless(res < 0, "Sent data unexpectedly"); fail_unless(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.8/t/api/ftp/dirlist.c000066400000000000000000000257231402074030700205060ustar00rootroot00000000000000/* * 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 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); fail_unless(res < 0, "Failed to handle null pool"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_ftp_dirlist_init(p, NULL); fail_unless(res < 0, "Failed to handle null proxy_sess"); fail_unless(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); fail_unless(res < 0, "Failed to handle null proxy_sess"); fail_unless(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); fail_unless(res == NULL, "Failed to handle null pool"); fail_unless(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); fail_unless(res == NULL, "Failed to handle null text"); fail_unless(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); fail_unless(res == NULL, "Failed to handle zero text len"); fail_unless(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); fail_unless(res == NULL, "Failed to handle bad text"); fail_unless(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); fail_unless(res == NULL, "Failed to handle null pool"); fail_unless(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); fail_unless(res == NULL, "Failed to handle null text"); fail_unless(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); fail_unless(res == NULL, "Failed to handle zero text len"); fail_unless(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); fail_unless(res == NULL, "Failed to handle null tm"); fail_unless(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); fail_unless(res == NULL, "Failed to handle bad text"); fail_unless(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); fail_unless(res == NULL, "Failed to handle null pool"); fail_unless(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); fail_unless(res == NULL, "Failed to handle null text"); fail_unless(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); fail_unless(res == NULL, "Failed to handle zero text len"); fail_unless(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); fail_unless(res == NULL, "Failed to handle null tm"); fail_unless(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); fail_unless(res == NULL, "Failed to handle null userdata"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); proxy_sess = (struct proxy_session *) proxy_session_alloc(p); fail_unless(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); fail_unless(res == NULL, "Failed to handle null proxy_sess->dirlist_ctx"); fail_unless(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); fail_unless(text == NULL, "Failed to handle null pool"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); mark_point(); text = proxy_ftp_dirlist_fileinfo_to_facts(p, NULL, NULL); fail_unless(text == NULL, "Failed to handle null fileinfo"); fail_unless(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); fail_unless(text == NULL, "Failed to handle null textlen"); fail_unless(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); fail_unless(res < 0, "Failed to handle null pool"); fail_unless(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); fail_unless(res < 0, "Failed to handle null buf"); fail_unless(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); fail_unless(res < 0, "Failed to handle zero buflen"); fail_unless(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); fail_unless(res < 0, "Failed to handle zero max textsz"); fail_unless(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); fail_unless(res < 0, "Failed to handle null output text"); fail_unless(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); fail_unless(res < 0, "Failed to handle null output textlen"); fail_unless(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); fail_unless(res < 0, "Failed to handle null userdata"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); proxy_sess = (struct proxy_session *) proxy_session_alloc(p); fail_unless(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); fail_unless(res < 0, "Failed to handle null proxy_sess->dirlist_ctx"); fail_unless(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.8/t/api/ftp/facts.c000066400000000000000000000037351402074030700201330ustar00rootroot00000000000000/* * 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.8/t/api/ftp/msg.c000066400000000000000000000450441402074030700176200ustar00rootroot00000000000000/* * ProFTPD - mod_proxy testsuite * Copyright (c) 2016-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. */ /* 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); fail_unless(res == NULL, "Failed to handle null pool"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_ftp_msg_fmt_addr(p, NULL, 0, FALSE); fail_unless(res == NULL, "Failed to handle null addr"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); addr = pr_netaddr_get_addr(p, "127.0.0.1", NULL); fail_unless(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); fail_unless(res != NULL, "Failed to format addr: %s", strerror(errno)); expected = "127,0,0,1,8,73"; fail_unless(strcmp(res, expected) == 0, "Expected '%s', got '%s'", expected, res); addr = pr_netaddr_get_addr(p, "169.254.254.254", NULL); fail_unless(addr != NULL, "Failed to get addr for 169.254.254.254: %s", strerror(errno)); res = proxy_ftp_msg_fmt_addr(p, addr, 38550, FALSE); fail_unless(res != NULL, "Failed to format addr: %s", strerror(errno)); expected = "169,254,254,254,150,150"; fail_unless(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); fail_unless(res == NULL, "Failed to handle null pool"); fail_unless(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); fail_unless(res == NULL, "Failed to handle null addr"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); addr = pr_netaddr_get_addr(p, "127.0.0.1", NULL); fail_unless(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); fail_unless(res == NULL, "Failed to handle null addr"); fail_unless(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); fail_unless(res != NULL, "Failed to format addr: %s", strerror(errno)); expected = "|1|127.0.0.1|2121|"; fail_unless(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); fail_unless(res != NULL, "Failed to format addr: %s", strerror(errno)); expected = "|||2121|"; fail_unless(strcmp(res, expected) == 0, "Expected '%s', got '%s'", expected, res); #if PR_USE_IPV6 addr = pr_netaddr_get_addr(p, "::1", NULL); fail_unless(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); fail_unless(res != NULL, "Failed to format addr: %s", strerror(errno)); expected = "|2|::1|2121|"; fail_unless(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); fail_unless(res != NULL, "Failed to format addr: %s", strerror(errno)); expected = "|||2121|"; fail_unless(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); fail_unless(res == NULL, "Failed to handle null pool"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_ftp_msg_parse_addr(p, NULL, 0); fail_unless(res == NULL, "Failed to handle null msg"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); msg = "foo"; res = proxy_ftp_msg_parse_addr(p, msg, 0); fail_unless(res == NULL, "Failed to handle invalid format"); fail_unless(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); fail_unless(res == NULL, "Failed to handle invalid format"); fail_unless(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); fail_unless(res == NULL, "Failed to handle invalid format"); fail_unless(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); fail_unless(res == NULL, "Failed to handle invalid format"); fail_unless(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); fail_unless(res == NULL, "Failed to handle invalid format"); fail_unless(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); fail_unless(res == NULL, "Failed to handle invalid format"); fail_unless(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); fail_unless(res != NULL, "Failed to parse message '%s': %s", msg, strerror(errno)); ip_str = pr_netaddr_get_ipstr(res); expected = "127.0.0.1"; fail_unless(strcmp(ip_str, expected) == 0, "Expected '%s', got '%s'", expected, ip_str); fail_unless(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); fail_unless(res != NULL, "Failed to parse message '%s': %s", msg, strerror(errno)); ip_str = pr_netaddr_get_ipstr(res); expected = "195.144.107.198"; fail_unless(strcmp(ip_str, expected) == 0, "Expected '%s', got '%s'", expected, ip_str); fail_unless(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); fail_unless(res != NULL, "Failed to parse message '%s': %s", msg, strerror(errno)); ip_str = pr_netaddr_get_ipstr(res); expected = "127.0.0.1"; fail_unless(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); fail_unless(res != NULL, "Failed to parse message '%s': %s", msg, strerror(errno)); ip_str = pr_netaddr_get_ipstr(res); expected = "::ffff:127.0.0.1"; fail_unless(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); fail_unless(res != NULL, "Failed to parse message '%s': %s", msg, strerror(errno)); ip_str = pr_netaddr_get_ipstr(res); expected = "::ffff:195.144.107.198"; fail_unless(strcmp(ip_str, expected) == 0, "Expected '%s', got '%s'", expected, ip_str); fail_unless(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); fail_unless(res != NULL, "Failed to parse message '%s': %s", msg, strerror(errno)); ip_str = pr_netaddr_get_ipstr(res); expected = "127.0.0.1"; fail_unless(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); fail_unless(res == NULL, "Failed to handle null pool"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_ftp_msg_parse_ext_addr(p, NULL, NULL, 0, NULL); fail_unless(res == NULL, "Failed to handle null msg"); fail_unless(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); fail_unless(res == NULL, "Failed to handle null addr"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); addr = pr_netaddr_get_addr(p, "127.0.0.1", NULL); fail_unless(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); fail_unless(res == NULL, "Failed to handle bad network protocol"); fail_unless(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); fail_unless(res == NULL, "Failed to handle bad EPSV response"); fail_unless(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); fail_unless(res == NULL, "Failed to handle bad EPSV response"); fail_unless(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); fail_unless(res == NULL, "Failed to handle bad network protocol"); fail_unless(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); fail_unless(res == NULL, "Failed to handle bad network protocol"); fail_unless(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); fail_unless(res == NULL, "Failed to handle bad network protocol"); fail_unless(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); fail_unless(res == NULL, "Failed to handle bad network protocol"); fail_unless(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); fail_unless(res == NULL, "Failed to handle bad network protocol"); fail_unless(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); fail_unless(res == NULL, "Failed to handle bad network protocol"); fail_unless(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); fail_unless(res == NULL, "Failed to handle badly formatted message"); fail_unless(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); fail_unless(res == NULL, "Failed to handle badly formatted message"); fail_unless(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); fail_unless(res == NULL, "Failed to handle badly formatted message"); fail_unless(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); fail_unless(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); fail_unless(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"); fail_unless(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"); fail_unless(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); fail_unless(res != NULL, "Failed to handle formatted message '%s': %s", msg, strerror(errno)); fail_unless( 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); fail_unless(res == NULL, "Failed to handle network protocol mismatch"); fail_unless(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); fail_unless(res == NULL, "Failed to handle network protocol mismatch"); fail_unless(errno == EPERM, "Expected EPERM (%d), got %s (%d)", EPERM, strerror(errno), errno); #ifdef PR_USE_IPV6 addr = pr_netaddr_get_addr(p, "::1", NULL); fail_unless(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); fail_unless(res == NULL, "Failed to handle network protocol mismatch"); fail_unless(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); fail_unless(res == NULL, "Failed to handle network protocol mismatch"); fail_unless(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); fail_unless(res == NULL, "Failed to handle network protocol mismatch"); fail_unless(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); fail_unless(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"); fail_unless(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"); fail_unless(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); fail_unless(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); fail_unless(res != NULL, "Failed to handle formatted message '%s': %s", msg, strerror(errno)); fail_unless( 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); fail_unless(res == NULL, "Failed to handle network protocol mismatch"); fail_unless(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.8/t/api/ftp/sess.c000066400000000000000000000124001402074030700177750ustar00rootroot00000000000000/* * ProFTPD - mod_proxy testsuite * Copyright (c) 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. */ /* 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); fail_unless(res < 0, "Failed to handle null pool"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_ftp_sess_get_feat(p, NULL); fail_unless(res < 0, "Failed to handle null proxy session"); fail_unless(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); fail_unless(res < 0, "Failed to handle null proxy session"); fail_unless(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); fail_unless(res < 0, "Failed to handle null pool"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_ftp_sess_send_auth_tls(p, NULL); fail_unless(res < 0, "Failed to handle null proxy session"); fail_unless(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 fail_unless(res < 0, "Sent AUTH TLS unexpectedly"); fail_unless(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); fail_unless(res < 0, "Failed to handle null pool"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_ftp_sess_send_host(p, NULL); fail_unless(res < 0, "Failed to handle null proxy session"); fail_unless(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); fail_unless(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); fail_unless(res < 0, "Failed to handle null pool"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_ftp_sess_send_pbsz_prot(p, NULL); fail_unless(res < 0, "Failed to handle null proxy session"); fail_unless(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); fail_unless(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.8/t/api/ftp/xfer.c000066400000000000000000000224761402074030700200020ustar00rootroot00000000000000/* * ProFTPD - mod_proxy testsuite * Copyright (c) 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. */ /* 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); fail_unless(res < 0, "Failed to handle null command"); fail_unless(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); fail_unless(res < 0, "Failed to handle null error code"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_ftp_xfer_prepare_active(0, cmd, "500", NULL, 0); fail_unless(res < 0, "Failed to handle null proxy session"); fail_unless(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); fail_unless(res < 0, "Failed to handle null proxy session"); fail_unless(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); fail_unless(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); fail_unless(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); fail_unless(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); fail_unless(res < 0, "Failed to handle illegal FTP command"); fail_unless(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); fail_unless(res < 0, "Failed to handle bad PORT command"); fail_unless(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); fail_unless(res < 0, "Failed to handle bad EPRT command"); fail_unless(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); fail_unless(res < 0, "Failed to handle bad EPRT command"); fail_unless(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); fail_unless(res < 0, "Failed to handle bad PORT command"); fail_unless(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); fail_unless(addr == NULL, "Failed to handle null command"); fail_unless(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); fail_unless(addr == NULL, "Failed to handle null error code"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); addr = proxy_ftp_xfer_prepare_passive(0, cmd, "500", NULL, 0); fail_unless(addr == NULL, "Failed to handle null proxy session"); fail_unless(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); fail_unless(addr == NULL, "Failed to handle null proxy session"); fail_unless(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); fail_unless(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); fail_unless(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); fail_unless(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); fail_unless(addr == NULL, "Failed to handle illegal FTP command"); fail_unless(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); fail_unless(addr == NULL, "Failed to handle bad PASV command"); fail_unless(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); fail_unless(addr == NULL, "Failed to handle bad EPSV command"); fail_unless(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); fail_unless(addr == NULL, "Failed to handle bad EPSV command"); fail_unless(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); fail_unless(addr == NULL, "Failed to handle bad PASV command"); fail_unless(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.8/t/api/inet.c000066400000000000000000000262751402074030700172050ustar00rootroot00000000000000/* * ProFTPD - mod_proxy testsuite * Copyright (c) 2015-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. */ /* 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); fail_unless(conn == NULL, "Failed to handle null arguments"); fail_unless(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); fail_unless(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); fail_unless(res < 0, "Failed to handle null pool"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_inet_connect(p, NULL, NULL, 0); fail_unless(res < 0, "Failed to handle null conn"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); conn = pr_inet_create_conn(p, -1, NULL, INPORT_ANY, FALSE); fail_unless(conn != NULL, "Failed to create conn: %s", strerror(errno)); mark_point(); res = proxy_inet_connect(p, conn, NULL, 0); fail_unless(res < 0, "Failed to handle null addr"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); addr = pr_netaddr_get_addr(p, "127.0.0.1", NULL); fail_unless(addr != NULL, "Failed to resolve '127.0.0.1': %s", strerror(errno)); mark_point(); res = proxy_inet_connect(p, conn, addr, 80); fail_unless(res < 0, "Connected to 127.0.0.1#80 unexpectedly"); fail_unless(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); fail_unless(addr != NULL, "Failed to resolve '8.8.8.8': %s", strerror(errno)); conn = pr_inet_create_conn(p, -1, NULL, INPORT_ANY, FALSE); fail_unless(conn != NULL, "Failed to create conn: %s", strerror(errno)); mark_point(); res = proxy_inet_connect(p, conn, addr, 53); fail_if(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); fail_unless(conn != NULL, "Failed to create conn: %s", strerror(errno)); conn->instrm = pr_netio_open(p, PR_NETIO_STRM_CTRL, -1, PR_NETIO_IO_RD); fail_unless(conn->instrm != NULL, "Failed to open ctrl reading stream: %s", strerror(errno)); mark_point(); res = proxy_inet_connect(p, conn, addr, 53); fail_if(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); fail_unless(conn != NULL, "Failed to create conn: %s", strerror(errno)); conn->instrm = pr_netio_open(p, PR_NETIO_STRM_CTRL, -1, PR_NETIO_IO_RD); fail_unless(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); fail_unless(conn->outstrm != NULL, "Failed to open othr writing stream: %s", strerror(errno)); mark_point(); res = proxy_inet_connect(p, conn, addr, 53); fail_if(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); fail_unless(conn != NULL, "Failed to create conn: %s", strerror(errno)); addr = pr_netaddr_get_addr(p, "::1", NULL); fail_unless(addr != NULL, "Failed to resolve '::1': %s", strerror(errno)); mark_point(); res = proxy_inet_connect(p, conn, addr, 80); fail_unless(res < 0, "Connected to 127.0.0.1#80 unexpectedly"); fail_unless(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); fail_unless(conn != NULL, "Failed to create conn: %s", strerror(errno)); addr = pr_netaddr_get_addr(p, "2001:4860:4860::8888", NULL); fail_unless(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. */ fail_unless(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); fail_unless(res < 0, "Failed to handle null arguments"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_inet_listen(p, NULL, 0, 0); fail_unless(res < 0, "Failed to handle null conn"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); conn = pr_inet_create_conn(p, -1, NULL, INPORT_ANY, FALSE); fail_unless(conn != NULL, "Failed to create conn: %s", strerror(errno)); mark_point(); res = proxy_inet_listen(p, conn, 5, 0); fail_unless(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); fail_unless(conn != NULL, "Failed to create conn: %s", strerror(errno)); conn->instrm = pr_netio_open(p, PR_NETIO_STRM_CTRL, -1, PR_NETIO_IO_RD); fail_unless(conn->instrm != NULL, "Failed to open ctrl reading stream: %s", strerror(errno)); mark_point(); res = proxy_inet_listen(p, conn, 5, 0); fail_unless(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); fail_unless(conn != NULL, "Failed to create conn: %s", strerror(errno)); conn->instrm = pr_netio_open(p, PR_NETIO_STRM_CTRL, -1, PR_NETIO_IO_RD); fail_unless(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); fail_unless(conn->outstrm != NULL, "Failed to open othr writing stream: %s", strerror(errno)); mark_point(); res = proxy_inet_listen(p, conn, 5, 0); fail_unless(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); fail_unless(res == NULL, "Failed to handle null arguments"); fail_unless(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); fail_unless(res == NULL, "Failed to handle null conn"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); conn = pr_inet_create_conn(p, -2, NULL, INPORT_ANY, FALSE); fail_unless(conn != NULL, "Failed to create conn: %s", strerror(errno)); res = proxy_inet_openrw(p, conn, NULL, PR_NETIO_STRM_OTHR, -1, -1, -1, FALSE); fail_unless(res == NULL, "Failed to handle null addr"); fail_unless(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); fail_unless(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); fail_unless(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.8/t/api/netio.c000066400000000000000000000336221402074030700173560ustar00rootroot00000000000000/* * ProFTPD - mod_proxy testsuite * Copyright (c) 2015-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. */ /* 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); fail_unless(res < 0, "Failed to handle null stream"); fail_unless(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); fail_unless(nstrm == NULL, "Failed to handle null pool"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); nstrm = proxy_netio_open(p, 77, -1, PR_NETIO_IO_RD); fail_unless(nstrm == NULL, "Failed to handle unsupported stream type"); fail_unless(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); fail_unless(nstrm != NULL, "Failed to handle othr stream type: %s", strerror(errno)); res = proxy_netio_close(nstrm); fail_unless(res < 0, "Successfully closed stream unexpectedly"); fail_unless(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); fail_unless(res < 0, "Failed to handle null stream"); fail_unless(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); fail_unless(nstrm != NULL, "Failed to handle othr stream type: %s", strerror(errno)); res = proxy_netio_poll(nstrm); fail_unless(res < 0, "Polled stream unexpectedly"); fail_unless(errno == EBADF, "Expected EBADF (%d), got '%s' (%d)", EBADF, strerror(errno), errno); res = proxy_netio_close(nstrm); fail_unless(res < 0, "Successfully closed stream unexpectedly"); fail_unless(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); fail_unless(res < 0, "Failed to handle null stream"); fail_unless(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); fail_unless(nstrm != NULL, "Failed to handle othr stream type: %s", strerror(errno)); res = proxy_netio_postopen(nstrm); fail_unless(res == 0, "Failed to postopen stream: %s", strerror(errno)); res = proxy_netio_close(nstrm); fail_unless(res < 0, "Successfully closed stream unexpectedly"); fail_unless(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"); fail_unless(res < 0, "Failed to handle null stream"); fail_unless(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); fail_unless(nstrm != NULL, "Failed to handle othr stream type: %s", strerror(errno)); res = proxy_netio_printf(nstrm, "%d", 7); fail_unless(res < 0, "Failed to handle null stream"); fail_unless(errno == EBADF, "Expected EBADF (%d), got '%s' (%d)", EBADF, strerror(errno), errno); res = proxy_netio_close(nstrm); fail_unless(res < 0, "Successfully closed stream unexpectedly"); fail_unless(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); fail_unless(res < 0, "Failed to handle null stream"); fail_unless(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); fail_unless(nstrm != NULL, "Failed to handle othr stream type: %s", strerror(errno)); mark_point(); res = proxy_netio_read(nstrm, buf, bufsz, 1); fail_unless(res < 0, "Successfully read from stream unexpectedly"); fail_unless(errno == EBADF, "Expected EBADF (%d), got '%s' (%d)", EBADF, strerror(errno), errno); res = proxy_netio_close(nstrm); fail_unless(res < 0, "Successfully closed stream unexpectedly"); fail_unless(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); fail_unless(nstrm != NULL, "Failed to handle othr stream type: %s", strerror(errno)); mark_point(); proxy_netio_reset_poll_interval(nstrm); res = proxy_netio_close(nstrm); fail_unless(res < 0, "Successfully closed stream unexpectedly"); fail_unless(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); fail_unless(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); fail_unless(res < 0, "Successfully closed stream unexpectedly"); fail_unless(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); fail_unless(res < 0, "Failed to handle null stream"); fail_unless(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); fail_unless(nstrm != NULL, "Failed to handle othr stream type: %s", strerror(errno)); mark_point(); res = proxy_netio_shutdown(nstrm, 0); fail_unless(res < 0, "Successfully shutdown stream unexpectedly"); fail_unless(errno == EBADF, "Expected EBADF (%d), got '%s' (%d)", EBADF, strerror(errno), errno); res = proxy_netio_close(nstrm); fail_unless(res < 0, "Successfully closed stream unexpectedly"); fail_unless(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); fail_unless(res < 0, "Failed to handle null stream"); fail_unless(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); fail_unless(nstrm != NULL, "Failed to handle othr stream type: %s", strerror(errno)); mark_point(); res = proxy_netio_write(nstrm, "foo", 1); fail_unless(res < 0, "Wrote to stream unexpectedly"); fail_unless(errno == EBADF, "Expected EBADF (%d), got '%s' (%d)", EBADF, strerror(errno), errno); res = proxy_netio_close(nstrm); fail_unless(res < 0, "Successfully closed stream unexpectedly"); fail_unless(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); fail_unless(netio == NULL, "Failed to handle null function string"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); netio = proxy_netio_unset(strm_type, "foo"); fail_unless(netio == NULL, "Expected null othr NetIO, got %p", netio); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_netio_set(strm_type, netio); fail_unless(res == 0, "Failed to set null othr netio: %s", strerror(errno)); strm_type = PR_NETIO_STRM_CTRL; res = proxy_netio_set(strm_type, netio); fail_unless(res == 0, "Failed to set null ctrl netio: %s", strerror(errno)); res = proxy_netio_set(strm_type, netio); fail_unless(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); fail_unless(res == 0, "Failed to set ctrl netio: %s", strerror(errno)); res = proxy_netio_set(strm_type, netio); fail_unless(res == 0, "Failed to set ctrl netio again: %s", strerror(errno)); netio = proxy_netio_unset(strm_type, "testcase"); fail_unless(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); fail_unless(res == 0, "Failed to set null data netio: %s", strerror(errno)); res = proxy_netio_set(strm_type, netio); fail_unless(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); fail_unless(res == 0, "Failed to set data netio: %s", strerror(errno)); res = proxy_netio_set(strm_type, netio); fail_unless(res == 0, "Failed to set data netio again: %s", strerror(errno)); netio = proxy_netio_unset(strm_type, "testcase"); fail_unless(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); fail_unless(res < 0, "Failed to handle null argument"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_netio_using(strm_type, &netio); fail_unless(res < 0, "Failed to handle othr stream type"); fail_unless(errno == ENOENT, "Expected ENOENT (%d), got '%s' (%d)", ENOENT, strerror(errno), errno); res = proxy_netio_using(PR_NETIO_STRM_CTRL, &netio); fail_unless(res == 0, "Failed to handle ctrl stream type: %s", strerror(errno)); fail_unless(netio == NULL, "Expected null ctrl netio, got %p", netio); res = proxy_netio_using(PR_NETIO_STRM_DATA, &netio); fail_unless(res == 0, "Failed to handle data stream type: %s", strerror(errno)); fail_unless(netio == NULL, "Expected null data netio, got %p", netio); res = proxy_netio_use(strm_type, NULL); fail_unless(res < 0, "Failed to handle othr stream type"); fail_unless(errno == ENOSYS, "Expected ENOSYS (%d), got '%s' (%d)", ENOSYS, strerror(errno), errno); res = proxy_netio_use(PR_NETIO_STRM_CTRL, NULL); fail_unless(res == 0, "Failed to handle ctrl stream type: %s", strerror(errno)); netio = proxy_netio_unset(PR_NETIO_STRM_CTRL, "testcase"); fail_unless(netio == NULL, "Unset ctrl stream unexpectedly"); fail_unless(errno == ENOSYS, "Expected ENOSYS (%d), got '%s' (%d)", ENOSYS, strerror(errno), errno); res = proxy_netio_use(PR_NETIO_STRM_DATA, NULL); fail_unless(res == 0, "Failed to handle data stream type: %s", strerror(errno)); netio = proxy_netio_unset(PR_NETIO_STRM_DATA, "testcase"); fail_unless(netio == NULL, "Unset data stream unexpectedly"); fail_unless(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.8/t/api/random.c000066400000000000000000000054121402074030700175140ustar00rootroot00000000000000/* * ProFTPD - mod_proxy testsuite * 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. */ /* 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); fail_if(num < min, "random number %ld less than minimum %ld", num, min); fail_if(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); fail_if(num < min, "random number %ld less than minimum %ld", num, min); fail_if(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++) { fail_unless(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.8/t/api/reverse.c000066400000000000000000000745521402074030700177220ustar00rootroot00000000000000/* * ProFTPD - mod_proxy testsuite * 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. */ /* 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) { fail_unless(res == 0, "Failed to create tmp directory '%s': %s", test_dir, strerror(errno)); } res = chmod(test_dir, perms); fail_unless(res == 0, "Failed to set perms %04o on directory '%s': %s", perms, test_dir, strerror(errno)); fh = fopen(test_file, "w+"); fail_if(fh == NULL, "Failed to create tmp file '%s': %s", test_file, strerror(errno)); perms = 0660; res = chmod(test_file, perms); fail_unless(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); fail_unless(res < 0, "Failed to handle null pool"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_reverse_free(p); fail_unless(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); fail_unless(res < 0, "Failed to handle null pool"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_reverse_init(p, NULL, flags); fail_unless(res < 0, "Failed to handle null tables dir"); fail_unless(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); fail_unless(res == 0, "Failed to init Reverse API resources: %s", strerror(errno)); res = proxy_reverse_free(p); fail_unless(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); fail_unless(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); fail_unless(res < 0, "Failed to handle null pool"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_reverse_sess_init(p, NULL, NULL, flags); fail_unless(res < 0, "Unexpectedly init'd Reverse API session resources"); fail_unless(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); fail_unless(res < 0, "Unexpectedly init'd Reverse API session resources"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_reverse_sess_free(p, NULL); fail_unless(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); fail_unless(res == 0, "Failed to test ReverseConnectPolicy Random: %s", strerror(errno)); mark_point(); res = proxy_reverse_sess_exit(p); fail_unless(res == 0, "Failed to exit session: %s", strerror(errno)); mark_point(); res = proxy_reverse_free(p); fail_unless(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); fail_unless(res == 0, "Failed to test ReverseConnectPolicy RoundRobin: %s", strerror(errno)); mark_point(); res = proxy_reverse_sess_exit(p); fail_unless(res == 0, "Failed to exit session: %s", strerror(errno)); mark_point(); res = proxy_reverse_free(p); fail_unless(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); fail_unless(res == 0, "Failed to test ReverseConnectPolicy LeastConns: %s", strerror(errno)); mark_point(); res = proxy_reverse_sess_exit(p); fail_unless(res == 0, "Failed to exit session: %s", strerror(errno)); mark_point(); res = proxy_reverse_free(p); fail_unless(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); fail_unless(res == 0, "Failed to test ReverseConnectPolicy LeastResponseTime: %s", strerror(errno)); mark_point(); res = proxy_reverse_sess_exit(p); fail_unless(res == 0, "Failed to exit session: %s", strerror(errno)); mark_point(); res = proxy_reverse_free(p); fail_unless(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); fail_unless(res == 0, "Failed to test ReverseConnectPolicy Shuffle: %s", strerror(errno)); mark_point(); res = proxy_reverse_sess_exit(p); fail_unless(res == 0, "Failed to exit session: %s", strerror(errno)); mark_point(); res = proxy_reverse_free(p); fail_unless(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); fail_unless(res == 0, "Failed to test ReverseConnectPolicy PerUser: %s", strerror(errno)); mark_point(); res = proxy_reverse_sess_exit(p); fail_unless(res == 0, "Failed to exit session: %s", strerror(errno)); mark_point(); res = proxy_reverse_free(p); fail_unless(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); fail_unless(res < 0, "Expected ReverseConnectPolicy PerGroup to fail"); fail_unless(errno == EPERM, "Expected EPERM (%d), got %s (%d)", EPERM, strerror(errno), errno); mark_point(); res = proxy_reverse_sess_exit(p); fail_unless(res == 0, "Failed to exit session: %s", strerror(errno)); mark_point(); res = proxy_reverse_free(p); fail_unless(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); fail_unless(res == 0, "Failed to test ReverseConnectPolicy PerHost: %s", strerror(errno)); mark_point(); res = proxy_reverse_sess_exit(p); fail_unless(res == 0, "Failed to exit session: %s", strerror(errno)); mark_point(); res = proxy_reverse_free(p); fail_unless(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); fail_unless(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); fail_unless(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); fail_unless(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); fail_unless(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); fail_if(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); fail_unless(res < 0, "Handled PASS unexpectedly"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_reverse_sess_exit(p); fail_unless(res == 0, "Failed to exit session: %s", strerror(errno)); mark_point(); res = proxy_reverse_free(p); fail_unless(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 travis, for now. It fails unexpectedly. */ if (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 travis, for now. It fails unexpectedly. */ if (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 travis, for now. It fails unexpectedly. */ if (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 travis, for now. It fails unexpectedly. */ if (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 travis, for now. It fails unexpectedly. */ if (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 travis, for now. It fails unexpectedly. */ if (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 travis, for now. It fails unexpectedly. */ if (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 travis, for now. It fails unexpectedly. */ if (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); fail_unless(uris == NULL, "Failed to handle null arguments"); fail_unless(errno == EINVAL, "Failed to set errno to EINVAL"); uris = proxy_reverse_json_parse_uris(p, NULL, 0); fail_unless(uris == NULL, "Failed to handle null path argument"); fail_unless(errno == EINVAL, "Failed to set errno to EINVAL"); path = "/tmp/test.dat"; uris = proxy_reverse_json_parse_uris(NULL, path, 0); fail_unless(uris == NULL, "Failed to handle null pool argument"); fail_unless(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); fail_unless(uris == NULL, "Failed to handle relative path '%s'", path); fail_unless(errno == EINVAL, "Failed to set errno to EINVAL"); path = test_file; uris = proxy_reverse_json_parse_uris(p, path, 0); fail_unless(uris == NULL, "Failed to handle nonexistent file '%s'", path); fail_unless(errno == ENOENT, "Failed to set errno to ENOENT"); res = mkdir(test_dir, 0777); fail_unless(res == 0, "Failed to create tmp directory '%s': %s", test_dir, strerror(errno)); uris = proxy_reverse_json_parse_uris(p, test_dir, 0); fail_unless(uris == NULL, "Failed to handle directory path '%s'", test_dir); fail_unless(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); fail_unless(res == 0, "Failed to create tmp directory '%s': %s", test_dir, strerror(errno)); res = chmod(test_dir, perms); fail_unless(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); fail_if(fd < 0, "Failed to create tmp file '%s': %s", test_file, strerror(errno)); res = fchmod(fd, perms); fail_unless(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); fail_unless(uris == NULL, "Failed to handle world-writable file '%s'", path); fail_unless(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); fail_unless(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); fail_unless(uris == NULL, "Failed to handle world-writable directory '%s'", test_file); fail_unless(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); fail_if(res < 0, "Failed to write file '%s': %s", test_file, strerror(errno)); mark_point(); uris = proxy_reverse_json_parse_uris(p, test_file, 0); fail_unless(uris != NULL, "Did not receive parsed list as expected"); fail_unless(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); fail_if(res < 0, "Failed to write file '%s': %s", test_file, strerror(errno)); mark_point(); uris = proxy_reverse_json_parse_uris(p, test_file, 0); fail_unless(uris != NULL, "Did not receive parsed list as expected"); fail_unless(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); fail_if(res < 0, "Failed to write file '%s': %s", test_file, strerror(errno)); mark_point(); uris = proxy_reverse_json_parse_uris(p, test_file, 0); fail_unless(uris != NULL, "Did not receive parsed list as expected"); expected = 3; fail_unless(uris->nelts == expected, "Expected %d elements, found %d", expected, uris->nelts); test_cleanup(p); } END_TEST START_TEST (reverse_connect_get_policy_test) { int res; const char *policy; res = proxy_reverse_connect_get_policy(NULL); fail_unless(res < 0, "Failed to handle null argument"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); policy = "foo"; res = proxy_reverse_connect_get_policy(policy); fail_unless(res < 0, "Failed to handle unsupported policy '%s'", policy); fail_unless(errno == ENOENT, "Expected ENOENT (%d), got '%s' (%d)", ENOENT, strerror(errno), errno); policy = "random2"; res = proxy_reverse_connect_get_policy(policy); fail_unless(res < 0, "Failed to handle unsupported policy '%s'", policy); fail_unless(errno == ENOENT, "Expected ENOENT (%d), got '%s' (%d)", ENOENT, strerror(errno), errno); policy = "random"; res = proxy_reverse_connect_get_policy(policy); fail_unless(res == PROXY_REVERSE_CONNECT_POLICY_RANDOM, "Failed to handle supported policy '%s'", policy); policy = "roundrobin"; res = proxy_reverse_connect_get_policy(policy); fail_unless(res == PROXY_REVERSE_CONNECT_POLICY_ROUND_ROBIN, "Failed to handle supported policy '%s'", policy); policy = "shuffle"; res = proxy_reverse_connect_get_policy(policy); fail_unless(res == PROXY_REVERSE_CONNECT_POLICY_SHUFFLE, "Failed to handle supported policy '%s'", policy); policy = "leastconns"; res = proxy_reverse_connect_get_policy(policy); fail_unless(res == PROXY_REVERSE_CONNECT_POLICY_LEAST_CONNS, "Failed to handle supported policy '%s'", policy); policy = "peruser"; res = proxy_reverse_connect_get_policy(policy); fail_unless(res == PROXY_REVERSE_CONNECT_POLICY_PER_USER, "Failed to handle supported policy '%s'", policy); policy = "pergroup"; res = proxy_reverse_connect_get_policy(policy); fail_unless(res == PROXY_REVERSE_CONNECT_POLICY_PER_GROUP, "Failed to handle supported policy '%s'", policy); policy = "perhost"; res = proxy_reverse_connect_get_policy(policy); fail_unless(res == PROXY_REVERSE_CONNECT_POLICY_PER_HOST, "Failed to handle supported policy '%s'", policy); policy = "leastresponsetime"; res = proxy_reverse_connect_get_policy(policy); fail_unless(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(); fail_unless(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); fail_unless(res == FALSE, "Expected false, got %d", res); proxy_sess_state |= PROXY_SESS_STATE_BACKEND_AUTHENTICATED; res = proxy_reverse_have_authenticated(cmd); fail_unless(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_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.8/t/api/session.c000066400000000000000000000151571402074030700177260ustar00rootroot00000000000000/* * ProFTPD - mod_proxy testsuite * Copyright (c) 2016-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. */ /* 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); fail_unless(res < 0, "Failed to handle null arguments"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_session_free(p, NULL); fail_unless(res < 0, "Failed to handle null session"); fail_unless(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); fail_unless(proxy_sess == NULL, "Failed to handle null argument"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); proxy_sess = (struct proxy_session *) proxy_session_alloc(p); fail_unless(proxy_sess != NULL, "Failed to allocate proxy session: %s", strerror(errno)); mark_point(); proxy_session_free(p, proxy_sess); proxy_sess = (struct proxy_session *) proxy_session_alloc(p); fail_unless(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_check_password_test) { int res; const char *user, *passwd; mark_point(); res = proxy_session_check_password(NULL, NULL, NULL); fail_unless(res < 0, "Failed to handle null arguments"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_session_check_password(p, NULL, NULL); fail_unless(res < 0, "Failed to handle null user"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); user = "foo"; mark_point(); res = proxy_session_check_password(p, user, NULL); fail_unless(res < 0, "Failed to handle null passwd"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); passwd = "bar"; mark_point(); res = proxy_session_check_password(p, user, passwd); fail_unless(res < 0, "Failed to handle unknown user"); fail_unless(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); fail_unless(res < 0, "Failed to handle null pool"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_session_setup_env(p, NULL, flags); fail_unless(res < 0, "Failed to handle null user"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL, strerror(errno), errno); session.c = pr_inet_create_conn(p, -1, NULL, INPORT_ANY, FALSE); fail_unless(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); fail_unless(res == 0, "Failed to setup environment: %s", strerror(errno)); fail_unless(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); fail_unless(res == 0, "Failed to setup environment: %s", strerror(errno)); fail_unless(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_check_password_test); tcase_add_test(testcase, session_setup_env_test); suite_add_tcase(suite, testcase); return suite; } proftpd-mod_proxy-0.8/t/api/str.c000066400000000000000000000055001402074030700170420ustar00rootroot00000000000000/* * 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. */ /* 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); fail_unless(res == NULL, "Failed to handle null s1"); mark_point(); s1 = "haystack"; res = proxy_strnstr(s1, NULL, 0); fail_unless(res == NULL, "Failed to handle null s2"); mark_point(); s2 = "needle"; res = proxy_strnstr(s1, s2, 0); fail_unless(res == NULL, "Failed to handle zero len"); mark_point(); len = 2; res = proxy_strnstr(s1, s2, len); fail_unless(res == NULL, "Expected null, got %p for len %lu", res, (unsigned long) len); mark_point(); s1 = " "; res = proxy_strnstr(s1, s2, len); fail_unless(res == NULL, "Expected null, got %p for s1 spaces", res); mark_point(); s1 = "haystack"; s2 = ""; res = proxy_strnstr(s1, s2, len); fail_unless(res == NULL, "Expected null, got %p for s2 empty", res); mark_point(); s1 = "haystack"; s2 = "haystack"; len = 8; res = proxy_strnstr(s1, s2, len); fail_unless(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); fail_unless(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.8/t/api/stubs.c000066400000000000000000000143661402074030700174040ustar00rootroot00000000000000/* * 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.8/t/api/tests.c000066400000000000000000000102121402074030700173700ustar00rootroot00000000000000/* * 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.8/t/api/tests.h000066400000000000000000000055401402074030700174050ustar00rootroot00000000000000/* * 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.8/t/api/tls.c000066400000000000000000000213521402074030700170370ustar00rootroot00000000000000/* * ProFTPD - mod_proxy testsuite * Copyright (c) 2015-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. */ /* 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); fail_unless(res == 0, "Failed to create tmp directory '%s': %s", test_dir, strerror(errno)); res = chmod(test_dir, perms); fail_unless(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); fail_unless(res < 0, "Failed to handle null pool"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_tls_free(p); fail_unless(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); #ifdef PR_USE_OPENSSL fail_unless(res < 0, "Failed to handle null pool"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); res = proxy_tls_init(p, NULL, flags); fail_unless(res < 0, "Failed to handle null tables directory"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_tls_init(p, test_dir, flags); fail_unless(res == 0, "Failed to init TLS API resources: %s", strerror(errno)); res = proxy_tls_free(p); fail_unless(res == 0, "Failed to free TLS API resources: %s", strerror(errno)); #else fail_unless(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); fail_unless(res < 0, "Failed to handle null pool"); fail_unless(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); fail_unless(res == 0, "Failed to init TLS API resources: %s", strerror(errno)); mark_point(); res = proxy_tls_sess_free(p); fail_unless(res == 0, "Failed to release TLS API session resources: %s", strerror(errno)); res = proxy_tls_free(p); fail_unless(res == 0, "Failed to free TLS API resources: %s", strerror(errno)); } END_TEST START_TEST (tls_sess_init_test) { #ifdef PR_USE_OPENSSL int res, flags = PROXY_DB_OPEN_FL_SKIP_VACUUM; res = proxy_tls_sess_init(NULL, flags); fail_unless(res < 0, "Failed to handle null pool"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); res = proxy_tls_sess_init(p, flags); fail_unless(res < 0, "Failed to handle invalid SSL_CTX"); fail_unless(errno == EPERM, "Expected EPERM (%d), got '%s' (%d)", EPERM, strerror(errno), errno); mark_point(); res = proxy_tls_init(p, test_dir, flags); fail_unless(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, flags); fail_unless(res == 0, "Failed to init TLS API session resources: %s", strerror(errno)); mark_point(); res = proxy_tls_sess_free(p); fail_unless(res == 0, "Failed to release TLS API session resources: %s", strerror(errno)); mark_point(); res = proxy_tls_free(p); fail_unless(res == 0, "Failed to release TLS API resources: %s", strerror(errno)); #endif /* PR_USE_OPENSSL */ } END_TEST START_TEST (tls_using_tls_test) { int res, tls; tls = proxy_tls_using_tls(); #ifdef PR_USE_OPENSSL fail_unless(tls == PROXY_TLS_ENGINE_AUTO, "Expected TLS auto, got %d", tls); #else fail_unless(tls == PROXY_TLS_ENGINE_OFF, "Expected TLS off, got %d", tls); #endif /* PR_USE_OPENSSL */ res = proxy_tls_set_tls(7); #ifdef PR_USE_OPENSSL fail_unless(res < 0, "Set TLS unexpectedly"); fail_unless(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(); fail_unless(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(); fail_unless(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(); fail_unless(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(); fail_unless(tls == PROXY_TLS_ENGINE_IMPLICIT, "Expected TLS implicit, got %d", tls); #endif /* PR_USE_OPENSSL */ } END_TEST START_TEST (tls_set_data_prot_test) { int res; res = proxy_tls_set_data_prot(TRUE); #ifdef PR_USE_OPENSSL fail_unless(res == TRUE, "Expected TRUE, got %d", res); res = proxy_tls_set_data_prot(FALSE); fail_unless(res == TRUE, "Expected TRUE, got %d", res); #else fail_unless(res == FALSE, "Expected FALSE, got %d", res); res = proxy_tls_set_data_prot(FALSE); fail_unless(res == FALSE, "Expected FALSE, got %d", res); #endif /* PR_USE_OPENSSL */ res = proxy_tls_set_data_prot(FALSE); fail_unless(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_set_data_prot_test); suite_add_tcase(suite, testcase); return suite; } proftpd-mod_proxy-0.8/t/api/uri.c000066400000000000000000000542361402074030700170430ustar00rootroot00000000000000/* * ProFTPD - mod_proxy 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. */ /* 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_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); fail_unless(res < 0, "Failed to handle null pool"); fail_unless(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); fail_unless(res < 0, "Failed to handle null URI"); fail_unless(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); fail_unless(res < 0, "Failed to handle null scheme"); fail_unless(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); fail_unless(res < 0, "Failed to handle null host"); fail_unless(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); fail_unless(res < 0, "Failed to handle null port"); fail_unless(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); fail_unless(res < 0, "Failed to handle URI missing a colon"); fail_unless(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); fail_unless(res < 0, "Failed to handle unknown/unsupported scheme"); fail_unless(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); fail_unless(res < 0, "Failed to handle illegal scheme character"); fail_unless(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); fail_unless(res < 0, "Failed to handle URI lacking double slashes"); fail_unless(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); fail_unless(res < 0, "Failed to handle URI lacking double slashes"); fail_unless(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); fail_unless(res < 0, "Failed to handle URI lacking double slashes"); fail_unless(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); fail_unless(res < 0, "Failed to handle URI lacking hostname/port"); fail_unless(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); fail_unless(res < 0, "Failed to handle URI using URL encoding"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); scheme = host = username = password = NULL; port = 0; uri = "ftp://foo"; res = proxy_uri_parse(p, uri, &scheme, &host, &port, &username, &password); fail_unless(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); fail_unless(strcmp(scheme, "ftp") == 0, "Expected scheme '%s', got '%s'", "ftp", scheme); fail_unless(strcmp(host, "foo") == 0, "Expected host '%s', got '%s'", "foo", host); fail_unless(port == 21, "Expected port '%u', got '%u'", 21, port); fail_unless(username == NULL, "Expected null username, got '%s'", username); fail_unless(password == NULL, "Expected null password, got '%s'", password); mark_point(); scheme = host = username = password = NULL; port = 0; uri = "ftps://foo"; res = proxy_uri_parse(p, uri, &scheme, &host, &port, &username, &password); fail_unless(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); fail_unless(strcmp(scheme, "ftps") == 0, "Expected scheme '%s', got '%s'", "ftps", scheme); fail_unless(strcmp(host, "foo") == 0, "Expected host '%s', got '%s'", "foo", host); fail_unless(port == 21, "Expected port '%u', got '%u'", 21, port); fail_unless(username == NULL, "Expected null username, got '%s'", username); fail_unless(password == NULL, "Expected null password, got '%s'", password); mark_point(); scheme = host = username = password = NULL; port = 0; uri = "sftp://foo"; res = proxy_uri_parse(p, uri, &scheme, &host, &port, &username, &password); fail_unless(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); fail_unless(strcmp(scheme, "sftp") == 0, "Expected scheme '%s', got '%s'", "sftp", scheme); fail_unless(strcmp(host, "foo") == 0, "Expected host '%s', got '%s'", "foo", host); fail_unless(port == 22, "Expected port '%u', got '%u'", 22, port); fail_unless(username == NULL, "Expected null username, got '%s'", username); fail_unless(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); fail_unless(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); fail_unless(strcmp(scheme, "ftp") == 0, "Expected scheme '%s', got '%s'", "ftp", scheme); fail_unless(strcmp(host, "foo") == 0, "Expected host '%s', got '%s'", "foo", host); fail_unless(port == 2121, "Expected port '%u', got '%u'", 2121, port); fail_unless(username == NULL, "Expected null username, got '%s'", username); fail_unless(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); fail_unless(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); fail_unless(strcmp(scheme, "ftp") == 0, "Expected scheme '%s', got '%s'", "ftp", scheme); fail_unless(strcmp(host, "127.0.0.1") == 0, "Expected host '%s', got '%s'", "127.0.0.1", host); fail_unless(port == 2121, "Expected port '%u', got '%u'", 2121, port); fail_unless(username == NULL, "Expected null username, got '%s'", username); fail_unless(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); fail_unless(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); fail_unless(strcmp(scheme, "ftp") == 0, "Expected scheme '%s', got '%s'", "ftp", scheme); fail_unless(strcmp(host, "::1") == 0, "Expected host '%s', got '%s'", "::1", host); fail_unless(port == 2121, "Expected port '%u', got '%u'", 2121, port); fail_unless(username == NULL, "Expected null username, got '%s'", username); fail_unless(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); fail_unless(res < 0, "Failed to reject URI with bad IPv6 host encoding"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); scheme = host = username = password = NULL; port = 0; uri = "ftps://foo:2121"; res = proxy_uri_parse(p, uri, &scheme, &host, &port, &username, &password); fail_unless(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); fail_unless(strcmp(scheme, "ftps") == 0, "Expected scheme '%s', got '%s'", "ftps", scheme); fail_unless(strcmp(host, "foo") == 0, "Expected host '%s', got '%s'", "foo", host); fail_unless(port == 2121, "Expected port '%u', got '%u'", 2121, port); fail_unless(username == NULL, "Expected null username, got '%s'", username); fail_unless(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); fail_unless(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); fail_unless(strcmp(scheme, "sftp") == 0, "Expected scheme '%s', got '%s'", "sftp", scheme); fail_unless(strcmp(host, "foo") == 0, "Expected host '%s', got '%s'", "foo", host); fail_unless(port == 2222, "Expected port '%u', got '%u'", 2222, port); fail_unless(username == NULL, "Expected null username, got '%s'", username); fail_unless(password == NULL, "Expected null password, got '%s'", password); 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); fail_unless(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); fail_unless(strcmp(scheme, "ftp") == 0, "Expected scheme '%s', got '%s'", "ftp", scheme); fail_unless(strcmp(host, "host") == 0, "Expected host '%s', got '%s'", "host", host); fail_unless(port == 21, "Expected port '%u', got '%u'", 21, port); fail_unless(username != NULL, "Expected non-null username"); fail_unless(strcmp(username, "user") == 0, "Expected username '%s', got '%s'", "user", username); fail_unless(password != NULL, "Expected non-null password"); fail_unless(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); fail_unless(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); fail_unless(strcmp(scheme, "ftp") == 0, "Expected scheme '%s', got '%s'", "ftp", scheme); fail_unless(strcmp(host, "host") == 0, "Expected host '%s', got '%s'", "host", host); fail_unless(port == 21, "Expected port '%u', got '%u'", 21, port); fail_unless(username != NULL, "Expected non-null username"); fail_unless(strcmp(username, "user") == 0, "Expected username '%s', got '%s'", "user", username); fail_unless(password != NULL, "Expected non-null password"); fail_unless(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); fail_unless(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); fail_unless(strcmp(scheme, "ftp") == 0, "Expected scheme '%s', got '%s'", "ftp", scheme); fail_unless(strcmp(host, "host") == 0, "Expected host '%s', got '%s'", "host", host); fail_unless(port == 21, "Expected port '%u', got '%u'", 21, port); fail_unless(username == NULL, "Expected null username, got '%s'", username); fail_unless(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); fail_unless(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); fail_unless(strcmp(scheme, "ftp") == 0, "Expected scheme '%s', got '%s'", "ftp", scheme); fail_unless(strcmp(host, "host") == 0, "Expected host '%s', got '%s'", "host", host); fail_unless(port == 21, "Expected port '%u', got '%u'", 21, port); fail_unless(username != NULL, "Expected non-null username"); fail_unless(strcmp(username, "anonymous") == 0, "Expected username '%s', got '%s'", "anonymous", username); fail_unless(password != NULL, "Expected non-null password"); fail_unless(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); fail_unless(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); fail_unless(strcmp(scheme, "ftp") == 0, "Expected scheme '%s', got '%s'", "ftp", scheme); fail_unless(strcmp(host, "host") == 0, "Expected host '%s', got '%s'", "host", host); fail_unless(port == 21, "Expected port '%u', got '%u'", 21, port); fail_unless(username != NULL, "Expected non-null username"); fail_unless(strcmp(username, "user@domain") == 0, "Expected username '%s', got '%s'", "user@domain", username); fail_unless(password != NULL, "Expected non-null password"); fail_unless(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); fail_unless(res < 0, "Failed to reject URI with too-large port"); fail_unless(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); fail_unless(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); fail_unless(strcmp(scheme, "ftp") == 0, "Expected scheme '%s', got '%s'", "ftp", scheme); fail_unless(strcmp(host, "foo") == 0, "Expected host '%s', got '%s'", "foo", host); fail_unless(port == 2121, "Expected port '%u', got '%u'", 2121, port); fail_unless(username == NULL, "Expected null username, got '%s'", username); fail_unless(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); fail_unless(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); fail_unless(strcmp(scheme, "ftps") == 0, "Expected scheme '%s', got '%s'", "ftps", scheme); fail_unless(strcmp(host, "foo") == 0, "Expected host '%s', got '%s'", "foo", host); fail_unless(port == 2121, "Expected port '%u', got '%u'", 2121, port); fail_unless(username == NULL, "Expected null username, got '%s'", username); fail_unless(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); fail_unless(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); fail_unless(strcmp(scheme, "sftp") == 0, "Expected scheme '%s', got '%s'", "sftp", scheme); fail_unless(strcmp(host, "foo") == 0, "Expected host '%s', got '%s'", "foo", host); fail_unless(port == 2222, "Expected port '%u', got '%u'", 2222, port); fail_unless(username == NULL, "Expected null username, got '%s'", username); fail_unless(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); fail_unless(res < 0, "Failed to reject URI with bad port spec"); fail_unless(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); fail_unless(res < 0, "Failed to reject URI with invalid port spec"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); mark_point(); scheme = host = username = password = NULL; port = 0; uri = "http://host"; res = proxy_uri_parse(p, uri, &scheme, &host, &port, NULL, NULL); fail_unless(res < 0, "Failed to reject URI with unsupported scheme"); fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL, strerror(errno), errno); /* SRV scheme variants */ 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); fail_unless(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); fail_unless(strcmp(scheme, "ftp+srv") == 0, "Expected scheme '%s', got '%s'", "ftp+srv", scheme); fail_unless(strcmp(host, "foo") == 0, "Expected host '%s', got '%s'", "foo", host); fail_unless(port == 0, "Expected port '%u', got '%u'", 0, port); fail_unless(username == NULL, "Expected null username, got '%s'", username); fail_unless(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); fail_unless(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); fail_unless(strcmp(scheme, "ftps+srv") == 0, "Expected scheme '%s', got '%s'", "ftps+srv", scheme); fail_unless(strcmp(host, "foo.bar") == 0, "Expected host '%s', got '%s'", "foo.bar", host); fail_unless(port == 0, "Expected port '%u', got '%u'", 0, port); fail_unless(username == NULL, "Expected null username, got '%s'", username); fail_unless(password == NULL, "Expected null password, got '%s'", password); /* TXT scheme variants */ 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); fail_unless(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); fail_unless(strcmp(scheme, "ftp+txt") == 0, "Expected scheme '%s', got '%s'", "ftp+txt", scheme); fail_unless(strcmp(host, "foo") == 0, "Expected host '%s', got '%s'", "foo", host); fail_unless(port == 0, "Expected port '%u', got '%u'", 0, port); fail_unless(username == NULL, "Expected null username, got '%s'", username); fail_unless(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); fail_unless(res == 0, "Expected successful parsing of URI '%s', got %s", uri, strerror(errno)); fail_unless(strcmp(scheme, "ftps+txt") == 0, "Expected scheme '%s', got '%s'", "ftps+txt", scheme); fail_unless(strcmp(host, "foo.bar") == 0, "Expected host '%s', got '%s'", "foo.bar", host); fail_unless(port == 0, "Expected port '%u', got '%u'", 0, port); fail_unless(username == NULL, "Expected null username, got '%s'", username); fail_unless(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_test); suite_add_tcase(suite, testcase); return suite; } proftpd-mod_proxy-0.8/t/etc/000077500000000000000000000000001402074030700160705ustar00rootroot00000000000000proftpd-mod_proxy-0.8/t/etc/modules/000077500000000000000000000000001402074030700175405ustar00rootroot00000000000000proftpd-mod_proxy-0.8/t/etc/modules/mod_tls/000077500000000000000000000000001402074030700212015ustar00rootroot00000000000000proftpd-mod_proxy-0.8/t/etc/modules/mod_tls/ca-cert.pem000066400000000000000000000046511402074030700232300ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIEZTCCA86gAwIBAgIBADANBgkqhkiG9w0BAQUFADCBvjEQMA4GA1UEAxMHY2Et Y2VydDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT B1NlYXR0bGUxEjAQBgNVBAoTCUNhc3RhZ2xpYTErMCkGA1UECxMiQ2FzdGFnbGlh IFJlc2VhcmNoIGFuZCBEZXZlbG9wbWVudDEUMBIGA1UECxMLVEogU2F1bmRlcnMx HzAdBgkqhkiG9w0BCQEWEHRqQGNhc3RhZ2xpYS5vcmcwHhcNMTAwNjI5MTYyNDEw WhcNMjAwNjI2MTYyNDEwWjCBvjEQMA4GA1UEAxMHY2EtY2VydDELMAkGA1UEBhMC VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1NlYXR0bGUxEjAQBgNV BAoTCUNhc3RhZ2xpYTErMCkGA1UECxMiQ2FzdGFnbGlhIFJlc2VhcmNoIGFuZCBE ZXZlbG9wbWVudDEUMBIGA1UECxMLVEogU2F1bmRlcnMxHzAdBgkqhkiG9w0BCQEW EHRqQGNhc3RhZ2xpYS5vcmcwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALWi DVcsabN5AxcvbwWozT3+sl++c2QBRFVJlRh36BAOl/PhxeJQl5g/EUJiYMu6I1oQ tQPHXZR0ydMOhP83z/ss58fMey4ORUM4YeG4VyVWvp1K2Xb7CkzBUdta15elxTFJ KACJa3TXThHaEU6+DCWAjyLIwLEYgotF63MeCpYlAgMBAAGjggFvMIIBazAMBgNV HRMEBTADAQH/ME4GA1UdEQRHMEWCEXd3dy5jYXN0YWdsaWEub3JngRB0akBjYXN0 YWdsaWEub3JnhwR/AAABhhhodHRwOi8vd3d3LmNhc3RhZ2xpYS5vcmcwHQYDVR0O BBYEFNPnga0QioAG4wJj4tA5rkbgcJubMIHrBgNVHSMEgeMwgeCAFNPnga0QioAG 4wJj4tA5rkbgcJuboYHEpIHBMIG+MRAwDgYDVQQDEwdjYS1jZXJ0MQswCQYDVQQG EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHU2VhdHRsZTESMBAG A1UEChMJQ2FzdGFnbGlhMSswKQYDVQQLEyJDYXN0YWdsaWEgUmVzZWFyY2ggYW5k IERldmVsb3BtZW50MRQwEgYDVQQLEwtUSiBTYXVuZGVyczEfMB0GCSqGSIb3DQEJ ARYQdGpAY2FzdGFnbGlhLm9yZ4IBADANBgkqhkiG9w0BAQUFAAOBgQAG7Wmx9l9B Q2G2mNxFYWZKB7Zxf5pMsckiXCa+4lku5W4apsdLlA8QrkTezsOlpJZQaxwq2R2K FFvIp9XD6L4mmGWjaffp4PDLtbE6d3U3HFmRdKK7f0OLBqSNvsp0v+7D3GzHN3+o pqr76JfEI5biylaX2YXvLZki0ht9qlKt6g== -----END CERTIFICATE----- -----BEGIN RSA PRIVATE KEY----- MIICXAIBAAKBgQC1og1XLGmzeQMXL28FqM09/rJfvnNkAURVSZUYd+gQDpfz4cXi UJeYPxFCYmDLuiNaELUDx12UdMnTDoT/N8/7LOfHzHsuDkVDOGHhuFclVr6dStl2 +wpMwVHbWteXpcUxSSgAiWt0104R2hFOvgwlgI8iyMCxGIKLRetzHgqWJQIDAQAB AoGAVJqyehue9NF2ZhNbNJinWaxM7BorZ7bLXKrUvzwDJY+WqixNX5jItEsUQAbR LbR7iRVlK+hup5sq85u8yaD2yC/juo23QrPnRSEEXM6HFMfJ+/ZuwAqaP48NnxFL fy+AX1zaieePCLj/IcMj64mT9h/avvSBppX98BUzuh/YrAECQQDvuzFtw9SC8L4F rNNggAdtW/LkP6Lx3n0N5JSM7ClqYOM+NnAimhDoR7/y+Qx9nzU6CuAHGUKB3U4u ThaDNC0BAkEAwfWHii64o+00S7XSFQV9K2bx0AhGy6hCLQfwXKuM3btpXMzk34kC HnDB3tzDP+bDx+FlwfFFJdLu9WJyfM8VJQJBALC1VzYFx7vNIQSl5BmZxd/Ci0Pb 9Iw86Ak5mJZX7h9P07GkBvw6fIP7f23mTmK63E0wfvo8kF2Rd3OCc+26pAECQFFj IdjN+hRvOH58cQb5IqjPrbBJiMt0czBKIIYCRj3UokWahH94EjeLwQ4vPI7X2ldJ MVXMU+OnOzYkdT4B9zECQDmnnMHaWJQGfjjQCXFQJvce+aPxV40CTf5opLsvEyPS zNgD0rFPqX9h6ap37NE7qlOb2Ffp577A2AuEeEa0KdM= -----END RSA PRIVATE KEY----- proftpd-mod_proxy-0.8/t/etc/modules/mod_tls/client-cert.pem000066400000000000000000000117471402074030700241270ustar00rootroot00000000000000Certificate: Data: Version: 3 (0x2) Serial Number: 17 (0x11) Signature Algorithm: sha1WithRSAEncryption Issuer: CN=ca-cert, C=US, ST=Washington, L=Seattle, O=Castaglia, OU=Castaglia Research and Development, OU=TJ Saunders/emailAddress=tj@castaglia.org Validity Not Before: Jun 29 16:24:56 2010 GMT Not After : Jun 26 16:24:56 2020 GMT Subject: CN=client-cert, C=US/emailAddress=tj@castaglia.org, O=Castaglia, OU=Castaglia Research and Development, OU=TJ Saunders, ST=Washington Subject Public Key Info: Public Key Algorithm: rsaEncryption RSA Public Key: (1024 bit) Modulus (1024 bit): 00:c0:37:cd:37:10:30:b7:61:a2:54:8e:2e:03:b6: 10:63:55:7b:1d:6f:74:fe:63:50:fe:d1:4b:00:85: 68:1b:ba:34:06:0f:a6:e4:55:07:dd:8d:73:4e:59: fb:f4:5c:fa:eb:bb:84:ea:80:cc:8e:7f:7d:46:6c: c3:e5:56:bc:74:56:62:db:28:59:6f:6b:79:16:66: df:07:91:aa:63:df:0b:9e:1e:85:bd:86:42:a5:a9: 03:c5:b4:e9:91:d7:bb:ac:c9:40:c4:53:65:58:e0: 27:48:78:68:33:54:4d:74:30:59:3b:b5:a4:cc:3a: e5:9a:b9:c0:72:dc:ba:34:67 Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Basic Constraints: CA:FALSE Netscape Comment: CertTool Certificate X509v3 Subject Alternative Name: DNS:familiar.castaglia.org, email:tj@castaglia.org, IP Address:127.0.0.1, URI:http://www.castaglia.org X509v3 Subject Key Identifier: E5:B0:C4:5B:D8:0A:C1:B5:0A:6B:F7:18:64:1D:EB:6D:39:E1:B2:77 X509v3 Authority Key Identifier: keyid:D3:E7:81:AD:10:8A:80:06:E3:02:63:E2:D0:39:AE:46:E0:70:9B:9B DirName:/CN=ca-cert/C=US/ST=Washington/L=Seattle/O=Castaglia/OU=Castaglia Research and Development/OU=TJ Saunders/emailAddress=tj@castaglia.org serial:00 Signature Algorithm: sha1WithRSAEncryption 3f:3e:b9:08:67:f9:8f:ca:30:38:b3:c2:29:73:52:29:52:bd: b7:a3:d3:5a:d2:64:24:29:52:6b:ba:db:72:d8:7c:d6:f3:54: 0e:51:0c:2e:e8:2e:dc:0f:d9:88:9a:bc:67:fa:a1:cb:57:fd: a6:33:50:72:7a:8c:52:56:15:ad:18:7a:1c:04:82:d9:69:d6: f0:5a:60:d4:d2:84:d1:fb:fd:37:14:3e:8c:63:3f:a7:4a:1a: 2e:62:3d:6d:cb:69:68:c4:5e:c2:57:89:5f:f0:45:a8:d5:74: b7:7d:b1:b4:e0:b4:94:20:e7:32:7c:c8:60:93:8e:dc:80:16: 3f:6d -----BEGIN CERTIFICATE----- MIIEfjCCA+egAwIBAgIBETANBgkqhkiG9w0BAQUFADCBvjEQMA4GA1UEAxMHY2Et Y2VydDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT B1NlYXR0bGUxEjAQBgNVBAoTCUNhc3RhZ2xpYTErMCkGA1UECxMiQ2FzdGFnbGlh IFJlc2VhcmNoIGFuZCBEZXZlbG9wbWVudDEUMBIGA1UECxMLVEogU2F1bmRlcnMx HzAdBgkqhkiG9w0BCQEWEHRqQGNhc3RhZ2xpYS5vcmcwHhcNMTAwNjI5MTYyNDU2 WhcNMjAwNjI2MTYyNDU2WjCBsDEUMBIGA1UEAxMLY2xpZW50LWNlcnQxCzAJBgNV BAYTAlVTMR8wHQYJKoZIhvcNAQkBFhB0akBjYXN0YWdsaWEub3JnMRIwEAYDVQQK EwlDYXN0YWdsaWExKzApBgNVBAsTIkNhc3RhZ2xpYSBSZXNlYXJjaCBhbmQgRGV2 ZWxvcG1lbnQxFDASBgNVBAsTC1RKIFNhdW5kZXJzMRMwEQYDVQQIEwpXYXNoaW5n dG9uMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAN803EDC3YaJUji4DthBj VXsdb3T+Y1D+0UsAhWgbujQGD6bkVQfdjXNOWfv0XPrru4TqgMyOf31GbMPlVrx0 VmLbKFlva3kWZt8Hkapj3wueHoW9hkKlqQPFtOmR17usyUDEU2VY4CdIeGgzVE10 MFk7taTMOuWaucBy3Lo0ZwIDAQABo4IBljCCAZIwCQYDVR0TBAIwADAjBglghkgB hvhCAQ0EFhYUQ2VydFRvb2wgQ2VydGlmaWNhdGUwUwYDVR0RBEwwSoIWZmFtaWxp YXIuY2FzdGFnbGlhLm9yZ4EQdGpAY2FzdGFnbGlhLm9yZ4cEfwAAAYYYaHR0cDov L3d3dy5jYXN0YWdsaWEub3JnMB0GA1UdDgQWBBTlsMRb2ArBtQpr9xhkHettOeGy dzCB6wYDVR0jBIHjMIHggBTT54GtEIqABuMCY+LQOa5G4HCbm6GBxKSBwTCBvjEQ MA4GA1UEAxMHY2EtY2VydDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0 b24xEDAOBgNVBAcTB1NlYXR0bGUxEjAQBgNVBAoTCUNhc3RhZ2xpYTErMCkGA1UE CxMiQ2FzdGFnbGlhIFJlc2VhcmNoIGFuZCBEZXZlbG9wbWVudDEUMBIGA1UECxML VEogU2F1bmRlcnMxHzAdBgkqhkiG9w0BCQEWEHRqQGNhc3RhZ2xpYS5vcmeCAQAw DQYJKoZIhvcNAQEFBQADgYEAPz65CGf5j8owOLPCKXNSKVK9t6PTWtJkJClSa7rb cth81vNUDlEMLugu3A/ZiJq8Z/qhy1f9pjNQcnqMUlYVrRh6HASC2WnW8Fpg1NKE 0fv9NxQ+jGM/p0oaLmI9bctpaMRewleJX/BFqNV0t32xtOC0lCDnMnzIYJOO3IAW P20= -----END CERTIFICATE----- -----BEGIN RSA PRIVATE KEY----- MIICXQIBAAKBgQDAN803EDC3YaJUji4DthBjVXsdb3T+Y1D+0UsAhWgbujQGD6bk VQfdjXNOWfv0XPrru4TqgMyOf31GbMPlVrx0VmLbKFlva3kWZt8Hkapj3wueHoW9 hkKlqQPFtOmR17usyUDEU2VY4CdIeGgzVE10MFk7taTMOuWaucBy3Lo0ZwIDAQAB AoGBAJXi7YMifNqCp7KHrBn4vo62+Wnan8A+ccpCKdoeLTBx4l9XlSw3ogqBYoiW YoIKfx+S+fJivR/hCi8AYUFUgV5bkBxkHCdDRDZxPEV7ntKShHVhwdPavOlk49np xTWAvdtKO1sxwMO4FQyFUdtiK7xoHxe+0ln6uusRNjzGo6cBAkEA7xzYUON8y2Cl Dw31Tr3LfkO8an4HaRfh+lHRQz1fX74gpNNlCuW/wv2l9ur2BPb1XebaGazCYkrI 53yv+cSkAwJBAM3LGdLZb/u629XhJdvUUZlAY1rycYXCY1UuWqXGVGwxBhcrTRqD LW2CaxHtm05XJMtBfYMSXSMKMxAiWUOeSs0CQCpp+nD2uUc2IHE4L6BFCFigWUam jlf09Y+6fZ0owMcx6YZzPQQe1tIWvh67dOJSkBmU/nD5dQ2MaHCvbGOontMCQC+k SUIq3GXmiGYnTWBq8skLwvSXE/jnW5+or4uZMoopf0N13s+4dpfXjXoFC+NDAV2c t7XUVoN6JQAjM48X4jECQQDMateInnOkLFOHIN4LSNO5zHmmorm2gLXUL0CYdOJy r/X6TZCD4g5T7Ol4h5RGSnaltG3KBUD2BCASRjxcOyRR -----END RSA PRIVATE KEY----- proftpd-mod_proxy-0.8/t/etc/modules/mod_tls/psk.dat000066400000000000000000000005011402074030700224640ustar00rootroot0000000000000025e262660dc554c2e77f33e3cdc9e7467070d4e6193a6a87f6ba6c08aeb76246278e8ca50d41254315cbc6f5a3afc87922620151a16fffde6b38dd3c8bf71e7ba87d95d880f2d6049d8b148a5883234189b7a5249d787e0b2fe4befe41bfce0b29634f6acde3db0e477d49efb766772a78de99e0ff316e6910b0c83c2875c77a551bf2e940807b5e705516509a00c9fc7d88956f89b6600efc4ca74bd12493a5 proftpd-mod_proxy-0.8/t/etc/modules/mod_tls/server-cert.pem000066400000000000000000000117471402074030700241570ustar00rootroot00000000000000Certificate: Data: Version: 3 (0x2) Serial Number: 16 (0x10) Signature Algorithm: sha1WithRSAEncryption Issuer: CN=ca-cert, C=US, ST=Washington, L=Seattle, O=Castaglia, OU=Castaglia Research and Development, OU=TJ Saunders/emailAddress=tj@castaglia.org Validity Not Before: Jun 29 16:24:34 2010 GMT Not After : Jun 26 16:24:34 2020 GMT Subject: CN=server-cert, C=US/emailAddress=tj@castaglia.org, O=Castaglia, OU=Castaglia Research and Development, OU=TJ Saunders, ST=Washington Subject Public Key Info: Public Key Algorithm: rsaEncryption RSA Public Key: (1024 bit) Modulus (1024 bit): 00:b3:a8:42:3c:58:10:ee:18:d2:32:67:21:63:dc: e7:22:d6:e9:c0:50:08:70:3a:50:cd:9a:87:1b:72: 0b:0d:15:e6:60:c7:09:2d:14:cf:36:da:59:87:ca: 0d:49:ea:ca:48:7f:7a:1d:95:e7:2e:a8:5d:bd:fe: 9c:2b:24:21:49:bf:ed:c6:99:5a:a6:f7:0e:3c:05: 81:1a:35:a9:69:d5:95:db:77:3f:c0:56:62:a4:4b: 83:51:b1:3a:a1:a5:ba:e4:bc:cb:9b:dc:d4:cd:96: a2:d6:86:52:cd:4d:08:4b:50:fd:a9:25:40:6d:75: ea:8b:7a:0d:ba:13:06:95:81 Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Basic Constraints: CA:FALSE Netscape Comment: CertTool Certificate X509v3 Subject Alternative Name: DNS:familiar.castaglia.org, email:tj@castaglia.org, IP Address:127.0.0.1, URI:http://www.castaglia.org X509v3 Subject Key Identifier: 5B:7B:5E:F5:33:1C:69:75:D4:47:D4:C1:26:F3:CB:2A:B0:CF:EC:79 X509v3 Authority Key Identifier: keyid:D3:E7:81:AD:10:8A:80:06:E3:02:63:E2:D0:39:AE:46:E0:70:9B:9B DirName:/CN=ca-cert/C=US/ST=Washington/L=Seattle/O=Castaglia/OU=Castaglia Research and Development/OU=TJ Saunders/emailAddress=tj@castaglia.org serial:00 Signature Algorithm: sha1WithRSAEncryption 2b:5f:59:94:b2:60:b7:c0:26:9b:ec:86:a6:4c:ea:e1:37:36: 92:6d:2f:c5:fc:ad:ca:e6:60:1f:56:88:32:8b:df:1a:67:35: b9:62:1f:4a:d1:dd:77:08:86:81:8c:ed:66:5a:99:35:d2:9d: 30:64:9c:56:21:54:75:cf:bc:94:83:d3:bd:13:a3:2b:b7:ed: a7:31:53:43:6c:08:ee:15:7e:cb:19:9e:1e:fc:03:10:82:6b: a9:0e:12:42:2f:7b:33:fd:3b:c7:59:20:13:93:c1:ce:b5:b1: 77:7f:d3:ae:7e:84:77:f5:ec:b0:1b:51:6b:7a:cf:a8:66:63: f1:46 -----BEGIN CERTIFICATE----- MIIEfjCCA+egAwIBAgIBEDANBgkqhkiG9w0BAQUFADCBvjEQMA4GA1UEAxMHY2Et Y2VydDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT B1NlYXR0bGUxEjAQBgNVBAoTCUNhc3RhZ2xpYTErMCkGA1UECxMiQ2FzdGFnbGlh IFJlc2VhcmNoIGFuZCBEZXZlbG9wbWVudDEUMBIGA1UECxMLVEogU2F1bmRlcnMx HzAdBgkqhkiG9w0BCQEWEHRqQGNhc3RhZ2xpYS5vcmcwHhcNMTAwNjI5MTYyNDM0 WhcNMjAwNjI2MTYyNDM0WjCBsDEUMBIGA1UEAxMLc2VydmVyLWNlcnQxCzAJBgNV BAYTAlVTMR8wHQYJKoZIhvcNAQkBFhB0akBjYXN0YWdsaWEub3JnMRIwEAYDVQQK EwlDYXN0YWdsaWExKzApBgNVBAsTIkNhc3RhZ2xpYSBSZXNlYXJjaCBhbmQgRGV2 ZWxvcG1lbnQxFDASBgNVBAsTC1RKIFNhdW5kZXJzMRMwEQYDVQQIEwpXYXNoaW5n dG9uMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCzqEI8WBDuGNIyZyFj3Oci 1unAUAhwOlDNmocbcgsNFeZgxwktFM822lmHyg1J6spIf3odlecuqF29/pwrJCFJ v+3GmVqm9w48BYEaNalp1ZXbdz/AVmKkS4NRsTqhpbrkvMub3NTNlqLWhlLNTQhL UP2pJUBtdeqLeg26EwaVgQIDAQABo4IBljCCAZIwCQYDVR0TBAIwADAjBglghkgB hvhCAQ0EFhYUQ2VydFRvb2wgQ2VydGlmaWNhdGUwUwYDVR0RBEwwSoIWZmFtaWxp YXIuY2FzdGFnbGlhLm9yZ4EQdGpAY2FzdGFnbGlhLm9yZ4cEfwAAAYYYaHR0cDov L3d3dy5jYXN0YWdsaWEub3JnMB0GA1UdDgQWBBRbe171MxxpddRH1MEm88sqsM/s eTCB6wYDVR0jBIHjMIHggBTT54GtEIqABuMCY+LQOa5G4HCbm6GBxKSBwTCBvjEQ MA4GA1UEAxMHY2EtY2VydDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0 b24xEDAOBgNVBAcTB1NlYXR0bGUxEjAQBgNVBAoTCUNhc3RhZ2xpYTErMCkGA1UE CxMiQ2FzdGFnbGlhIFJlc2VhcmNoIGFuZCBEZXZlbG9wbWVudDEUMBIGA1UECxML VEogU2F1bmRlcnMxHzAdBgkqhkiG9w0BCQEWEHRqQGNhc3RhZ2xpYS5vcmeCAQAw DQYJKoZIhvcNAQEFBQADgYEAK19ZlLJgt8Amm+yGpkzq4Tc2km0vxfytyuZgH1aI MovfGmc1uWIfStHddwiGgYztZlqZNdKdMGScViFUdc+8lIPTvROjK7ftpzFTQ2wI 7hV+yxmeHvwDEIJrqQ4SQi97M/07x1kgE5PBzrWxd3/Trn6Ed/XssBtRa3rPqGZj 8UY= -----END CERTIFICATE----- -----BEGIN RSA PRIVATE KEY----- MIICXAIBAAKBgQCzqEI8WBDuGNIyZyFj3Oci1unAUAhwOlDNmocbcgsNFeZgxwkt FM822lmHyg1J6spIf3odlecuqF29/pwrJCFJv+3GmVqm9w48BYEaNalp1ZXbdz/A VmKkS4NRsTqhpbrkvMub3NTNlqLWhlLNTQhLUP2pJUBtdeqLeg26EwaVgQIDAQAB AoGASudQFlCxXlPC73jIFxa213O7KY80tXXa0p4mzm6R5PbIgnj5fm46pqgKw+6d 87+MbwWXKFajeHSCAQDyo5oAtkdp7pecB2lRDK2QMkpP+ehI8NuC9T+2cLSnHHJc 3YVD/MIhzdMKD/AmbypjzC9YY4wVxthXmw3czAf1pUuYUAECQQDp75h/Vm/6pWT0 2464N0sQLnwz4se09ZxPfto/Gwq0X2fYDWmY5hKFe6fVauCo5Ha+yUVc1JPqWLvx wrctXbMhAkEAxJoZjNtDMMwFXHPtgRjjJN3S332kskFxQ3x+Gk3pS6kxzw7FRtG7 1VGco3YyitaQlbcKuDVnCqcd7PA7b+T2YQJAFyblOsT9NBsmUK1iBI1EWoefNytc hGZCYAO36cLtXkiK6HD7YGx0rM0+IPsA3PYvYlZdDQDk2q6JezXAFzdMwQJADIlT BcNZhnwL/3g49dlzan9mme+2F9PKeCYxGFZNgRCZ530moTxwgMrCdT3tPSMvdwyD 93kYR/qeEuTCtYDhIQJBANa1VX5gVZryNwbXoPxo0H80NXrJLkUt6h16pncMXg/s 9gJsg19El2YsZVeom4RwtTEVLDEbgUXKp1225Z/LX74= -----END RSA PRIVATE KEY----- proftpd-mod_proxy-0.8/t/lib/000077500000000000000000000000001402074030700160635ustar00rootroot00000000000000proftpd-mod_proxy-0.8/t/lib/ProFTPD/000077500000000000000000000000001402074030700173015ustar00rootroot00000000000000proftpd-mod_proxy-0.8/t/lib/ProFTPD/Tests/000077500000000000000000000000001402074030700204035ustar00rootroot00000000000000proftpd-mod_proxy-0.8/t/lib/ProFTPD/Tests/Modules/000077500000000000000000000000001402074030700220135ustar00rootroot00000000000000proftpd-mod_proxy-0.8/t/lib/ProFTPD/Tests/Modules/mod_proxy.pm000066400000000000000000026053431402074030700244060ustar00rootroot00000000000000package 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_list_port_enoent => { 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_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_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_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 { 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}, 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"; } $self->assert($orig_nfds == $restart_nfds, test_msg("Expected $orig_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($orig_nfds == $restart_nfds, test_msg("Expected $orig_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}, 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}, 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, 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}, 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, 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 = '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, 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" 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, 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}, 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, 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, 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 $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, SocketBindTight => 'on', # For dropping privs User => 'nobody', Group => 'nobody', 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->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_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'; 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}, 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(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, 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}, 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, 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_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, 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, 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, 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_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, 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, 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.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, 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('|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, $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_retr_pasv_ascii { 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 $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', 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); $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); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_retr_pasv_binary { 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 $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', 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); $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); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub proxy_reverse_retr_large_file { 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_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: $!"); } } 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 = 120; 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', 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); $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($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_retr_empty_file { 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_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: $!"); } } 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 = 120; 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', 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); $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($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_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: $!"); } } 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}, SocketBindTight => 'on', TimeoutIdle => $timeout_idle, TimeoutLinger => 1, UseIPv6 => 'off', 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 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 { sleep(1); 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 $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 $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', 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->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, $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_stor_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 $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 $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', 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->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, $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_stor_large_file { 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_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, $log_file, $vhost_port); my $timeout_idle = 120; 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', 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); $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($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_stor_empty_file { 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_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, $log_file, $vhost_port); my $timeout_idle = 120; 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', 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); $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($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_stor_pasv_eperm { 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_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: $!"); } } 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 $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', 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); $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($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_stor_port_eperm { 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_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: $!"); } } 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 $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', 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); $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($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_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}, SocketBindTight => 'on', TimeoutIdle => $timeout_idle, TimeoutLinger => 1, UseIPv6 => 'off', 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 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 { sleep(1); 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 $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_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: $!"); } } 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 $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', 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); $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(); my $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($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_rest_stor { 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_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: $!"); } } 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 $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', 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 AllowOverwrite on AllowStoreRestart on 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); $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(); my $expected = 2 * $test_datalen; my $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($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_stat { 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, 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 AllowOverwrite on AllowStoreRestart on 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); $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($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($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_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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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 $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_timeoutstalled = 2; my $frontend_timeout_delay = $frontend_timeoutstalled + 2; my $backend_timeoutstalled = 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, SocketBindTight => 'on', TimeoutStalled => $frontend_timeoutstalled, 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 TimeoutStalled $backend_timeoutstalled 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->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(); my $expected; # Perl's Net::Cmd module uses a very non-standard 599 code to indicate # that the connection is closed $expected = 599; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "Connection closed"; $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_timeoutstalled + 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_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_timeoutstalled = 12; my $backend_timeoutstalled = 2; my $frontend_timeout_delay = $backend_timeoutstalled + 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, SocketBindTight => 'on', TimeoutStalled => $frontend_timeoutstalled, 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 TimeoutStalled $backend_timeoutstalled 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->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(); my $expected; # Perl's Net::Cmd module uses a very non-standard 599 code to indicate # that the connection is closed $expected = 599; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "Connection closed"; $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_timeoutstalled + 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_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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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_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}, 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) { 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"); } my $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}, 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) { 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"); } my $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}, 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'")); 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) { 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")); my $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}, 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 = ''; 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) { 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"); } my $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; 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}, 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 # 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(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) { if ($line =~ /^modify=\S+;(size=\d+;)?type=\S+; (.*?)$/) { $res->{$2} = 1; } } if (scalar(keys(%$res)) == 0) { die("Failed to parse MLSD data"); } my $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}, 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}, 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}); 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"; } 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) { if ($line =~ /^modify=\S+;size=\d+;type=\S+; (.*?)$/) { $res->{$1} = 1; } } if (scalar(keys(%$res)) == 0) { die("Failed to parse MLSD data"); } my $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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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() }; 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 > 7.9, test_msg("Expected > 8 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, 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}, 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(); 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_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, 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"); } 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_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, 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}, 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}, 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}, 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}, 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, 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")); my $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, 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")); my $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, 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, 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")); my $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, 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")); my $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, 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")); my $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, 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")); my $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, 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")); my $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, 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")); my $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, 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 = <$fh>; close($fh); chomp($next_line); if (length($next_line) > 0) { 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, 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, 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, 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, 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, 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, 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, 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, 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, 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 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}, 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}, SocketBindTight => 'on', UseIPv6 => '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_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, 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, 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}, 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}, 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); 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 < 2, 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 $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, 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); $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_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, 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}, 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, 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, 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 $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, 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 { # Set the appropriate Net::FTP environment variables for "FTP firewalls". $ENV{FTP_FIREWALL} = "127.0.0.1:$vhost_port"; $ENV{FTP_FIREWALL_TYPE} = 1; 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_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}, 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 $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: Operation timed out"; $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_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}, 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, 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}, 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: 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_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}, 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, 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_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, 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 ($resp_code, $resp_msg) = $client->feat(); my $expected; $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, 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, 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, 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 $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, 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->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) }; 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_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, 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, 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->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, 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); eval { $client->user($user) }; unless ($@) { die("Extra USER 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 = 'Bad sequence of commands'; $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 $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_addr = '1.2.3.4:5678'; eval { $client->login("$user\@$bad_addr", $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 $bad_addr: Operation timed out"; $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_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: 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 { sleep(1); 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); eval { $client->user($user) }; unless ($@) { die("Extra USER 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 = 'Bad sequence of commands'; $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 $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@proftpd.org'; 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_failed_bad_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/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_addr = '1.2.3.4:5678'; $client->login("$proxy_user\@$bad_addr", $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 $bad_addr: Operation timed out"; $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_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: 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, 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, 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}, 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}, 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.8/t/lib/ProFTPD/Tests/Modules/mod_proxy/000077500000000000000000000000001402074030700240335ustar00rootroot00000000000000proftpd-mod_proxy-0.8/t/lib/ProFTPD/Tests/Modules/mod_proxy/ban.pm000066400000000000000000000545031402074030700251400ustar00rootroot00000000000000package 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 5 secs BanOnEvent => 'MaxLoginAttempts 1/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); 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) }; unless ($@) { die("Connect succeeded unexpectedly"); } 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'")); }; 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 = ""; $self->assert($expected eq $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 = ""; $self->assert($expected eq $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 = ""; $self->assert($expected eq $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.8/t/lib/ProFTPD/Tests/Modules/mod_proxy/redis.pm000066400000000000000000001305441402074030700255060ustar00rootroot00000000000000package 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)], }, proxy_reverse_config_redis_connect_policy_leastconns => { order => ++$order, test_class => [qw(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 $config = { RedisEngine => 'on', RedisLog => $log_file, RedisServer => '127.0.0.1:6379', }; 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, 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_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 $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 $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_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.8/t/lib/ProFTPD/Tests/Modules/mod_proxy/reverse/000077500000000000000000000000001402074030700255065ustar00rootroot00000000000000proftpd-mod_proxy-0.8/t/lib/ProFTPD/Tests/Modules/mod_proxy/reverse/ipv6.pm000066400000000000000000000517161402074030700267420ustar00rootroot00000000000000package 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 Sys::HostAddr; 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(forking reverse)], }, proxy_reverse_ipv6_list_port => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_ipv6_epsv => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_ipv6_eprt_ipv4 => { order => ++$order, test_class => [qw(forking reverse)], }, proxy_reverse_ipv6_eprt_ipv6 => { order => ++$order, test_class => [qw(feature_ipv6 forking 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://\[::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($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($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($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($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 $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 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('|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, $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.8/t/lib/ProFTPD/Tests/Modules/mod_proxy/sql.pm000066400000000000000000001541021402074030700251730ustar00rootroot00000000000000package 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 $config = { RedisEngine => 'on', RedisLog => $log_file, RedisServer => '127.0.0.1:6379', }; 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; push(@$proxy_config, "ProxyDatastore Redis 127.0.0.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 $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++) { 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); $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 { 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 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', SQLConnectInfo => $db_file, 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'"); my $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', SQLConnectInfo => $db_file, 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'"); my $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', SQLConnectInfo => $db_file, 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'"); my $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', SQLConnectInfo => $db_file, 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'"); my $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.8/t/lib/ProFTPD/Tests/Modules/mod_proxy/tls.pm000066400000000000000000005365141402074030700252110ustar00rootroot00000000000000package 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)], }, 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)], }, proxy_reverse_backend_tls_login_failed_missing_client_auth => { 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_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_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_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_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)], }, proxy_forward_frontend_backend_tls_list_pasv_prot_c => { 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, }; 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, 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 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('t/etc/modules/mod_tls/server-cert.pem'); my $ca_file = File::Spec->rel2abs('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, TLSProtocol => 'SSLv3 TLSv1', 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('t/etc/modules/mod_tls/server-cert.pem'); my $ca_file = File::Spec->rel2abs('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, TLSProtocol => 'SSLv3 TLSv1', 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('t/etc/modules/mod_tls/server-cert.pem'); my $client_cert_file = File::Spec->rel2abs('t/etc/modules/mod_tls/client-cert.pem'); my $ca_file = File::Spec->rel2abs('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, TLSProtocol => 'SSLv3 TLSv1', 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('t/etc/modules/mod_tls/server-cert.pem'); my $ca_file = File::Spec->rel2abs('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, TLSProtocol => 'SSLv3 TLSv1', 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 $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('t/etc/modules/mod_tls/server-cert.pem'); my $ca_file = File::Spec->rel2abs('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 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 TLSProtocol SSLv3 TLSv1 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); $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_implicit_login { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $cert_file = File::Spec->rel2abs('t/etc/modules/mod_tls/server-cert.pem'); my $ca_file = File::Spec->rel2abs('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(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); } # 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('t/etc/modules/mod_tls/server-cert.pem'); my $ca_file = File::Spec->rel2abs('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 TLSProtocol SSLv3 TLSv1 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('t/etc/modules/mod_tls/server-cert.pem'); my $ca_file = File::Spec->rel2abs('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, 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); } 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 $client_cert_file = File::Spec->rel2abs('t/etc/modules/mod_tls/client-cert.pem'); my $server_cert_file = File::Spec->rel2abs('t/etc/modules/mod_tls/server-cert.pem'); my $ca_file = File::Spec->rel2abs('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 TLSProtocol SSLv3 TLSv1 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 $client_cert_file = File::Spec->rel2abs('t/etc/modules/mod_tls/client-cert.pem'); my $server_cert_file = File::Spec->rel2abs('t/etc/modules/mod_tls/server-cert.pem'); my $ca_file = File::Spec->rel2abs('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"; 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 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('t/etc/modules/mod_tls/server-cert.pem'); my $ca_file = File::Spec->rel2abs('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'; 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 TLSProtocol SSLv3 TLSv1 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 $client_cert_file = File::Spec->rel2abs('t/etc/modules/mod_tls/client-cert.pem'); my $server_cert_file = File::Spec->rel2abs('t/etc/modules/mod_tls/server-cert.pem'); my $ca_file = File::Spec->rel2abs('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 TLSProtocol SSLv3 TLSv1 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_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('t/etc/modules/mod_tls/server-cert.pem'); my $ca_file = File::Spec->rel2abs('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 TLSProtocol SSLv3 TLSv1 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 $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('t/etc/modules/mod_tls/server-cert.pem'); my $ca_file = File::Spec->rel2abs('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, TLSProtocol => 'SSLv3 TLSv1', TLSRequired => 'on', TLSRSACertificateFile => $cert_file, TLSCACertificateFile => $ca_file, TLSOptions => 'NoCertRequest 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"); 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 ProxyReverseServers ftp://127.0.0.1:$vhost_port ProxyReverseConnectPolicy RoundRobin ProxyTLSEngine auto ProxyTLSCACertificateFile $ca_file ProxyTLSOptions EnableDiags TLSEngine on TLSLog $log_file TLSProtocol SSLv3 TLSv1 TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file TLSVerifyClient off TLSVerifyServer off TLSOptions EnableDiags NoCertRequest 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 TLSProtocol SSLv3 TLSv1 TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file TLSVerifyClient off TLSVerifyServer off TLSOptions EnableDiags NoCertRequest 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_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('t/etc/modules/mod_tls/server-cert.pem'); my $ca_file = File::Spec->rel2abs('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, TLSProtocol => 'SSLv3 TLSv1', TLSRequired => 'on', TLSRSACertificateFile => $cert_file, TLSCACertificateFile => $ca_file, TLSOptions => 'NoCertRequest 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 TLSEngine on TLSLog $log_file TLSProtocol SSLv3 TLSv1 TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file TLSVerifyClient off TLSVerifyServer off TLSOptions EnableDiags NoCertRequest 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 TLSProtocol SSLv3 TLSv1 TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file TLSVerifyClient off TLSVerifyServer off TLSOptions EnableDiags NoCertRequest 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 TLSProtocol SSLv3 TLSv1 TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file TLSVerifyClient off TLSVerifyServer off TLSOptions EnableDiags NoCertRequest 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 $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('t/etc/modules/mod_tls/server-cert.pem'); my $ca_file = File::Spec->rel2abs('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 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, SocketBindTight => 'on', IfModules => { 'mod_proxy.c' => $proxy_config, 'mod_tls.c' => { TLSEngine => 'on', TLSLog => $log_file, TLSProtocol => 'SSLv3 TLSv1', TLSRequired => 'on', TLSRSACertificateFile => $cert_file, TLSCACertificateFile => $ca_file, TLSOptions => 'NoCertRequest 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")) { 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 TLSProtocol SSLv3 TLSv1 TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file TLSVerifyClient off TLSVerifyServer off TLSOptions EnableDiags NoCertRequest 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. 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, 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_abort { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'proxy'); my $cert_file = File::Spec->rel2abs('t/etc/modules/mod_tls/server-cert.pem'); my $ca_file = File::Spec->rel2abs('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}, TLSProtocol => 'SSLv3 TLSv1', 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} TLSProtocol SSLv3 TLSv1 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('t/etc/modules/mod_tls/server-cert.pem'); my $ca_file = File::Spec->rel2abs('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, TLSProtocol => 'SSLv3 TLSv1', 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 TLSProtocol SSLv3 TLSv1 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('t/etc/modules/mod_tls/server-cert.pem'); my $ca_file = File::Spec->rel2abs('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('t/etc/modules/mod_tls/server-cert.pem'); my $ca_file = File::Spec->rel2abs('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('t/etc/modules/mod_tls/server-cert.pem'); my $ca_file = File::Spec->rel2abs('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_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('t/etc/modules/mod_tls/server-cert.pem'); my $ca_file = File::Spec->rel2abs('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 TLSProtocol SSLv3 TLSv1 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 TLSProtocol SSLv3 TLSv1 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('t/etc/modules/mod_tls/server-cert.pem'); my $ca_file = File::Spec->rel2abs('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"; 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 TLSProtocol SSLv3 TLSv1 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 TLSProtocol SSLv3 TLSv1 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('t/etc/modules/mod_tls/server-cert.pem'); my $ca_file = File::Spec->rel2abs('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 TLSProtocol SSLv3 TLSv1 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 TLSProtocol SSLv3 TLSv1 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('t/etc/modules/mod_tls/server-cert.pem'); my $ca_file = File::Spec->rel2abs('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 TLSProtocol SSLv3 TLSv1 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('t/etc/modules/mod_tls/server-cert.pem'); my $ca_file = File::Spec->rel2abs('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} TLSProtocol SSLv3 TLSv1 # 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('t/etc/modules/mod_tls/server-cert.pem'); my $ca_file = File::Spec->rel2abs('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}, TLSProtocol => 'SSLv3 TLSv1', 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} TLSProtocol SSLv3 TLSv1 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_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('t/etc/modules/mod_tls/server-cert.pem'); my $ca_file = File::Spec->rel2abs('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, TLSProtocol => 'SSLv3 TLSv1', 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('t/etc/modules/mod_tls/server-cert.pem'); my $ca_file = File::Spec->rel2abs('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 TLSProtocol SSLv3 TLSv1 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'); my $cert_file = File::Spec->rel2abs('t/etc/modules/mod_tls/server-cert.pem'); my $ca_file = File::Spec->rel2abs('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(2); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $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('t/etc/modules/mod_tls/server-cert.pem'); my $ca_file = File::Spec->rel2abs('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; 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 TLSProtocol SSLv3 TLSv1 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: 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($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_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('t/etc/modules/mod_tls/server-cert.pem'); my $ca_file = File::Spec->rel2abs('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('t/etc/modules/mod_tls/server-cert.pem'); my $ca_file = File::Spec->rel2abs('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 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_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('t/etc/modules/mod_tls/server-cert.pem'); my $ca_file = File::Spec->rel2abs('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 proxy_forward_frontend_backend_tls_list_pasv_prot_c { 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('t/etc/modules/mod_tls/server-cert.pem'); my $ca_file = File::Spec->rel2abs('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->{ProxyTLSTransferProtectionPolicy} = 'Client'; $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 => 'ctrl', 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 ctrl 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, # 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("$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 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('t/etc/modules/mod_tls/server-cert.pem'); my $ca_file = File::Spec->rel2abs('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} TLSProtocol SSLv3 TLSv1 # 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('t/etc/modules/mod_tls/server-cert.pem'); my $ca_file = File::Spec->rel2abs('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}, TLSProtocol => 'SSLv3 TLSv1', 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} TLSProtocol SSLv3 TLSv1 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.8/t/lib/ProFTPD/Tests/Modules/mod_proxy/tls/000077500000000000000000000000001402074030700246355ustar00rootroot00000000000000proftpd-mod_proxy-0.8/t/lib/ProFTPD/Tests/Modules/mod_proxy/tls/redis.pm000066400000000000000000000247701402074030700263130ustar00rootroot00000000000000package 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('t/etc/modules/mod_tls/server-cert.pem'); my $ca_file = File::Spec->rel2abs('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 $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 => '127.0.0.1:6379', 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('t/etc/modules/mod_tls/server-cert.pem'); my $ca_file = File::Spec->rel2abs('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 $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 => '127.0.0.1:6379', 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.8/t/modules/000077500000000000000000000000001402074030700167655ustar00rootroot00000000000000proftpd-mod_proxy-0.8/t/modules/mod_proxy.t000066400000000000000000000002651402074030700211750ustar00rootroot00000000000000#!/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.8/t/modules/mod_proxy/000077500000000000000000000000001402074030700210055ustar00rootroot00000000000000proftpd-mod_proxy-0.8/t/modules/mod_proxy/ban.t000066400000000000000000000002721402074030700217330ustar00rootroot00000000000000#!/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.8/t/modules/mod_proxy/redis.t000066400000000000000000000002741402074030700223030ustar00rootroot00000000000000#!/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.8/t/modules/mod_proxy/reverse/000077500000000000000000000000001402074030700224605ustar00rootroot00000000000000proftpd-mod_proxy-0.8/t/modules/mod_proxy/reverse/ipv6.t000066400000000000000000000003041402074030700235260ustar00rootroot00000000000000#!/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.8/t/modules/mod_proxy/sql.t000066400000000000000000000002721402074030700217720ustar00rootroot00000000000000#!/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.8/t/modules/mod_proxy/tls.t000066400000000000000000000002721402074030700217750ustar00rootroot00000000000000#!/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.8/t/modules/mod_proxy/tls/000077500000000000000000000000001402074030700216075ustar00rootroot00000000000000proftpd-mod_proxy-0.8/t/modules/mod_proxy/tls/redis.t000066400000000000000000000003011402074030700230740ustar00rootroot00000000000000#!/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");