pax_global_header 0000666 0000000 0000000 00000000064 14757370167 0014533 g ustar 00root root 0000000 0000000 52 comment=d477503556a1e39deba984fed4a1b4964318027a
proftpd-mod_proxy-0.9.5/ 0000775 0000000 0000000 00000000000 14757370167 0015242 5 ustar 00root root 0000000 0000000 proftpd-mod_proxy-0.9.5/.codeql.yml 0000664 0000000 0000000 00000001471 14757370167 0017315 0 ustar 00root root 0000000 0000000 ---
query-filters:
- exclude:
# See: https://codeql.github.com/codeql-query-help/cpp/cpp-commented-out-code/
id: cpp/commented-out-code
- exclude:
# See: https://codeql.github.com/codeql-query-help/cpp/cpp-long-switch/
id: cpp/long-switch
- exclude:
# See: https://codeql.github.com/codeql-query-help/cpp/cpp-empty-if/
id: cpp/empty-if
- exclude:
# See: https://codeql.github.com/codeql-query-help/cpp/cpp-loop-variable-changed/
id: cpp/loop-variable-changed
- exclude:
# See: https://codeql.github.com/codeql-query-help/cpp/cpp-missing-check-scanf/
id: cpp/missing-check-scanf
- exclude:
# See: https://codeql.github.com/codeql-query-help/cpp/cpp-poorly-documented-function/
id: cpp/poorly-documented-function
paths:
- contrib/mod_proxy
proftpd-mod_proxy-0.9.5/.gitattributes 0000664 0000000 0000000 00000000062 14757370167 0020133 0 ustar 00root root 0000000 0000000 *.pl linguist-language=C
*.pm linguist-language=C
proftpd-mod_proxy-0.9.5/.github/ 0000775 0000000 0000000 00000000000 14757370167 0016602 5 ustar 00root root 0000000 0000000 proftpd-mod_proxy-0.9.5/.github/workflows/ 0000775 0000000 0000000 00000000000 14757370167 0020637 5 ustar 00root root 0000000 0000000 proftpd-mod_proxy-0.9.5/.github/workflows/ci.yml 0000664 0000000 0000000 00000016214 14757370167 0021761 0 ustar 00root root 0000000 0000000 name: CI
on:
push:
branches:
- master
paths-ignore:
- '*.html'
- '*.md'
- 't/etc/**'
- 't/lib/**'
- 't/modules/**'
pull_request:
branches:
- master
schedule:
- cron: '11 1 * * 0'
jobs:
build:
runs-on: ubuntu-latest
env:
# We need to avoid using NodeJS v20, because it doesn't work with
# older glibc versions. See:
# https://github.com/actions/checkout/issues/1809.
ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true
CI: true
strategy:
matrix:
compiler:
- clang
- gcc
container:
- almalinux:8
- alpine:3.18
- ubuntu:22.04
container: ${{ matrix.container }}
steps:
- name: Checkout ProFTPD
uses: actions/checkout@v3
with:
repository: proftpd/proftpd
path: proftpd
- name: Checkout module source code
uses: actions/checkout@v3
with:
path: proftpd/contrib/mod_proxy
- name: Whitespace check
if: ${{ matrix.container == 'ubuntu:22.04' }}
run: |
apt-get update -qq
apt-get install -y git
cd proftpd/contrib/mod_proxy
if [[ -n $(git diff --check HEAD^) ]]; then
echo "You must remove whitespace before submitting a pull request"
echo ""
git diff --check HEAD^
exit 1
fi
- name: Install Alpine packages
if: ${{ matrix.container == 'alpine:3.18' }}
run: |
apk update
# for builds
apk add bash build-base clang compiler-rt gcc make zlib-dev
# for unit tests
apk add check check-dev subunit subunit-dev
# for Redis support
apk add hiredis-dev
# for OpenSSL support
apk add openssl openssl-dev
# for SQLite support
apk add sqlite sqlite-dev
# for debugging
clang --version
gcc --version
openssl version -a
- name: Install RPM packages
if: ${{ matrix.container == 'almalinux:8' }}
run: |
# Need to add other repos for e.g. libsodium
yum install -y dnf-plugins-core epel-release clang gcc make zlib-devel
# for unit tests
yum install -y check-devel https://cbs.centos.org/kojifiles/packages/subunit/1.4.0/1.el8/x86_64/subunit-1.4.0-1.el8.x86_64.rpm https://cbs.centos.org/kojifiles/packages/subunit/1.4.0/1.el8/x86_64/subunit-devel-1.4.0-1.el8.x86_64.rpm
# for Redis support
yum install -y hiredis-devel
# for OpenSSL support
yum install -y openssl openssl-devel
# for SQLite support
yum install -y sqlite-devel
# for debugging
clang --version
gcc --version
openssl version -a
- name: Install Ubuntu packages
if: ${{ matrix.container == 'ubuntu:22.04' }}
run: |
apt-get update -qq
# for builds
apt-get install -y clang gcc make
# for unit tests
apt-get install -y check libsubunit-dev
# for Redis support
apt-get install -y libhiredis-dev
# for OpenSSL support
apt-get install -y libssl-dev
# for SQLite support
apt-get install -y libsqlite3-dev sqlite3
# for test code coverage
apt-get install -y lcov ruby
gem install coveralls-lcov
# for HTML validation
apt-get install -y tidy
# for debugging
clang --version
gcc --version
openssl version -a
- name: Prepare code coverage
if: ${{ matrix.container == 'ubuntu:22.04' }}
run: |
lcov --directory proftpd --zerocounters
- name: Build without Redis, SSL support
env:
CC: ${{ matrix.compiler }}
run: |
cd proftpd
./configure LIBS="-lm -lsubunit -lrt -pthread" --enable-devel=coverage --enable-tests --with-modules=mod_proxy
make
- name: Build with Redis, without SSL support
env:
CC: ${{ matrix.compiler }}
run: |
cd proftpd
make clean
./configure LIBS="-lm -lsubunit -lrt -pthread" --enable-devel=coverage --enable-redis --enable-tests --with-modules=mod_proxy
make
- name: Build with Redis, SSL support as shared module
env:
CC: ${{ matrix.compiler }}
run: |
cd proftpd
make clean
./configure LIBS="-lm -lsubunit -lrt -pthread" --enable-devel=coverage --enable-dso --enable-redis --enable-tests --with-shared=mod_tls:mod_proxy
make
- name: Build with Redis, SSL support as static module
env:
CC: ${{ matrix.compiler }}
run: |
cd proftpd
make clean
./configure LIBS="-lm -lsubunit -lrt -pthread" --enable-devel=coverage --enable-redis --enable-tests --with-modules=mod_tls:mod_proxy
make
- name: Run unit tests
env:
CC: ${{ matrix.compiler }}
# Note: Skip the unit tests on Alpine
if: ${{ matrix.container != 'alpine:3.18' }}
run: |
cd proftpd/contrib/mod_proxy
make TEST_VERBOSE=1 check
- name: Install with static modules
run: |
cd proftpd
make install
- name: Build with shared modules
env:
CC: ${{ matrix.compiler }}
run: |
cd proftpd
make clean
./configure LIBS="-lm -lsubunit -lrt -pthread" --enable-devel --enable-dso --with-shared=mod_proxy
make
- name: Install with shared modules
run: |
cd proftpd
make install
# https://github.com/google/sanitizers/wiki/AddressSanitizer
# https://github.com/google/sanitizers/wiki/AddressSanitizerLeakSanitizer
# https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html
#
# NOTE: Using MemorySanitizer is desirable, but currently unusable since
# libcheck is not instrumented, resulting in unsuppressible false
# positives.
- name: Run unit tests under asan+lsan+ubsan
env:
ASAN_OPTIONS: abort_on_error=1,check_initialization_order=true,debug=true,detect_invalid_pointer_pairs=2,detect_leaks=1,detect_stack_use_after_return=true,strict_string_checks=true,verbosity=0
CC: ${{ matrix.compiler }}
CFLAGS: -fsanitize=address,undefined
LDFLAGS: -fsanitize=address,undefined
if: ${{ matrix.compiler == 'clang' && matrix.container == 'ubuntu:22.04' }}
run: |
cd proftpd
make clean
./configure LIBS="-lm -lsubunit -lrt -pthread" --enable-devel --enable-redis --enable-tests --with-modules=mod_tls:mod_proxy
make
cd contrib/mod_proxy
export ASAN_SYMBOLIZER_PATH=$(readlink -f $(which llvm-symbolizer-10))
make TEST_VERBOSE=1 check
- name: Check HTML docs
if: ${{ matrix.container == 'ubuntu:22.04' }}
run: |
cd proftpd/contrib/mod_proxy
for f in $(/bin/ls *.html); do echo "Processing $f"; tidy -errors -omit -q $f; done || exit 0
proftpd-mod_proxy-0.9.5/.github/workflows/codeql.yml 0000664 0000000 0000000 00000005052 14757370167 0022633 0 ustar 00root root 0000000 0000000 name: CodeQL
on:
push:
branches:
- master
paths-ignore:
- '*.html'
- '**/*.md'
- '**/doc/*'
- 't/etc/**'
- 't/lib/**'
- 't/modules/**'
pull_request:
branches:
- master
paths-ignore:
- '**/*.md'
- '**/doc/*'
schedule:
- cron: "37 17 * * 2"
jobs:
analyze:
name: CodeQL Analysis
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: true
matrix:
language:
- cpp
steps:
- name: Checkout ProFTPD
uses: actions/checkout@v4
with:
repository: proftpd/proftpd
- name: Checkout mod_proxy
uses: actions/checkout@v4
with:
path: contrib/mod_proxy
- name: Install Packages
run: |
sudo apt-get update
sudo apt-get install --yes libhiredis-dev libsqlite3-dev libssl-dev libsodium-dev zlib1g-dev
- name: Configure
run: |
./configure --enable-redis --with-modules=mod_sftp:mod_tls:mod_proxy
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
config-file: contrib/mod_proxy/.codeql.yml
queries: +security-and-quality
source-root: contrib/mod_proxy
- name: Build
run: |
make
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{ matrix.language }}"
checkout_path: contrib/mod_proxy
output: sarif-results
upload: false
- name: Filter CodeQL SARIF
uses: advanced-security/filter-sarif@v1
with:
patterns: |
-**/lib/proxy/dns.c:cpp/large-parameter
-**/lib/proxy/ssh.c:cpp/stack-address-escape
-**/lib/proxy/ssh/compress.c:cpp/stack-address-escape
-**/lib/proxy/ssh/compress.c:cpp/uncontrolled-allocation-size
-**/lib/proxy/ssh/packet.c:cpp/stack-address-escape
-**/lib/proxy/ssh/packet.c:cpp/uncontrolled-allocation-size
-**/lib/proxy/ssh/umac.c
-**/lib/proxy/ssh/umac128.c
input: "sarif-results/${{ matrix.language }}.sarif"
output: "sarif-results/${{ matrix.language }}.sarif"
- name: Upload CodeQL SARIF
uses: github/codeql-action/upload-sarif@v2
with:
checkout_path: contrib/mod_proxy
sarif_file: "sarif-results/${{ matrix.language }}.sarif"
proftpd-mod_proxy-0.9.5/.github/workflows/regressions.yml 0000664 0000000 0000000 00000007011 14757370167 0023724 0 ustar 00root root 0000000 0000000 name: Regression Tests
on:
push:
branches:
- master
paths-ignore:
- '*.html'
- '*.md'
pull_request:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
env:
# We need to avoid using NodeJS v20, because it doesn't work with
# older glibc versions. See:
# https://github.com/actions/checkout/issues/1809.
ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true
DEBIAN_FRONTEND: noninteractive
REDIS_HOST: redis
TZ: America/Los_Angeles
services:
redis:
# Docker Hub image
image: redis:6-alpine
# Set health checks to wait until redis has started
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
strategy:
matrix:
compiler:
- gcc
container:
- ubuntu:22.04
container: ${{ matrix.container }}
steps:
- name: Checkout ProFTPD
uses: actions/checkout@v3
with:
repository: proftpd/proftpd
path: proftpd
- name: Checkout mod_proxy_protocol source code
uses: actions/checkout@v3
with:
repository: Castaglia/proftpd-mod_proxy_protocol
path: mod_proxy_protocol
- name: Checkout module source code
uses: actions/checkout@v3
with:
path: proftpd/contrib/mod_proxy
- name: Install Ubuntu packages
run: |
apt-get update -qq
# for builds
apt-get install -y gcc git make tzdata
# for Redis support
apt-get install -y libhiredis-dev
# for OpenSSL support
apt-get install -y libssl-dev
# for SQLite support
apt-get install -y libsqlite3-dev sqlite3
# for Sodium support
apt-get install -y --force-yes libsodium-dev
# for integration/regression tests
apt-get install -y \
libauthen-oath-perl \
libcompress-raw-zlib-perl \
libdata-dumper-simple-perl \
libdatetime-perl \
libfile-copy-recursive-perl \
libfile-path-tiny-perl \
libfile-spec-native-perl \
libmime-base32-perl \
libnet-address-ip-local-perl \
libnet-inet6glue-perl \
libnet-ssh2-perl \
libnet-ssleay-perl \
libnet-telnet-perl \
libposix-2008-perl \
libtest-unit-perl \
libtime-hr-perl \
libwww-perl
PERL_MM_USE_DEFAULT=1 perl -MCPAN -e 'install Net::FTPSSL'
# for debugging
gcc --version
openssl version -a
- name: Prepare source code
run: |
cp mod_proxy_protocol/mod_proxy_protocol.c proftpd/contrib/mod_proxy_protocol.c
- name: Install with static modules
# NOTE: Docker does not have good IPv6 support, hence we disable it.
run: |
cd proftpd
./configure --enable-ctrls --disable-ipv6 --enable-redis --with-modules=mod_ban:mod_rewrite:mod_sftp:mod_auth_otp:mod_sql:mod_sql_sqlite:mod_tls:mod_tls_shmcache:mod_proxy:mod_unique_id:mod_proxy_protocol
make
./proftpd -V
make install
- name: Run integration tests
env:
PROFTPD_TEST_BIN: /usr/local/sbin/proftpd
PROFTPD_TEST_CI: github
PROFTPD_TEST_DIR: ${{ github.workspace }}/proftpd/tests
run: |
cd proftpd/contrib/mod_proxy
perl tests.pl
proftpd-mod_proxy-0.9.5/.gitignore 0000664 0000000 0000000 00000000232 14757370167 0017227 0 ustar 00root root 0000000 0000000 configure
Makefile
config.log
config.status
autom4te.cache
mod_proxy.h
tests.log
t/api-tests
t/api-tests.log
.libs
*.a
*.sw?
*.la
*.lo
*Tests*.log
*.o
*~
proftpd-mod_proxy-0.9.5/Makefile.in 0000664 0000000 0000000 00000010242 14757370167 0017306 0 ustar 00root root 0000000 0000000 top_builddir=../..
top_srcdir=../..
srcdir=@srcdir@
include $(top_srcdir)/Make.rules
.SUFFIXES: .la .lo
SHARED_CFLAGS=-DPR_SHARED_MODULE
SHARED_LDFLAGS=-avoid-version -export-dynamic -module
VPATH=@srcdir@
MODULE_LIBS=@MODULE_LIBS@
MODULE_NAME=mod_proxy
MODULE_OBJS=mod_proxy.o \
lib/proxy/random.o \
lib/proxy/db.o \
lib/proxy/dns.o \
lib/proxy/session.o \
lib/proxy/conn.o \
lib/proxy/netio.o \
lib/proxy/inet.o \
lib/proxy/str.o \
lib/proxy/ssh.o \
lib/proxy/ssh/db.o \
lib/proxy/ssh/redis.o \
lib/proxy/tls.o \
lib/proxy/tls/db.o \
lib/proxy/tls/redis.o \
lib/proxy/uri.o \
lib/proxy/forward.o \
lib/proxy/reverse.o \
lib/proxy/reverse/db.o \
lib/proxy/reverse/redis.o \
lib/proxy/ftp/conn.o \
lib/proxy/ftp/ctrl.o \
lib/proxy/ftp/data.o \
lib/proxy/ftp/dirlist.o \
lib/proxy/ftp/facts.o \
lib/proxy/ftp/msg.o \
lib/proxy/ftp/sess.o \
lib/proxy/ftp/xfer.o \
lib/proxy/ssh/agent.o \
lib/proxy/ssh/auth.o \
lib/proxy/ssh/bcrypt.o \
lib/proxy/ssh/cipher.o \
lib/proxy/ssh/compress.o \
lib/proxy/ssh/crypto.o \
lib/proxy/ssh/disconnect.o \
lib/proxy/ssh/interop.o \
lib/proxy/ssh/kex.o \
lib/proxy/ssh/keys.o \
lib/proxy/ssh/mac.o \
lib/proxy/ssh/misc.o \
lib/proxy/ssh/msg.o \
lib/proxy/ssh/packet.o \
lib/proxy/ssh/service.o \
lib/proxy/ssh/session.o \
lib/proxy/ssh/umac.o \
lib/proxy/ssh/umac128.o \
lib/proxy/ssh/utf8.o
SHARED_MODULE_OBJS=mod_proxy.lo \
lib/proxy/random.lo \
lib/proxy/db.lo \
lib/proxy/dns.lo \
lib/proxy/session.lo \
lib/proxy/conn.lo \
lib/proxy/netio.lo \
lib/proxy/inet.lo \
lib/proxy/str.lo \
lib/proxy/ssh.lo \
lib/proxy/ssh/db.lo \
lib/proxy/ssh/redis.lo \
lib/proxy/tls.lo \
lib/proxy/tls/db.lo \
lib/proxy/tls/redis.lo \
lib/proxy/uri.lo \
lib/proxy/forward.lo \
lib/proxy/reverse.lo \
lib/proxy/reverse/db.lo \
lib/proxy/reverse/redis.lo \
lib/proxy/ftp/conn.lo \
lib/proxy/ftp/ctrl.lo \
lib/proxy/ftp/data.lo \
lib/proxy/ftp/dirlist.lo \
lib/proxy/ftp/facts.lo \
lib/proxy/ftp/msg.lo \
lib/proxy/ftp/sess.lo \
lib/proxy/ftp/xfer.lo \
lib/proxy/ssh/agent.lo \
lib/proxy/ssh/auth.lo \
lib/proxy/ssh/bcrypt.lo \
lib/proxy/ssh/cipher.lo \
lib/proxy/ssh/compress.lo \
lib/proxy/ssh/crypto.lo \
lib/proxy/ssh/disconnect.lo \
lib/proxy/ssh/interop.lo \
lib/proxy/ssh/kex.lo \
lib/proxy/ssh/keys.lo \
lib/proxy/ssh/mac.lo \
lib/proxy/ssh/misc.lo \
lib/proxy/ssh/msg.lo \
lib/proxy/ssh/packet.lo \
lib/proxy/ssh/service.lo \
lib/proxy/ssh/session.lo \
lib/proxy/ssh/umac.lo \
lib/proxy/ssh/umac128.lo \
lib/proxy/ssh/utf8.lo
# Necessary redefinitions
INCLUDES=-I. -I./include -I../.. -I../../include @INCLUDES@
CPPFLAGS= $(ADDL_CPPFLAGS) -DHAVE_CONFIG_H $(DEFAULT_PATHS) $(PLATFORM) $(INCLUDES)
LDFLAGS=-L../../lib @LIBDIRS@
.c.o:
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
.c.lo:
$(LIBTOOL) --mode=compile --tag=CC $(CC) $(CPPFLAGS) $(CFLAGS) $(SHARED_CFLAGS) -c $< -o $@
shared: $(SHARED_MODULE_OBJS)
$(LIBTOOL) --mode=link --tag=CC $(CC) -o $(MODULE_NAME).la $(SHARED_MODULE_OBJS) -rpath $(LIBEXECDIR) $(LDFLAGS) $(SHARED_LDFLAGS) $(MODULE_LIBS) $(SHARED_MODULE_LIBS) `cat $(MODULE_NAME).c | grep '$$Libraries:' | sed -e 's/^.*\$$Libraries: \(.*\)\\$$/\1/'`
static: $(MODULE_OBJS)
test -z "$(MODULE_LIBS)" || echo "$(MODULE_LIBS)" >> $(MODULE_LIBS_FILE)
$(AR) rc $(MODULE_NAME).a $(MODULE_OBJS)
$(RANLIB) $(MODULE_NAME).a
install: install-misc
if [ -f $(MODULE_NAME).la ] ; then \
$(LIBTOOL) --mode=install --tag=CC $(INSTALL_BIN) $(MODULE_NAME).la $(DESTDIR)$(LIBEXECDIR) ; \
fi
install-misc:
$(INSTALL) -o $(INSTALL_USER) -g $(INSTALL_GROUP) -m 0644 cacerts.pem $(DESTDIR)$(sysconfdir)/cacerts.pem
clean:
$(LIBTOOL) --mode=clean $(RM) $(MODULE_NAME).a $(MODULE_NAME).la *.o *.lo .libs/*.o lib/proxy/*.o lib/proxy/*.lo lib/proxy/ftp/*.o lib/proxy/ftp/*.lo lib/proxy/reverse/*.o lib/proxy/reverse/*.lo lib/proxy/ssh/*.o lib/proxy/ssh/*.lo lib/proxy/tls/*.o lib/proxy/tls/*.lo
cd t/ && $(MAKE) clean
# Run the API tests
check:
test -z "$(ENABLE_TESTS)" || (cd t/ && $(MAKE) api-tests)
distclean: clean
$(RM) Makefile $(MODULE_NAME).h config.status config.cache config.log *.gcda *.gcno
-$(RM) -r .libs/ .git/ CVS/ RCS/
proftpd-mod_proxy-0.9.5/README.md 0000664 0000000 0000000 00000001551 14757370167 0016523 0 ustar 00root root 0000000 0000000 proftpd-mod_proxy
=================
Status
------
[](https://github.com/Castaglia/proftpd-mod_proxy/actions/workflows/ci.yml)
[](https://github.com/Castaglia/proftpd-mod_proxy/actions/workflows/codeql.yml)
[](https://img.shields.io/badge/license-GPL-brightgreen.svg)
Synopsis
--------
The `mod_proxy` module for ProFTPD proxies FTP/FTPS connections, supporting
both forward and reverse proxy configurations.
See the [mod_proxy.html](https://htmlpreview.github.io/?https://github.com/Castaglia/proftpd-mod_proxy/blob/master/mod_proxy.html) documentation for more details.
proftpd-mod_proxy-0.9.5/cacerts.pem 0000664 0000000 0000000 00000717606 14757370167 0017412 0 ustar 00root root 0000000 0000000 ##
## Bundle of CA Root Certificates
##
## Certificate data from Mozilla as of: Sun Dec 12 01:50:20 2021 GMT
##
## This is a bundle of X.509 certificates of public Certificate Authorities
## (CA). These were automatically extracted from Mozilla's root certificates
## file (certdata.txt). This file can be found in the mozilla source tree:
## https://hg.mozilla.org/releases/mozilla-release/raw-file/default/security/nss/lib/ckfw/builtins/certdata.txt
##
## It contains the certificates in PEM format and therefore
## can be directly used with curl / libcurl / php_curl, or with
## an Apache+mod_ssl webserver for SSL client authentication.
## Just configure this file as the SSLCACertificateFile.
##
## Conversion done with mk-ca-bundle.pl version 1.28.
## mk-ca-bundle.pl -v -f -p ALL:TRUSTED_DELEGATOR
## SHA256: bb36818a81feaa4cca61101e6d6276cd09e972efcb08112dfed846918ca41d7f
##
GlobalSign Root CA
==================
-----BEGIN CERTIFICATE-----
MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkGA1UEBhMCQkUx
GTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jvb3QgQ0ExGzAZBgNVBAMTEkds
b2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAwMDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNV
BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYD
VQQDExJHbG9iYWxTaWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDa
DuaZjc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavpxy0Sy6sc
THAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp1Wrjsok6Vjk4bwY8iGlb
Kk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdGsnUOhugZitVtbNV4FpWi6cgKOOvyJBNP
c1STE4U6G7weNLWLBYy5d4ux2x8gkasJU26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrX
gzT/LCrBbBlDSgeF59N89iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUF
AAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOzyj1hTdNGCbM+w6Dj
Y1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE38NflNUVyRRBnMRddWQVDf9VMOyG
j/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymPAbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhH
hm4qxFYxldBniYUr+WymXUadDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveC
X4XSQRjbgbMEHMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==
-----END CERTIFICATE-----
GlobalSign Root CA - R2
=======================
-----BEGIN CERTIFICATE-----
MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4GA1UECxMXR2xv
YmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh
bFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT
aWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln
bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6
ErPLv4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8eoLrvozp
s6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklqtTleiDTsvHgMCJiEbKjN
S7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzdC9XZzPnqJworc5HGnRusyMvo4KD0L5CL
TfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pazq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6C
ygPCm48CAwEAAaOBnDCBmTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
FgQUm+IHV2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5nbG9i
YWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG3lm0mi3f3BmGLjAN
BgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4GsJ0/WwbgcQ3izDJr86iw8bmEbTUsp
9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu
01yiPqFbQfXf5WRDLenVOavSot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG7
9G+dwfCMNYxdAfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7
TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg==
-----END CERTIFICATE-----
Verisign Class 1 Public Primary Certification Authority - G3
============================================================
-----BEGIN CERTIFICATE-----
MIIEGjCCAwICEQCLW3VWhFSFCwDPrzhIzrGkMA0GCSqGSIb3DQEBBQUAMIHKMQswCQYDVQQGEwJV
UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv
cmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl
IG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQsw
CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy
dXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhv
cml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDEgUHVibGljIFByaW1hcnkg
Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBAN2E1Lm0+afY8wR4nN493GwTFtl63SRRZsDHJlkNrAYIwpTRMx/wgzUfbhvI3qpuFU5UJ+/E
bRrsC+MO8ESlV8dAWB6jRx9x7GD2bZTIGDnt/kIYVt/kTEkQeE4BdjVjEjbdZrwBBDajVWjVojYJ
rKshJlQGrT/KFOCsyq0GHZXi+J3x4GD/wn91K0zM2v6HmSHquv4+VNfSWXjbPG7PoBMAGrgnoeS+
Z5bKoMWznN3JdZ7rMJpfo83ZrngZPyPpXNspva1VyBtUjGP26KbqxzcSXKMpHgLZ2x87tNcPVkeB
FQRKr4Mn0cVYiMHd9qqnoxjaaKptEVHhv2Vrn5Z20T0CAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA
q2aN17O6x5q25lXQBfGfMY1aqtmqRiYPce2lrVNWYgFHKkTp/j90CxObufRNG7LRX7K20ohcs5/N
y9Sn2WCVhDr4wTcdYcrnsMXlkdpUpqwxga6X3s0IrLjAl4B/bnKk52kTlWUfxJM8/XmPBNQ+T+r3
ns7NZ3xPZQL/kYVUc8f/NveGLezQXk//EZ9yBta4GvFMDSZl4kSAHsef493oCtrspSCAaWihT37h
a88HQfqDjrw43bAuEbFrskLMmrz5SCJ5ShkPshw+IHTZasO+8ih4E1Z5T21Q6huwtVexN2ZYI/Pc
D98Kh8TvhgXVOBRgmaNL3gaWcSzy27YfpO8/7g==
-----END CERTIFICATE-----
Verisign Class 2 Public Primary Certification Authority - G3
============================================================
-----BEGIN CERTIFICATE-----
MIIEGTCCAwECEGFwy0mMX5hFKeewptlQW3owDQYJKoZIhvcNAQEFBQAwgcoxCzAJBgNVBAYTAlVT
MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29y
azE6MDgGA1UECxMxKGMpIDE5OTkgVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ug
b25seTFFMEMGA1UEAxM8VmVyaVNpZ24gQ2xhc3MgMiBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0
aW9uIEF1dGhvcml0eSAtIEczMB4XDTk5MTAwMTAwMDAwMFoXDTM2MDcxNjIzNTk1OVowgcoxCzAJ
BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1
c3QgTmV0d29yazE6MDgGA1UECxMxKGMpIDE5OTkgVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9y
aXplZCB1c2Ugb25seTFFMEMGA1UEAxM8VmVyaVNpZ24gQ2xhc3MgMiBQdWJsaWMgUHJpbWFyeSBD
ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEczMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
AQEArwoNwtUs22e5LeWUJ92lvuCwTY+zYVY81nzD9M0+hsuiiOLh2KRpxbXiv8GmR1BeRjmL1Za6
tW8UvxDOJxOeBUebMXoT2B/Z0wI3i60sR/COgQanDTAM6/c8DyAd3HJG7qUCyFvDyVZpTMUYwZF7
C9UTAJu878NIPkZgIIUq1ZC2zYugzDLdt/1AVbJQHFauzI13TccgTacxdu9okoqQHgiBVrKtaaNS
0MscxCM9H5n+TOgWY47GCI72MfbS+uV23bUckqNJzc0BzWjNqWm6o+sdDZykIKbBoMXRRkwXbdKs
Zj+WjOCE1Db/IlnF+RFgqF8EffIa9iVCYQ/ESrg+iQIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQA0
JhU8wI1NQ0kdvekhktdmnLfexbjQ5F1fdiLAJvmEOjr5jLX77GDx6M4EsMjdpwOPMPOY36TmpDHf
0xwLRtxyID+u7gU8pDM/CzmscHhzS5kr3zDCVLCoO1Wh/hYozUK9dG6A2ydEp85EXdQbkJgNHkKU
sQAsBNB0owIFImNjzYO1+8FtYmtpdf1dcEG59b98377BMnMiIYtYgXsVkXq642RIsH/7NiXaldDx
JBQX3RiAa0YjOVT1jmIJBB2UkKab5iXiQkWquJCtvgiPqQtCGJTPcjnhsUPgKM+351psE2tJs//j
GHyJizNdrDPXp/naOlXJWBD5qu9ats9LS98q
-----END CERTIFICATE-----
Entrust.net Premium 2048 Secure Server CA
=========================================
-----BEGIN CERTIFICATE-----
MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChMLRW50cnVzdC5u
ZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBpbmNvcnAuIGJ5IHJlZi4gKGxp
bWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNV
BAMTKkVudHJ1c3QubmV0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQx
NzUwNTFaFw0yOTA3MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3
d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTEl
MCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5u
ZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEArU1LqRKGsuqjIAcVFmQqK0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOL
Gp18EzoOH1u3Hs/lJBQesYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSr
hRSGlVuXMlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVTXTzW
nLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/HoZdenoVve8AjhUi
VBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH4QIDAQABo0IwQDAOBgNVHQ8BAf8E
BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJ
KoZIhvcNAQEFBQADggEBADubj1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPy
T/4xmf3IDExoU8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf
zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5bu/8j72gZyxKT
J1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+bYQLCIt+jerXmCHG8+c8eS9e
nNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/ErfF6adulZkMV8gzURZVE=
-----END CERTIFICATE-----
Baltimore CyberTrust Root
=========================
-----BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJRTESMBAGA1UE
ChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYDVQQDExlCYWx0aW1vcmUgQ3li
ZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoXDTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMC
SUUxEjAQBgNVBAoTCUJhbHRpbW9yZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFs
dGltb3JlIEN5YmVyVHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKME
uyKrmD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjrIZ3AQSsB
UnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeKmpYcqWe4PwzV9/lSEy/C
G9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSuXmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9
XbIGevOF6uvUA65ehD5f/xXtabz5OTZydc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjpr
l3RjM71oGDHweI12v/yejl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoI
VDaGezq1BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEB
BQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT929hkTI7gQCvlYpNRh
cL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3WgxjkzSswF07r51XgdIGn9w/xZchMB5
hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsa
Y71k5h+3zvDyny67G7fyUIhzksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9H
RCwBXbsdtTLSR9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp
-----END CERTIFICATE-----
Entrust Root Certification Authority
====================================
-----BEGIN CERTIFICATE-----
MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMCVVMxFjAUBgNV
BAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0Lm5ldC9DUFMgaXMgaW5jb3Jw
b3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMWKGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsG
A1UEAxMkRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0
MloXDTI2MTEyNzIwNTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMu
MTkwNwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSByZWZlcmVu
Y2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNVBAMTJEVudHJ1c3QgUm9v
dCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
ALaVtkNC+sZtKm9I35RMOVcF7sN5EUFoNu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYsz
A9u3g3s+IIRe7bJWKKf44LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOww
Cj0Yzfv9KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGIrb68
j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi94DkZfs0Nw4pgHBN
rziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOBsDCBrTAOBgNVHQ8BAf8EBAMCAQYw
DwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAigA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1
MzQyWjAfBgNVHSMEGDAWgBRokORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DH
hmak8fdLQ/uEvW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA
A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9tO1KzKtvn1ISM
Y/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6ZuaAGAT/3B+XxFNSRuzFVJ7yVTa
v52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTS
W3iDVuycNsMm4hH2Z0kdkquM++v/eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0
tHuu2guQOHXvgR1m0vdXcDazv/wor3ElhVsT/h5/WrQ8
-----END CERTIFICATE-----
Certum Root CA
==============
-----BEGIN CERTIFICATE-----
MIIDDDCCAfSgAwIBAgIDAQAgMA0GCSqGSIb3DQEBBQUAMD4xCzAJBgNVBAYTAlBMMRswGQYDVQQK
ExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBDQTAeFw0wMjA2MTExMDQ2Mzla
Fw0yNzA2MTExMDQ2MzlaMD4xCzAJBgNVBAYTAlBMMRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8u
by4xEjAQBgNVBAMTCUNlcnR1bSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6x
wS7TT3zNJc4YPk/EjG+AanPIW1H4m9LcuwBcsaD8dQPugfCI7iNS6eYVM42sLQnFdvkrOYCJ5JdL
kKWoePhzQ3ukYbDYWMzhbGZ+nPMJXlVjhNWo7/OxLjBos8Q82KxujZlakE403Daaj4GIULdtlkIJ
89eVgw1BS7Bqa/j8D35in2fE7SZfECYPCE/wpFcozo+47UX2bu4lXapuOb7kky/ZR6By6/qmW6/K
Uz/iDsaWVhFu9+lmqSbYf5VT7QqFiLpPKaVCjF62/IUgAKpoC6EahQGcxEZjgoi2IrHu/qpGWX7P
NSzVttpd90gzFFS269lvzs2I1qsb2pY7HVkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkq
hkiG9w0BAQUFAAOCAQEAuI3O7+cUus/usESSbLQ5PqKEbq24IXfS1HeCh+YgQYHu4vgRt2PRFze+
GXYkHAQaTOs9qmdvLdTN/mUxcMUbpgIKumB7bVjCmkn+YzILa+M6wKyrO7Do0wlRjBCDxjTgxSvg
GrZgFCdsMneMvLJymM/NzD+5yCRCFNZX/OYmQ6kd5YCQzgNUKD73P9P4Te1qCjqTE5s7FCMTY5w/
0YcneeVMUeMBrYVdGjux1XMQpNPyvG5k9VpWkKjHDkx0Dy5xO/fIR/RpbxXyEV6DHpx8Uq79AtoS
qFlnGNu8cN2bsWntgM6JQEhqDjXKKWYVIZQs6GAqm4VKQPNriiTsBhYscw==
-----END CERTIFICATE-----
Comodo AAA Services root
========================
-----BEGIN CERTIFICATE-----
MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS
R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg
TGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAw
MFoXDTI4MTIzMTIzNTk1OVowezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hl
c3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV
BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQuaBtDFcCLNSS1UY8y2bmhG
C1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe3M/vg4aijJRPn2jymJBGhCfHdr/jzDUs
i14HZGWCwEiwqJH5YZ92IFCokcdmtet4YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszW
Y19zjNoFmag4qMsXeDZRrOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjH
Ypy+g8cmez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQUoBEK
Iz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wewYDVR0f
BHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNl
cy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29tb2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2Vz
LmNybDANBgkqhkiG9w0BAQUFAAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm
7l3sAg9g1o1QGE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz
Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2G9w84FoVxp7Z
8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsil2D4kF501KKaU73yqWjgom7C
12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg==
-----END CERTIFICATE-----
QuoVadis Root CA 2
==================
-----BEGIN CERTIFICATE-----
MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT
EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMjAeFw0wNjExMjQx
ODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4IC
DwAwggIKAoICAQCaGMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6
XJxgFyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55JWpzmM+Yk
lvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bBrrcCaoF6qUWD4gXmuVbB
lDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp+ARz8un+XJiM9XOva7R+zdRcAitMOeGy
lZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt
66/3FsvbzSUr5R/7mp/iUcw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1Jdxn
wQ5hYIizPtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og/zOh
D7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UHoycR7hYQe7xFSkyy
BNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuIyV77zGHcizN300QyNQliBJIWENie
J0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1Ud
DgQWBBQahGK8SEwzJQTU7tD2A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGU
a6FJpEcwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT
ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2fBluornFdLwUv
Z+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzng/iN/Ae42l9NLmeyhP3ZRPx3
UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2BlfF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodm
VjB3pjd4M1IQWK4/YY7yarHvGH5KWWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK
+JDSV6IZUaUtl0HaB0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrW
IozchLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPRTUIZ3Ph1
WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWDmbA4CD/pXvk1B+TJYm5X
f6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0ZohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II
4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8
VCLAAVBpQ570su9t+Oza8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u
-----END CERTIFICATE-----
QuoVadis Root CA 3
==================
-----BEGIN CERTIFICATE-----
MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT
EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMzAeFw0wNjExMjQx
OTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4IC
DwAwggIKAoICAQDMV0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNgg
DhoB4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUrH556VOij
KTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd8lyyBTNvijbO0BNO/79K
DDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9CabwvvWhDFlaJKjdhkf2mrk7AyxRllDdLkgbv
BNDInIjbC3uBr7E9KsRlOni27tyAsdLTmZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwp
p5ijJUMv7/FfJuGITfhebtfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8
nT8KKdjcT5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDtWAEX
MJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZc6tsgLjoC2SToJyM
Gf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A4iLItLRkT9a6fUg+qGkM17uGcclz
uD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYDVR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHT
BgkrBgEEAb5YAAMwgcUwgZMGCCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmlj
YXRlIGNvbnN0aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0
aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVudC4wLQYIKwYB
BQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2NwczALBgNVHQ8EBAMCAQYwHQYD
VR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4GA1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4
ywLQoUmkRzBFMQswCQYDVQQGEwJCTTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UE
AxMSUXVvVmFkaXMgUm9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZV
qyM07ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSemd1o417+s
hvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd+LJ2w/w4E6oM3kJpK27z
POuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2
Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadNt54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp
8kokUvd0/bpO5qgdAm6xDYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBC
bjPsMZ57k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6szHXu
g/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0jWy10QJLZYxkNc91p
vGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeTmJlglFwjz1onl14LBQaTNx47aTbr
qZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK4SVhM7JZG+Ju1zdXtg2pEto=
-----END CERTIFICATE-----
Security Communication Root CA
==============================
-----BEGIN CERTIFICATE-----
MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP
U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw
HhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP
U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw
8yl89f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJDKaVv0uM
DPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9Ms+k2Y7CI9eNqPPYJayX
5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/NQV3Is00qVUarH9oe4kA92819uZKAnDfd
DJZkndwi92SL32HeFZRSFaB9UslLqCHJxrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2
JChzAgMBAAGjPzA9MB0GA1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYw
DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vGkl3g
0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfrUj94nK9NrvjVT8+a
mCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5Bw+SUEmK3TGXX8npN6o7WWWXlDLJ
s58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJUJRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ
6rBK+1YWc26sTfcioU+tHXotRSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAi
FL39vmwLAw==
-----END CERTIFICATE-----
Camerfirma Chambers of Commerce Root
====================================
-----BEGIN CERTIFICATE-----
MIIEvTCCA6WgAwIBAgIBADANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJFVTEnMCUGA1UEChMe
QUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1i
ZXJzaWduLm9yZzEiMCAGA1UEAxMZQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdDAeFw0wMzA5MzAx
NjEzNDNaFw0zNzA5MzAxNjEzNDRaMH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZp
cm1hIFNBIENJRiBBODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3Jn
MSIwIAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290MIIBIDANBgkqhkiG9w0BAQEFAAOC
AQ0AMIIBCAKCAQEAtzZV5aVdGDDg2olUkfzIx1L4L1DZ77F1c2VHfRtbunXF/KGIJPov7coISjlU
xFF6tdpg6jg8gbLL8bvZkSM/SAFwdakFKq0fcfPJVD0dBmpAPrMMhe5cG3nCYsS4No41XQEMIwRH
NaqbYE6gZj3LJgqcQKH0XZi/caulAGgq7YN6D6IUtdQis4CwPAxaUWktWBiP7Zme8a7ileb2R6jW
DA+wWFjbw2Y3npuRVDM30pQcakjJyfKl2qUMI/cjDpwyVV5xnIQFUZot/eZOKjRa3spAN2cMVCFV
d9oKDMyXroDclDZK9D7ONhMeU+SsTjoF7Nuucpw4i9A5O4kKPnf+dQIBA6OCAUQwggFAMBIGA1Ud
EwEB/wQIMAYBAf8CAQwwPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC5jaGFtYmVyc2lnbi5v
cmcvY2hhbWJlcnNyb290LmNybDAdBgNVHQ4EFgQU45T1sU3p26EpW1eLTXYGduHRooowDgYDVR0P
AQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzAnBgNVHREEIDAegRxjaGFtYmVyc3Jvb3RAY2hh
bWJlcnNpZ24ub3JnMCcGA1UdEgQgMB6BHGNoYW1iZXJzcm9vdEBjaGFtYmVyc2lnbi5vcmcwWAYD
VR0gBFEwTzBNBgsrBgEEAYGHLgoDATA+MDwGCCsGAQUFBwIBFjBodHRwOi8vY3BzLmNoYW1iZXJz
aWduLm9yZy9jcHMvY2hhbWJlcnNyb290Lmh0bWwwDQYJKoZIhvcNAQEFBQADggEBAAxBl8IahsAi
fJ/7kPMa0QOx7xP5IV8EnNrJpY0nbJaHkb5BkAFyk+cefV/2icZdp0AJPaxJRUXcLo0waLIJuvvD
L8y6C98/d3tGfToSJI6WjzwFCm/SlCgdbQzALogi1djPHRPH8EjX1wWnz8dHnjs8NMiAT9QUu/wN
UPf6s+xCX6ndbcj0dc97wXImsQEcXCz9ek60AcUFV7nnPKoF2YjpB0ZBzu9Bga5Y34OirsrXdx/n
ADydb47kMgkdTXg0eDQ8lJsm7U9xxhl6vSAiSFr+S30Dt+dYvsYyTnQeaN2oaFuzPu5ifdmA6Ap1
erfutGWaIZDgqtCYvDi1czyL+Nw=
-----END CERTIFICATE-----
Camerfirma Global Chambersign Root
==================================
-----BEGIN CERTIFICATE-----
MIIExTCCA62gAwIBAgIBADANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJFVTEnMCUGA1UEChMe
QUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1i
ZXJzaWduLm9yZzEgMB4GA1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwHhcNMDMwOTMwMTYx
NDE4WhcNMzcwOTMwMTYxNDE4WjB9MQswCQYDVQQGEwJFVTEnMCUGA1UEChMeQUMgQ2FtZXJmaXJt
YSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEg
MB4GA1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwggEgMA0GCSqGSIb3DQEBAQUAA4IBDQAw
ggEIAoIBAQCicKLQn0KuWxfH2H3PFIP8T8mhtxOviteePgQKkotgVvq0Mi+ITaFgCPS3CU6gSS9J
1tPfnZdan5QEcOw/Wdm3zGaLmFIoCQLfxS+EjXqXd7/sQJ0lcqu1PzKY+7e3/HKE5TWH+VX6ox8O
by4o3Wmg2UIQxvi1RMLQQ3/bvOSiPGpVeAp3qdjqGTK3L/5cPxvusZjsyq16aUXjlg9V9ubtdepl
6DJWk0aJqCWKZQbua795B9Dxt6/tLE2Su8CoX6dnfQTyFQhwrJLWfQTSM/tMtgsL+xrJxI0DqX5c
8lCrEqWhz0hQpe/SyBoT+rB/sYIcd2oPX9wLlY/vQ37mRQklAgEDo4IBUDCCAUwwEgYDVR0TAQH/
BAgwBgEB/wIBDDA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vY3JsLmNoYW1iZXJzaWduLm9yZy9j
aGFtYmVyc2lnbnJvb3QuY3JsMB0GA1UdDgQWBBRDnDafsJ4wTcbOX60Qq+UDpfqpFDAOBgNVHQ8B
Af8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgAHMCoGA1UdEQQjMCGBH2NoYW1iZXJzaWducm9vdEBj
aGFtYmVyc2lnbi5vcmcwKgYDVR0SBCMwIYEfY2hhbWJlcnNpZ25yb290QGNoYW1iZXJzaWduLm9y
ZzBbBgNVHSAEVDBSMFAGCysGAQQBgYcuCgEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly9jcHMuY2hh
bWJlcnNpZ24ub3JnL2Nwcy9jaGFtYmVyc2lnbnJvb3QuaHRtbDANBgkqhkiG9w0BAQUFAAOCAQEA
PDtwkfkEVCeR4e3t/mh/YV3lQWVPMvEYBZRqHN4fcNs+ezICNLUMbKGKfKX0j//U2K0X1S0E0T9Y
gOKBWYi+wONGkyT+kL0mojAt6JcmVzWJdJYY9hXiryQZVgICsroPFOrGimbBhkVVi76SvpykBMdJ
PJ7oKXqJ1/6v/2j1pReQvayZzKWGVwlnRtvWFsJG8eSpUPWP0ZIV018+xgBJOm5YstHRJw0lyDL4
IBHNfTIzSJRUTN3cecQwn+uOuFW114hcxWokPbLTBQNRxgfvzBRydD1ucs4YKIxKoHflCStFREes
t2d/AYoFWpO+ocH/+OcOZ6RHSXZddZAa9SaP8A==
-----END CERTIFICATE-----
XRamp Global CA Root
====================
-----BEGIN CERTIFICATE-----
MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UE
BhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2Vj
dXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB
dXRob3JpdHkwHhcNMDQxMTAxMTcxNDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMx
HjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkg
U2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
dHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS638eMpSe2OAtp87ZOqCwu
IR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCPKZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMx
foArtYzAQDsRhtDLooY2YKTVMIJt2W7QDxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FE
zG+gSqmUsE3a56k0enI4qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqs
AxcZZPRaJSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNViPvry
xS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud
EwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASsjVy16bYbMDYGA1UdHwQvMC0wK6Ap
oCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMC
AQEwDQYJKoZIhvcNAQEFBQADggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc
/Kh4ZzXxHfARvbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt
qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLaIR9NmXmd4c8n
nxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSyi6mx5O+aGtA9aZnuqCij4Tyz
8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQO+7ETPTsJ3xCwnR8gooJybQDJbw=
-----END CERTIFICATE-----
Go Daddy Class 2 CA
===================
-----BEGIN CERTIFICATE-----
MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMY
VGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRp
ZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkG
A1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g
RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQAD
ggENADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv
2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+qN1j3hybX2C32
qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6j
YGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmY
vLEHZ6IVDd2gWMZEewo+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0O
BBYEFNLEsNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h/t2o
atTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMu
MTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwG
A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wim
PQoZ+YeAEW5p5JYXMP80kWNyOO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKt
I3lpjbi2Tc7PTMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ
HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mERdEr/VxqHD3VI
Ls9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5CufReYNnyicsbkqWletNw+vHX/b
vZ8=
-----END CERTIFICATE-----
Starfield Class 2 CA
====================
-----BEGIN CERTIFICATE-----
MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzElMCMGA1UEChMc
U3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZpZWxkIENsYXNzIDIg
Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQwNjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBo
MQswCQYDVQQGEwJVUzElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAG
A1UECxMpU3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqG
SIb3DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf8MOh2tTY
bitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN+lq2cwQlZut3f+dZxkqZ
JRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVm
epsZGD3/cVE8MC5fvj13c7JdBmzDI1aaK4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSN
F4Azbl5KXZnJHoe0nRrA1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HF
MIHCMB0GA1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fRzt0f
hvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNo
bm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBDbGFzcyAyIENlcnRpZmljYXRpb24g
QXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGs
afPzWdqbAYcaT1epoXkJKtv3L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLM
PUxA2IGvd56Deruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl
xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynpVSJYACPq4xJD
KVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEYWQPJIrSPnNVeKtelttQKbfi3
QBFGmh95DmK/D5fs4C8fF5Q=
-----END CERTIFICATE-----
DigiCert Assured ID Root CA
===========================
-----BEGIN CERTIFICATE-----
MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQG
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw
IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzEx
MTEwMDAwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL
ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0Ew
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7cJpSIqvTO
9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYPmDI2dsze3Tyoou9q+yHy
UmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW
/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpy
oeb6pNnVFzF1roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf
GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRF
66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzANBgkq
hkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2Bc
EkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38Fn
SbNd67IJKusm7Xi+fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i
8b5QZ7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe
+o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g==
-----END CERTIFICATE-----
DigiCert Global Root CA
=======================
-----BEGIN CERTIFICATE-----
MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQG
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw
HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAw
MDAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3
dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkq
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsBCSDMAZOn
TjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97nh6Vfe63SKMI2tavegw5
BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt43C/dxC//AH2hdmoRBBYMql1GNXRor5H
4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7PT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y
7vrTC0LUq7dBMtoM1O/4gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQAB
o2MwYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbRTLtm
8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDQYJKoZIhvcNAQEF
BQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/EsrhMAtudXH/vTBH1jLuG2cenTnmCmr
EbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIt
tep3Sp+dWOIrWcBAI+0tKIJFPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886
UAb3LujEV0lsYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
-----END CERTIFICATE-----
DigiCert High Assurance EV Root CA
==================================
-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQG
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSsw
KQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAw
MFoXDTMxMTExMDAwMDAwMFowbDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ
MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFu
Y2UgRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm+9S75S0t
Mqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTWPNt0OKRKzE0lgvdKpVMS
OO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEMxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3
MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFBIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQ
NAQTXKFx01p8VdteZOE3hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUe
h10aUAsgEsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMB
Af8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSY
JhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3NecnzyIZgYIVyHbIUf4KmeqvxgydkAQ
V8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6zeM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFp
myPInngiK3BD41VHMWEZ71jFhS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkK
mNEVX58Svnw2Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep+OkuE6N36B9K
-----END CERTIFICATE-----
SwissSign Platinum CA - G2
==========================
-----BEGIN CERTIFICATE-----
MIIFwTCCA6mgAwIBAgIITrIAZwwDXU8wDQYJKoZIhvcNAQEFBQAwSTELMAkGA1UEBhMCQ0gxFTAT
BgNVBAoTDFN3aXNzU2lnbiBBRzEjMCEGA1UEAxMaU3dpc3NTaWduIFBsYXRpbnVtIENBIC0gRzIw
HhcNMDYxMDI1MDgzNjAwWhcNMzYxMDI1MDgzNjAwWjBJMQswCQYDVQQGEwJDSDEVMBMGA1UEChMM
U3dpc3NTaWduIEFHMSMwIQYDVQQDExpTd2lzc1NpZ24gUGxhdGludW0gQ0EgLSBHMjCCAiIwDQYJ
KoZIhvcNAQEBBQADggIPADCCAgoCggIBAMrfogLi2vj8Bxax3mCq3pZcZB/HL37PZ/pEQtZ2Y5Wu
669yIIpFR4ZieIbWIDkm9K6j/SPnpZy1IiEZtzeTIsBQnIJ71NUERFzLtMKfkr4k2HtnIuJpX+UF
eNSH2XFwMyVTtIc7KZAoNppVRDBopIOXfw0enHb/FZ1glwCNioUD7IC+6ixuEFGSzH7VozPY1kne
WCqv9hbrS3uQMpe5up1Y8fhXSQQeol0GcN1x2/ndi5objM89o03Oy3z2u5yg+gnOI2Ky6Q0f4nIo
j5+saCB9bzuohTEJfwvH6GXp43gOCWcwizSC+13gzJ2BbWLuCB4ELE6b7P6pT1/9aXjvCR+htL/6
8++QHkwFix7qepF6w9fl+zC8bBsQWJj3Gl/QKTIDE0ZNYWqFTFJ0LwYfexHihJfGmfNtf9dng34T
aNhxKFrYzt3oEBSa/m0jh26OWnA81Y0JAKeqvLAxN23IhBQeW71FYyBrS3SMvds6DsHPWhaPpZjy
domyExI7C3d3rLvlPClKknLKYRorXkzig3R3+jVIeoVNjZpTxN94ypeRSCtFKwH3HBqi7Ri6Cr2D
+m+8jVeTO9TUps4e8aCxzqv9KyiaTxvXw3LbpMS/XUz13XuWae5ogObnmLo2t/5u7Su9IPhlGdpV
CX4l3P5hYnL5fhgC72O00Puv5TtjjGePAgMBAAGjgawwgakwDgYDVR0PAQH/BAQDAgEGMA8GA1Ud
EwEB/wQFMAMBAf8wHQYDVR0OBBYEFFCvzAeHFUdvOMW0ZdHelarp35zMMB8GA1UdIwQYMBaAFFCv
zAeHFUdvOMW0ZdHelarp35zMMEYGA1UdIAQ/MD0wOwYJYIV0AVkBAQEBMC4wLAYIKwYBBQUHAgEW
IGh0dHA6Ly9yZXBvc2l0b3J5LnN3aXNzc2lnbi5jb20vMA0GCSqGSIb3DQEBBQUAA4ICAQAIhab1
Fgz8RBrBY+D5VUYI/HAcQiiWjrfFwUF1TglxeeVtlspLpYhg0DB0uMoI3LQwnkAHFmtllXcBrqS3
NQuB2nEVqXQXOHtYyvkv+8Bldo1bAbl93oI9ZLi+FHSjClTTLJUYFzX1UWs/j6KWYTl4a0vlpqD4
U99REJNi54Av4tHgvI42Rncz7Lj7jposiU0xEQ8mngS7twSNC/K5/FqdOxa3L8iYq/6KUFkuozv8
KV2LwUvJ4ooTHbG/u0IdUt1O2BReEMYxB+9xJ/cbOQncguqLs5WGXv312l0xpuAxtpTmREl0xRbl
9x8DYSjFyMsSoEJL+WuICI20MhjzdZ/EfwBPBZWcoxcCw7NTm6ogOSkrZvqdr16zktK1puEa+S1B
aYEUtLS17Yk9zvupnTVCRLEcFHOBzyoBNZox1S2PbYTfgE1X4z/FhHXaicYwu+uPyyIIoK6q8QNs
OktNCaUOcsZWayFCTiMlFGiudgp8DAdwZPmaL/YFOSbGDI8Zf0NebvRbFS/bYV3mZy8/CJT5YLSY
Mdp08YSTcU1f+2BY0fvEwW2JorsgH51xkcsymxM9Pn2SUjWskpSi0xjCfMfqr3YFFt1nJ8J+HAci
IfNAChs0B0QTwoRqjt8ZWr9/6x3iGjjRXK9HkmuAtTClyY3YqzGBH9/CZjfTk6mFhnll0g==
-----END CERTIFICATE-----
SwissSign Gold CA - G2
======================
-----BEGIN CERTIFICATE-----
MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkNIMRUw
EwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzIwHhcN
MDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBFMQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dp
c3NTaWduIEFHMR8wHQYDVQQDExZTd2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0B
AQEFAAOCAg8AMIICCgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUq
t2/876LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+bbqBHH5C
jCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c6bM8K8vzARO/Ws/BtQpg
vd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqEemA8atufK+ze3gE/bk3lUIbLtK/tREDF
ylqM2tIrfKjuvqblCqoOpd8FUrdVxyJdMmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvR
AiTysybUa9oEVeXBCsdtMDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuend
jIj3o02yMszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69yFGkO
peUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPiaG59je883WX0XaxR
7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxMgI93e2CaHt+28kgeDrpOVG2Y4OGi
GqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw
AwEB/zAdBgNVHQ4EFgQUWyV7lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64
OfPAeGZe6Drn8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov
L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe645R88a7A3hfm
5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczOUYrHUDFu4Up+GC9pWbY9ZIEr
44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOf
Mke6UiI0HTJ6CVanfCU2qT1L2sCCbwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6m
Gu6uLftIdxf+u+yvGPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxp
mo/a77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCChdiDyyJk
vC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid392qgQmwLOM7XdVAyksLf
KzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEppLd6leNcG2mqeSz53OiATIgHQv2ieY2Br
NU0LbbqhPcCT4H8js1WtciVORvnSFu+wZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6Lqj
viOvrv1vA+ACOzB2+httQc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ
-----END CERTIFICATE-----
SwissSign Silver CA - G2
========================
-----BEGIN CERTIFICATE-----
MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCQ0gxFTAT
BgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMB4X
DTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0NlowRzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3
aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG
9w0BAQEFAAOCAg8AMIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644
N0MvFz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7brYT7QbNHm
+/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieFnbAVlDLaYQ1HTWBCrpJH
6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH6ATK72oxh9TAtvmUcXtnZLi2kUpCe2Uu
MGoM9ZDulebyzYLs2aFK7PayS+VFheZteJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5h
qAaEuSh6XzjZG6k4sIN/c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5
FZGkECwJMoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRHHTBs
ROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTfjNFusB3hB48IHpmc
celM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb65i/4z3GcRm25xBWNOHkDRUjvxF3X
CO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/
BAUwAwEB/zAdBgNVHQ4EFgQUF6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRB
tjpbO8tFnb0cwpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0
cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBAHPGgeAn0i0P
4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShpWJHckRE1qTodvBqlYJ7YH39F
kWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L
3XWgwF15kIwb4FDm3jH+mHtwX6WQ2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx
/uNncqCxv1yL5PqZIseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFa
DGi8aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2Xem1ZqSqP
e97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQRdAtq/gsD/KNVV4n+Ssuu
WxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJ
DIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ub
DgEj8Z+7fNzcbBGXJbLytGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u
-----END CERTIFICATE-----
SecureTrust CA
==============
-----BEGIN CERTIFICATE-----
MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBIMQswCQYDVQQG
EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xFzAVBgNVBAMTDlNlY3VyZVRy
dXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIzMTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAe
BgNVBAoTF1NlY3VyZVRydXN0IENvcnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCC
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQX
OZEzZum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO0gMdA+9t
DWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIaowW8xQmxSPmjL8xk037uH
GFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b
01k/unK8RCSc43Oz969XL0Imnal0ugBS8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmH
ursCAwEAAaOBnTCBmjATBgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/
BAUwAwEB/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCegJYYj
aHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ
KoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt36Z3q059c4EVlew3KW+JwULKUBRSu
SceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHf
mbx8IVQr5Fiiu1cprp6poxkmD5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZ
nMUFdAvnZyPSCPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR
3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE=
-----END CERTIFICATE-----
Secure Global CA
================
-----BEGIN CERTIFICATE-----
MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQG
EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBH
bG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkxMjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEg
MB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwg
Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jx
YDiJiQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa/FHtaMbQ
bqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJjnIFHovdRIWCQtBJwB1g
8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnIHmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYV
HDGA76oYa8J719rO+TMg1fW9ajMtgQT7sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi
0XPnj3pDAgMBAAGjgZ0wgZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud
EwEB/wQFMAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCswKaAn
oCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsGAQQBgjcVAQQDAgEA
MA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0LURYD7xh8yOOvaliTFGCRsoTciE6+
OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXOH0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cn
CDpOGR86p1hcF895P4vkp9MmI50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/5
3CYNv6ZHdAbYiNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc
f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW
-----END CERTIFICATE-----
COMODO Certification Authority
==============================
-----BEGIN CERTIFICATE-----
MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCBgTELMAkGA1UE
BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG
A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNVBAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1
dGhvcml0eTAeFw0wNjEyMDEwMDAwMDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEb
MBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFD
T01PRE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0aG9yaXR5
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3UcEbVASY06m/weaKXTuH
+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI2GqGd0S7WWaXUF601CxwRM/aN5VCaTww
xHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV
4EajcNxo2f8ESIl33rXp+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA
1KGzqSX+DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5OnKVI
rLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW/zAOBgNVHQ8BAf8E
BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLmNvbW9k
b2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOC
AQEAPpiem/Yb6dc5t3iuHXIYSdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CP
OGEIqB6BCsAvIC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/
RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4zJVSk/BwJVmc
IGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5ddBA6+C4OmF4O5MBKgxTMVBbkN
+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IBZQ==
-----END CERTIFICATE-----
Network Solutions Certificate Authority
=======================================
-----BEGIN CERTIFICATE-----
MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQG
EwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydOZXR3b3Jr
IFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMx
MjM1OTU5WjBiMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu
MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0G
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwzc7MEL7xx
jOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPPOCwGJgl6cvf6UDL4wpPT
aaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rlmGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXT
crA/vGp97Eh/jcOrqnErU2lBUzS1sLnFBgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc
/Qzpf14Dl847ABSHJ3A4qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMB
AAGjgZcwgZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIBBjAP
BgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwubmV0c29sc3NsLmNv
bS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3JpdHkuY3JsMA0GCSqGSIb3DQEBBQUA
A4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc86fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q
4LqILPxFzBiwmZVRDuwduIj/h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/
GGUsyfJj4akH/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv
wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHNpGxlaKFJdlxD
ydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey
-----END CERTIFICATE-----
COMODO ECC Certification Authority
==================================
-----BEGIN CERTIFICATE-----
MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTELMAkGA1UEBhMC
R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE
ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBB
dXRob3JpdHkwHhcNMDgwMzA2MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0Ix
GzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR
Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRo
b3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSRFtSrYpn1PlILBs5BAH+X
4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0JcfRK9ChQtP6IHG4/bC8vCVlbpVsLM5ni
wz2J+Wos77LTBumjQjBAMB0GA1UdDgQWBBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8E
BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VG
FAkK+qDmfQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdvGDeA
U/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY=
-----END CERTIFICATE-----
OISTE WISeKey Global Root GA CA
===============================
-----BEGIN CERTIFICATE-----
MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCBijELMAkGA1UE
BhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHlyaWdodCAoYykgMjAwNTEiMCAG
A1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBH
bG9iYWwgUm9vdCBHQSBDQTAeFw0wNTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYD
VQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIw
IAYDVQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5
IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy0+zAJs9
Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxRVVuuk+g3/ytr6dTqvirdqFEr12bDYVxg
Asj1znJ7O7jyTmUIms2kahnBAbtzptf2w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbD
d50kc3vkDIzh2TbhmYsFmQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ
/yxViJGg4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t94B3R
LoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw
AwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ
KoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOxSPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vIm
MMkQyh2I+3QZH4VFvbBsUfk2ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4
+vg1YFkCExh8vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa
hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZiFj4A4xylNoEY
okxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ/L7fCg0=
-----END CERTIFICATE-----
Certigna
========
-----BEGIN CERTIFICATE-----
MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNVBAYTAkZSMRIw
EAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4XDTA3MDYyOTE1MTMwNVoXDTI3
MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwI
Q2VydGlnbmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7q
XOEm7RFHYeGifBZ4QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyH
GxnygQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbwzBfsV1/p
ogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q130yGLMLLGq/jj8UEYkg
DncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKf
Irjxwo1p3Po6WAbfAgMBAAGjgbwwgbkwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQ
tCRZvgHyUtVF9lo53BEwZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJ
BgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzjAQ/J
SP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG9w0BAQUFAAOCAQEA
hQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8hbV6lUmPOEvjvKtpv6zf+EwLHyzs+
ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFncfca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1klu
PBS1xp81HlDQwY9qcEQCYsuuHWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY
1gkIl2PlwS6wt0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw
WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg==
-----END CERTIFICATE-----
Cybertrust Global Root
======================
-----BEGIN CERTIFICATE-----
MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYGA1UEChMPQ3li
ZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBSb290MB4XDTA2MTIxNTA4
MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQD
ExZDeWJlcnRydXN0IEdsb2JhbCBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+Mi8vRRQZhP/8NN57CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW
0ozSJ8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2yHLtgwEZL
AfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iPt3sMpTjr3kfb1V05/Iin
89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNzFtApD0mpSPCzqrdsxacwOUBdrsTiXSZT
8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAYXSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAP
BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2
MDSgMqAwhi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3JsMB8G
A1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUAA4IBAQBW7wojoFRO
lZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMjWqd8BfP9IjsO0QbE2zZMcwSO5bAi
5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUxXOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2
hO0j9n0Hq0V+09+zv+mKts2oomcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+T
X3EJIrduPuocA06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW
WL1WMRJOEcgh4LMRkWXbtKaIOM5V
-----END CERTIFICATE-----
ePKI Root Certification Authority
=================================
-----BEGIN CERTIFICATE-----
MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBeMQswCQYDVQQG
EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xKjAoBgNVBAsMIWVQS0kg
Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMx
MjdaMF4xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEq
MCgGA1UECwwhZVBLSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0B
AQEFAAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAHSyZbCUNs
IZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAhijHyl3SJCRImHJ7K2RKi
lTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3XDZoTM1PRYfl61dd4s5oz9wCGzh1NlDiv
qOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX
12ruOzjjK9SXDrkb5wdJfzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0O
WQqraffAsgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uUWH1+
ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLSnT0IFaUQAS2zMnao
lQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pHdmX2Os+PYhcZewoozRrSgx4hxyy/
vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJipNiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXi
Zo1jDiVN1Rmy5nk3pyKdVDECAwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/Qkqi
MAwGA1UdEwQFMAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH
ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGBuvl2ICO1J2B0
1GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6YlPwZpVnPDimZI+ymBV3QGypzq
KOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkPJXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdV
xrsStZf0X4OFunHB2WyBEXYKCrC/gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEP
NXubrjlpC2JgQCA2j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+r
GNm65ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUBo2M3IUxE
xJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS/jQ6fbjpKdx2qcgw+BRx
gMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2zGp1iro2C6pSe3VkQw63d4k3jMdXH7Ojy
sP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTEW9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmOD
BCEIZ43ygknQW/2xzQ+DhNQ+IIX3Sj0rnP0qCglN6oH4EZw=
-----END CERTIFICATE-----
certSIGN ROOT CA
================
-----BEGIN CERTIFICATE-----
MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYTAlJPMREwDwYD
VQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTAeFw0wNjA3MDQxNzIwMDRa
Fw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UE
CxMQY2VydFNJR04gUk9PVCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7I
JUqOtdu0KBuqV5Do0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHH
rfAQUySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5dRdY4zTW2
ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQOA7+j0xbm0bqQfWwCHTD
0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwvJoIQ4uNllAoEwF73XVv4EOLQunpL+943
AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B
Af8EBAMCAcYwHQYDVR0OBBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IB
AQA+0hyJLjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecYMnQ8
SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ44gx+FkagQnIl6Z0
x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6IJd1hJyMctTEHBDa0GpC9oHRxUIlt
vBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNwi/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7Nz
TogVZ96edhBiIL5VaZVDADlN9u6wWk5JRFRYX0KD
-----END CERTIFICATE-----
NetLock Arany (Class Gold) FÅ‘tanúsÃtvány
========================================
-----BEGIN CERTIFICATE-----
MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQGEwJIVTERMA8G
A1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610
dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBB
cmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgx
MjA2MTUwODIxWjCBpzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxO
ZXRMb2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlmaWNhdGlv
biBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNzIEdvbGQpIEbFkXRhbsO6
c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCRec75LbRTDofTjl5Bu
0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrTlF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw
/HpYzY6b7cNGbIRwXdrzAZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAk
H3B5r9s5VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRGILdw
fzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2BJtr+UBdADTHLpl1
neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIB
BjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2MU9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwW
qZw8UQCgwBEIBaeZ5m8BiFRhbvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTta
YtOUZcTh5m2C+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC
bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2FuLjbvrW5Kfna
NwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2XjG4Kvte9nHfRCaexOYNkbQu
dZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E=
-----END CERTIFICATE-----
Hongkong Post Root CA 1
=======================
-----BEGIN CERTIFICATE-----
MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoT
DUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMB4XDTAzMDUx
NTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25n
IFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEArP84tulmAknjorThkPlAj3n54r15/gK97iSSHSL22oVyaf7XPwnU3ZG1
ApzQjVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEnPzlTCeqr
auh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjhZY4bXSNmO7ilMlHIhqqh
qZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9nnV0ttgCXjqQesBCNnLsak3c78QA3xMY
V18meMjWCnl3v/evt3a5pQuEF10Q6m/hq5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNV
HRMBAf8ECDAGAQH/AgEDMA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7i
h9legYsCmEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI37pio
l7Yutmcn1KZJ/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clBoiMBdDhViw+5Lmei
IAQ32pwL0xch4I+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJsEhTkYY2sEJCehFC78JZvRZ+K88ps
T/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpOfMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilT
c4afU9hDDl3WY4JxHYB0yvbiAmvZWg==
-----END CERTIFICATE-----
SecureSign RootCA11
===================
-----BEGIN CERTIFICATE-----
MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDErMCkGA1UEChMi
SmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoGA1UEAxMTU2VjdXJlU2lnbiBS
b290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSsw
KQYDVQQKEyJKYXBhbiBDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1
cmVTaWduIFJvb3RDQTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvL
TJszi1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8h9uuywGO
wvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOVMdrAG/LuYpmGYz+/3ZMq
g6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rP
O7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitA
bpSACW22s293bzUIUPsCh8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZX
t94wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAKCh
OBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xmKbabfSVSSUOrTC4r
bnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQX5Ucv+2rIrVls4W6ng+4reV6G4pQ
Oh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWrQbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01
y8hSyn+B/tlr0/cR7SXf+Of5pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061
lgeLKBObjBmNQSdJQO7e5iNEOdyhIta6A/I=
-----END CERTIFICATE-----
Microsec e-Szigno Root CA 2009
==============================
-----BEGIN CERTIFICATE-----
MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYDVQQGEwJIVTER
MA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jv
c2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o
dTAeFw0wOTA2MTYxMTMwMThaFw0yOTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UE
BwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUt
U3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTCCASIw
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvPkd6mJviZpWNwrZuuyjNA
fW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tccbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG
0IMZfcChEhyVbUr02MelTTMuhTlAdX4UfIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKA
pxn1ntxVUwOXewdI/5n7N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm
1HxdrtbCxkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1+rUC
AwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTLD8bf
QkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAbBgNVHREE
FDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqGSIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0o
lZMEyL/azXm4Q5DwpL7v8u8hmLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfX
I/OMn74dseGkddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775
tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c2Pm2G2JwCz02
yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5tHMN1Rq41Bab2XD0h7lbwyYIi
LXpUq3DDfSJlgnCW
-----END CERTIFICATE-----
GlobalSign Root CA - R3
=======================
-----BEGIN CERTIFICATE-----
MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4GA1UECxMXR2xv
YmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh
bFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT
aWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln
bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWt
iHL8RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsTgHeMCOFJ
0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmmKPZpO/bLyCiR5Z2KYVc3
rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zdQQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjl
OCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZXriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2
xmmFghcCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE
FI/wS3+oLkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZURUm7
lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMpjjM5RcOO5LlXbKr8
EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK6fBdRoyV3XpYKBovHd7NADdBj+1E
bddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQXmcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18
YIvDQVETI53O9zJrlAGomecsMx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7r
kpeDMdmztcpHWD9f
-----END CERTIFICATE-----
Autoridad de Certificacion Firmaprofesional CIF A62634068
=========================================================
-----BEGIN CERTIFICATE-----
MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UEBhMCRVMxQjBA
BgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2
MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEyMzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIw
QAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBB
NjI2MzQwNjgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDD
Utd9thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQMcas9UX4P
B99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefGL9ItWY16Ck6WaVICqjaY
7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15iNA9wBj4gGFrO93IbJWyTdBSTo3OxDqqH
ECNZXyAFGUftaI6SEspd/NYrspI8IM/hX68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyI
plD9amML9ZMWGxmPsu2bm8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctX
MbScyJCyZ/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirjaEbsX
LZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/TKI8xWVvTyQKmtFLK
bpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF6NkBiDkal4ZkQdU7hwxu+g/GvUgU
vzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVhOSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1Ud
EwEB/wQIMAYBAf8CAQEwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNH
DhpkLzCBpgYDVR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp
cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBvACAAZABlACAA
bABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBlAGwAbwBuAGEAIAAwADgAMAAx
ADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx
51tkljYyGOylMnfX40S2wBEqgLk9am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qk
R71kMrv2JYSiJ0L1ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaP
T481PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS3a/DTg4f
Jl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5kSeTy36LssUzAKh3ntLFl
osS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF3dvd6qJ2gHN99ZwExEWN57kci57q13XR
crHedUTnQn3iV2t93Jm8PYMo6oCTjcVMZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoR
saS8I8nkvof/uZS2+F0gStRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTD
KCOM/iczQ0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQBjLMi
6Et8Vcad+qMUu2WFbm5PEn4KPJ2V
-----END CERTIFICATE-----
Izenpe.com
==========
-----BEGIN CERTIFICATE-----
MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4MQswCQYDVQQG
EwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wHhcNMDcxMjEz
MTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMu
QS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ
03rKDx6sp4boFmVqscIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAK
ClaOxdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6HLmYRY2xU
+zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFXuaOKmMPsOzTFlUFpfnXC
PCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQDyCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxT
OTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbK
F7jJeodWLBoBHmy+E60QrLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK
0GqfvEyNBjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8Lhij+
0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIBQFqNeb+Lz0vPqhbB
leStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+HMh3/1uaD7euBUbl8agW7EekFwID
AQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2luZm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+
SVpFTlBFIFMuQS4gLSBDSUYgQTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBG
NjIgUzgxQzBBBgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx
MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O
BBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUAA4ICAQB4pgwWSp9MiDrAyw6l
Fn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWblaQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbga
kEyrkgPH7UIBzg/YsfqikuFgba56awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8q
hT/AQKM6WfxZSzwoJNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Cs
g1lwLDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCTVyvehQP5
aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGkLhObNA5me0mrZJfQRsN5
nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJbUjWumDqtujWTI6cfSN01RpiyEGjkpTHC
ClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZo
Q0iy2+tzJOeRf1SktoA+naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1Z
WrOZyGlsQyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw==
-----END CERTIFICATE-----
Chambers of Commerce Root - 2008
================================
-----BEGIN CERTIFICATE-----
MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYDVQQGEwJFVTFD
MEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNv
bS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMu
QS4xKTAnBgNVBAMTIENoYW1iZXJzIG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4MB4XDTA4MDgwMTEy
Mjk1MFoXDTM4MDczMTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNl
ZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIwEAYDVQQF
EwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJl
cnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
AQCvAMtwNyuAWko6bHiUfaN/Gh/2NdW928sNRHI+JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKA
XuFixrYp4YFs8r/lfTJqVKAyGVn+H4vXPWCGhSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorj
h40G072QDuKZoRuGDtqaCrsLYVAGUvGef3bsyw/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR5gN/
ikilTWh1uem8nk4ZcfUyS5xtYBkL+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfLZEFHcpOrUMPrCXZk
NNI5t3YRCQ12RcSprj1qr7V9ZS+UWBDsXHyvfuK2GNnQm05aSd+pZgvMPMZ4fKecHePOjlO+Bd5g
D2vlGts/4+EhySnB8esHnFIbAURRPHsl18TlUlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331
lubKgdaX8ZSD6e2wsWsSaR6s+12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ
0wlf2eOKNcx5Wk0ZN5K3xMGtr/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhj
ya6BXBg14JC7vjxPNyK5fuvPnnchpj04gftI2jE9K+OJ9dC1vX7gUMQSibMjmhAxhduub+84Mxh2
EQIDAQABo4IBbDCCAWgwEgYDVR0TAQH/BAgwBgEB/wIBDDAdBgNVHQ4EFgQU+SSsD7K1+HnA+mCI
G8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU+SSsD7K1+HnA+mCIG8TZTQKeFxmhgbSkgbEwga4xCzAJ
BgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNh
bWVyZmlybWEuY29tL2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENh
bWVyZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDiC
CQCj2kJ+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUH
AgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZIhvcNAQEFBQADggIBAJASryI1
wqM58C7e6bXpeHxIvj99RZJe6dqxGfwWPJ+0W2aeaufDuV2I6A+tzyMP3iU6XsxPpcG1Lawk0lgH
3qLPaYRgM+gQDROpI9CF5Y57pp49chNyM/WqfcZjHwj0/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbU
RWpGqOt1glanq6B8aBMz9p0w8G8nOSQjKpD9kCk18pPfNKXG9/jvjA9iSnyu0/VU+I22mlaHFoI6
M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO+rcf+g769HsJtg1pDDFOqxXnrN2pSB7+R5KBWIBpih1
YJeSDW4+TTdDDZIVnBgizVGZoCkaPF+KMjNbMMeJL0eYD6MDxvbxrN8y8NmBGuScvfaAFPDRLLmF
9dijscilIeUcE5fuDr3fKanvNFNb0+RqE4QGtjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcK
zBIKinmwPQN/aUv0NCB9szTqjktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvG
nrDQWzilm1DefhiYtUU79nm06PcaewaD+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRg
OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZd0jQ
-----END CERTIFICATE-----
Global Chambersign Root - 2008
==============================
-----BEGIN CERTIFICATE-----
MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYDVQQGEwJFVTFD
MEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNv
bS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMu
QS4xJzAlBgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODAeFw0wODA4MDExMjMx
NDBaFw0zODA3MzExMjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUg
Y3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJ
QTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBD
aGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDf
VtPkOpt2RbQT2//BthmLN0EYlVJH6xedKYiONWwGMi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXf
XjaOcNFccUMd2drvXNL7G706tcuto8xEpw2uIRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0
ZJJ0YPP2zxhPYLIj0Mc7zmFLmY/CDNBAspjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4ddPB
/gBVsIdU6CeQNR1MM62X/JcumIS/LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyGHoiMvvKRhI9lNNgA
TH23MRdaKXoKGCQwoze1eqkBfSbW+Q6OWfH9GzO1KTsXO0G2Id3UwD2ln58fQ1DJu7xsepeY7s2M
H/ucUa6LcL0nn3HAa6x9kGbo1106DbDVwo3VyJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfe
Ox2YItaswTXbo6Al/3K1dh3ebeksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSF
HTynyQbehP9r6GsaPMWis0L7iwk+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9Jh
wZG7SMA0j0GMS0zbaRL/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsogzCtLkykPAgMB
AAGjggFqMIIBZjASBgNVHRMBAf8ECDAGAQH/AgEMMB0GA1UdDgQWBBS5CcqcHtvTbDprru1U8VuT
BjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDprru1U8VuTBjUuXqGBsqSBrzCBrDELMAkGA1UE
BhMCRVUxQzBBBgNVBAcTOk1hZHJpZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cuY2FtZXJm
aXJtYS5jb20vYWRkcmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMSQUMgQ2FtZXJm
aXJtYSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAtIDIwMDiCCQDJzdPp
1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0
dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZIhvcNAQEFBQADggIBAICIf3DekijZBZRG
/5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZUohwEurdPfWbU1Rv4WCiqAm57OtZfMY18dwY6fFn5a+6
ReAJ3spED8IXDneRRXozX1+WLGiLwUePmJs9wOzL9dWCkoQ10b42OFZyMVtHLaoXpGNR6woBrX/s
dZ7LoR/xfxKxueRkf2fWIyr0uDldmOghp+G9PUIadJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVza2Mg
9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJjUhjxsYjV5KTXjXBjfkK9yydYhz2rXzdpjEetrHH
foUm+qRqtdpjMNHvkzeyZi99Bffnt0uYlDXA2TopwZ2yUDMdSqlapskD7+3056huirRXhOukP9Du
qqqHW2Pok+JrqNS4cnhrG+055F3Lm6qH1U9OAP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETr
P3iZ8ntxPjzxmKfFGBI/5rsoM0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVq
c5iJWzouE4gev8CSlDQb4ye3ix5vQv/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z
09gwzxMNTxXJhLynSC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp/B
-----END CERTIFICATE-----
Go Daddy Root Certificate Authority - G2
========================================
-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT
B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoTEUdvRGFkZHkuY29tLCBJbmMu
MTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5
MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6
b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8G
A1UEAxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKDE6bFIEMBO4Tx5oVJnyfq
9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD
+qK+ihVqf94Lw7YZFAXK6sOoBJQ7RnwyDfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutd
fMh8+7ArU6SSYmlRJQVhGkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMl
NAJWJwGRtDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEAAaNC
MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFDqahQcQZyi27/a9
BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmXWWcDYfF+OwYxdS2hII5PZYe096ac
vNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r
5N9ss4UXnT3ZJE95kTXWXwTrgIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYV
N8Gb5DKj7Tjo2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO
LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI4uJEvlz36hz1
-----END CERTIFICATE-----
Starfield Root Certificate Authority - G2
=========================================
-----BEGIN CERTIFICATE-----
MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT
B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s
b2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVsZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0
eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAw
DgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQg
VGVjaG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZpY2F0ZSBB
dXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL3twQP89o/8ArFv
W59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMgnLRJdzIpVv257IzdIvpy3Cdhl+72WoTs
bhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNk
N3mSwOxGXn/hbVNMYq/NHwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7Nf
ZTD4p7dNdloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0HZbU
JtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
AQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0GCSqGSIb3DQEBCwUAA4IBAQARWfol
TwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjUsHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx
4mcujJUDJi5DnUox9g61DLu34jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUw
F5okxBDgBPfg8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K
pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1mMpYjn0q7pBZ
c2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0
-----END CERTIFICATE-----
Starfield Services Root Certificate Authority - G2
==================================================
-----BEGIN CERTIFICATE-----
MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMxEDAOBgNVBAgT
B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s
b2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVsZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRl
IEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNV
BAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxT
dGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2VydmljZXMg
Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20pOsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2
h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm28xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4Pa
hHQUw2eeBGg6345AWh1KTs9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLP
LJGmpufehRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk6mFB
rMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAwDwYDVR0TAQH/BAUw
AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+qAdcwKziIorhtSpzyEZGDMA0GCSqG
SIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMIbw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPP
E95Dz+I0swSdHynVv/heyNXBve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTy
xQGjhdByPq1zqwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd
iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn0q23KXB56jza
YyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCNsSi6
-----END CERTIFICATE-----
AffirmTrust Commercial
======================
-----BEGIN CERTIFICATE-----
MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UEBhMCVVMxFDAS
BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMB4XDTEw
MDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly
bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6Eqdb
DuKPHx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yrba0F8PrV
C8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPALMeIrJmqbTFeurCA+ukV6
BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1yHp52UKqK39c/s4mT6NmgTWvRLpUHhww
MmWd5jyTXlBOeuM61G7MGvv50jeuJCqrVwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNV
HQ4EFgQUnZPGU4teyq8/nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
AQYwDQYJKoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYGXUPG
hi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNjvbz4YYCanrHOQnDi
qX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivtZ8SOyUOyXGsViQK8YvxO8rUzqrJv
0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9gN53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0kh
sUlHRUe072o0EclNmsxZt9YCnlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8=
-----END CERTIFICATE-----
AffirmTrust Networking
======================
-----BEGIN CERTIFICATE-----
MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UEBhMCVVMxFDAS
BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMB4XDTEw
MDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly
bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SE
Hi3yYJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbuakCNrmreI
dIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRLQESxG9fhwoXA3hA/Pe24
/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gb
h+0t+nvujArjqWaJGctB+d1ENmHP4ndGyH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNV
HQ4EFgQUBx/S55zawm6iQLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
AQYwDQYJKoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfOtDIu
UFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzuQY0x2+c06lkh1QF6
12S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZLgo/bNjR9eUJtGxUAArgFU2HdW23
WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4uolu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9
/ZFvgrG+CJPbFEfxojfHRZ48x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s=
-----END CERTIFICATE-----
AffirmTrust Premium
===================
-----BEGIN CERTIFICATE-----
MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UEBhMCVVMxFDAS
BgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMB4XDTEwMDEy
OTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRy
dXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
MIICCgKCAgEAxBLfqV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtn
BKAQJG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ+jjeRFcV
5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrSs8PhaJyJ+HoAVt70VZVs
+7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmd
GPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d770O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5R
p9EixAqnOEhss/n/fauGV+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NI
S+LI+H+SqHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S5u04
6uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4IaC1nEWTJ3s7xgaVY5
/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TXOwF0lkLgAOIua+rF7nKsu7/+6qqo
+Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYEFJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB
/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByv
MiPIs0laUZx2KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg
Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B8OWycvpEgjNC
6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQMKSOyARiqcTtNd56l+0OOF6S
L5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK
+4w1IX2COPKpVJEZNZOUbWo6xbLQu4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmV
BtWVyuEklut89pMFu+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFg
IxpHYoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8GKa1qF60
g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaORtGdFNrHF+QFlozEJLUb
zxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6eKeC2uAloGRwYQw==
-----END CERTIFICATE-----
AffirmTrust Premium ECC
=======================
-----BEGIN CERTIFICATE-----
MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMCVVMxFDASBgNV
BAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQcmVtaXVtIEVDQzAeFw0xMDAx
MjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1U
cnVzdDEgMB4GA1UEAwwXQWZmaXJtVHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQA
IgNiAAQNMF4bFZ0D0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQ
N8O9ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0GA1UdDgQW
BBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAK
BggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/VsaobgxCd05DhT1wV/GzTjxi+zygk8N53X
57hG8f2h4nECMEJZh0PUUd+60wkyWs6Iflc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKM
eQ==
-----END CERTIFICATE-----
Certum Trusted Network CA
=========================
-----BEGIN CERTIFICATE-----
MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK
ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIy
MTIwNzM3WhcNMjkxMjMxMTIwNzM3WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBU
ZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5
MSIwIAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rHUV+rpDKmYYe2bg+G0jAC
l/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LMTXPb865Px1bVWqeWifrzq2jUI4ZZJ88J
J7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVUBBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4
fOQtf/WsX+sWn7Et0brMkUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0
cvW0QM8xAcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNVHRMB
Af8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNVHQ8BAf8EBAMCAQYw
DQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15ysHhE49wcrwn9I0j6vSrEuVUEtRCj
jSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfLI9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1
mS1FhIrlQgnXdAIv94nYmem8J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5aj
Zt3hrvJBW8qYVoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI
03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw=
-----END CERTIFICATE-----
TWCA Root Certification Authority
=================================
-----BEGIN CERTIFICATE-----
MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJ
VEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMzWhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQG
EwJUVzESMBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NB
IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFEAcK0HMMx
QhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HHK3XLfJ+utdGdIzdjp9xC
oi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeXRfwZVzsrb+RH9JlF/h3x+JejiB03HFyP
4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/zrX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1r
y+UPizgN7gr8/g+YnzAx3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIB
BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkqhkiG
9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeCMErJk/9q56YAf4lC
mtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdlsXebQ79NqZp4VKIV66IIArB6nCWlW
QtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62Dlhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVY
T0bf+215WfKEIlKuD8z7fDvnaspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocny
Yh0igzyXxfkZYiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw==
-----END CERTIFICATE-----
Security Communication RootCA2
==============================
-----BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDElMCMGA1UEChMc
U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMeU2VjdXJpdHkgQ29tbXVuaWNh
dGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoXDTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMC
SlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3Vy
aXR5IENvbW11bmljYXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
ANAVOVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGrzbl+dp++
+T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVMVAX3NuRFg3sUZdbcDE3R
3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQhNBqyjoGADdH5H5XTz+L62e4iKrFvlNV
spHEfbmwhRkGeC7bYRr6hfVKkaHnFtWOojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1K
EOtOghY6rCcMU/Gt1SSwawNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8
QIH4D5csOPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB
CwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpFcoJxDjrSzG+ntKEj
u/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXcokgfGT+Ok+vx+hfuzU7jBBJV1uXk
3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6q
tnRGEmyR7jTV7JqR50S+kDFy1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29
mvVXIwAHIRc/SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03
-----END CERTIFICATE-----
EC-ACC
======
-----BEGIN CERTIFICATE-----
MIIFVjCCBD6gAwIBAgIQ7is969Qh3hSoYqwE893EATANBgkqhkiG9w0BAQUFADCB8zELMAkGA1UE
BhMCRVMxOzA5BgNVBAoTMkFnZW5jaWEgQ2F0YWxhbmEgZGUgQ2VydGlmaWNhY2lvIChOSUYgUS0w
ODAxMTc2LUkpMSgwJgYDVQQLEx9TZXJ2ZWlzIFB1YmxpY3MgZGUgQ2VydGlmaWNhY2lvMTUwMwYD
VQQLEyxWZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAoYykwMzE1MDMGA1UE
CxMsSmVyYXJxdWlhIEVudGl0YXRzIGRlIENlcnRpZmljYWNpbyBDYXRhbGFuZXMxDzANBgNVBAMT
BkVDLUFDQzAeFw0wMzAxMDcyMzAwMDBaFw0zMTAxMDcyMjU5NTlaMIHzMQswCQYDVQQGEwJFUzE7
MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8gKE5JRiBRLTA4MDExNzYt
SSkxKDAmBgNVBAsTH1NlcnZlaXMgUHVibGljcyBkZSBDZXJ0aWZpY2FjaW8xNTAzBgNVBAsTLFZl
Z2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQubmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLEyxKZXJh
cnF1aWEgRW50aXRhdHMgZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAxMGRUMtQUND
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsyLHT+KXQpWIR4NA9h0X84NzJB5R85iK
w5K4/0CQBXCHYMkAqbWUZRkiFRfCQ2xmRJoNBD45b6VLeqpjt4pEndljkYRm4CgPukLjbo73FCeT
ae6RDqNfDrHrZqJyTxIThmV6PttPB/SnCWDaOkKZx7J/sxaVHMf5NLWUhdWZXqBIoH7nF2W4onW4
HvPlQn2v7fOKSGRdghST2MDk/7NQcvJ29rNdQlB50JQ+awwAvthrDk4q7D7SzIKiGGUzE3eeml0a
E9jD2z3Il3rucO2n5nzbcc8tlGLfbdb1OL4/pYUKGbio2Al1QnDE6u/LDsg0qBIimAy4E5S2S+zw
0JDnJwIDAQABo4HjMIHgMB0GA1UdEQQWMBSBEmVjX2FjY0BjYXRjZXJ0Lm5ldDAPBgNVHRMBAf8E
BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUoMOLRKo3pUW/l4Ba0fF4opvpXY0wfwYD
VR0gBHgwdjB0BgsrBgEEAfV4AQMBCjBlMCwGCCsGAQUFBwIBFiBodHRwczovL3d3dy5jYXRjZXJ0
Lm5ldC92ZXJhcnJlbDA1BggrBgEFBQcCAjApGidWZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5l
dC92ZXJhcnJlbCAwDQYJKoZIhvcNAQEFBQADggEBAKBIW4IB9k1IuDlVNZyAelOZ1Vr/sXE7zDkJ
lF7W2u++AVtd0x7Y/X1PzaBB4DSTv8vihpw3kpBWHNzrKQXlxJ7HNd+KDM3FIUPpqojlNcAZQmNa
Al6kSBg6hW/cnbw/nZzBh7h6YQjpdwt/cKt63dmXLGQehb+8dJahw3oS7AwaboMMPOhyRp/7SNVe
l+axofjk70YllJyJ22k4vuxcDlbHZVHlUIiIv0LVKz3l+bqeLrPK9HOSAgu+TGbrIP65y7WZf+a2
E/rKS03Z7lNGBjvGTq2TWoF+bCpLagVFjPIhpDGQh2xlnJ2lYJU6Un/10asIbvPuW/mIPX64b24D
5EI=
-----END CERTIFICATE-----
Hellenic Academic and Research Institutions RootCA 2011
=======================================================
-----BEGIN CERTIFICATE-----
MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1IxRDBCBgNVBAoT
O0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9y
aXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25z
IFJvb3RDQSAyMDExMB4XDTExMTIwNjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYT
AkdSMUQwQgYDVQQKEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25z
IENlcnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNo
IEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPzdYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI
1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJfel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa
71HFK9+WXesyHgLacEnsbgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u
8yBRQlqD75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSPFEDH
3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNVHRMBAf8EBTADAQH/
MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp5dgTBCPuQSUwRwYDVR0eBEAwPqA8
MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQub3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQu
b3JnMA0GCSqGSIb3DQEBBQUAA4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVt
XdMiKahsog2p6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8
TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7dIsXRSZMFpGD
/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8AcysNnq/onN694/BtZqhFLKPM58N
7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXIl7WdmplNsDz4SgCbZN2fOUvRJ9e4
-----END CERTIFICATE-----
Actalis Authentication Root CA
==============================
-----BEGIN CERTIFICATE-----
MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCSVQxDjAM
BgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UE
AwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDky
MjExMjIwMlowazELMAkGA1UEBhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlz
IFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290
IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNvUTufClrJ
wkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX4ay8IMKx4INRimlNAJZa
by/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9KK3giq0itFZljoZUj5NDKd45RnijMCO6
zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1f
YVEiVRvjRuPjPdA1YprbrxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2
oxgkg4YQ51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2Fbe8l
EfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxeKF+w6D9Fz8+vm2/7
hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4Fv6MGn8i1zeQf1xcGDXqVdFUNaBr8
EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbnfpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5
jF66CyCU3nuDuP/jVo23Eek7jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLY
iDrIn3hm7YnzezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt
ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQALe3KHwGCmSUyI
WOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70jsNjLiNmsGe+b7bAEzlgqqI0
JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDzWochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKx
K3JCaKygvU5a2hi/a5iB0P2avl4VSM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+
Xlff1ANATIGk0k9jpwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC
4yyXX04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+OkfcvHlXHo
2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7RK4X9p2jIugErsWx0Hbhz
lefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btUZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXem
OR/qnuOf0GZvBeyqdn6/axag67XH/JJULysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9
vwGYT7JZVEc+NHt4bVaTLnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg==
-----END CERTIFICATE-----
Buypass Class 2 Root CA
=======================
-----BEGIN CERTIFICATE-----
MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU
QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMiBSb290IENBMB4X
DTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1owTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1
eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIw
DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1
g1Lr6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPVL4O2fuPn
9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC911K2GScuVr1QGbNgGE41b
/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHxMlAQTn/0hpPshNOOvEu/XAFOBz3cFIqU
CqTqc/sLUegTBxj6DvEr0VQVfTzh97QZQmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeff
awrbD02TTqigzXsu8lkBarcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgI
zRFo1clrUs3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLiFRhn
Bkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRSP/TizPJhk9H9Z2vX
Uq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN9SG9dKpN6nIDSdvHXx1iY8f93ZHs
M+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxPAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD
VR0OBBYEFMmAd+BikoL1RpzzuvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF
AAOCAgEAU18h9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s
A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3tOluwlN5E40EI
osHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo+fsicdl9sz1Gv7SEr5AcD48S
aq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYd
DnkM/crqJIByw5c/8nerQyIKx+u2DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWD
LfJ6v9r9jv6ly0UsH8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0
oyLQI+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK75t98biGC
wWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h3PFaTWwyI0PurKju7koS
CTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPzY11aWOIv4x3kqdbQCtCev9eBCfHJxyYN
rJgWVqA=
-----END CERTIFICATE-----
Buypass Class 3 Root CA
=======================
-----BEGIN CERTIFICATE-----
MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU
QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMyBSb290IENBMB4X
DTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFowTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1
eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIw
DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRH
sJ8YZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3EN3coTRiR
5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9tznDDgFHmV0ST9tD+leh
7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX0DJq1l1sDPGzbjniazEuOQAnFN44wOwZ
ZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH
2xc519woe2v1n/MuwU8XKhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV
/afmiSTYzIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvSO1UQ
RwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D34xFMFbG02SrZvPA
Xpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgPK9Dx2hzLabjKSWJtyNBjYt1gD1iq
j6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD
VR0OBBYEFEe4zf/lb+74suwvTg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF
AAOCAgEAACAjQTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV
cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXSIGrs/CIBKM+G
uIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2HJLw5QY33KbmkJs4j1xrG0aG
Q0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsaO5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8
ZORK15FTAaggiG6cX0S5y2CBNOxv033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2
KSb12tjE8nVhz36udmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz
6MkEkbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg413OEMXbug
UZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvDu79leNKGef9JOxqDDPDe
eOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq4/g7u9xN12TyUb7mqqta6THuBrxzvxNi
Cp/HuZc=
-----END CERTIFICATE-----
T-TeleSec GlobalRoot Class 3
============================
-----BEGIN CERTIFICATE-----
MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM
IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU
cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgx
MDAxMTAyOTU2WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz
dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD
ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN8ELg63iIVl6bmlQdTQyK
9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/RLyTPWGrTs0NvvAgJ1gORH8EGoel15YU
NpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZF
iP0Zf3WHHx+xGwpzJFu5ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W
0eDrXltMEnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGjQjBA
MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1A/d2O2GCahKqGFPr
AyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOyWL6ukK2YJ5f+AbGwUgC4TeQbIXQb
fsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzT
ucpH9sry9uetuUg/vBa3wW306gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7h
P0HHRwA11fXT91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml
e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4pTpPDpFQUWw==
-----END CERTIFICATE-----
D-TRUST Root Class 3 CA 2 2009
==============================
-----BEGIN CERTIFICATE-----
MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQK
DAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTAe
Fw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NThaME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxE
LVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIw
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOAD
ER03UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42tSHKXzlA
BF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9RySPocq60vFYJfxLLHLGv
KZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsMlFqVlNpQmvH/pStmMaTJOKDfHR+4CS7z
p+hnUquVH+BGPtikw8paxTGA6Eian5Rp/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUC
AwEAAaOCARowggEWMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ
4PGEMA4GA1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVjdG9y
eS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUyMENBJTIwMiUyMDIw
MDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRlcmV2b2NhdGlvbmxpc3QwQ6BBoD+G
PWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAw
OS5jcmwwDQYJKoZIhvcNAQELBQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm
2H6NMLVwMeniacfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0
o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4KzCUqNQT4YJEV
dT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8PIWmawomDeCTmGCufsYkl4ph
X5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3YJohw1+qRzT65ysCQblrGXnRl11z+o+I=
-----END CERTIFICATE-----
D-TRUST Root Class 3 CA 2 EV 2009
=================================
-----BEGIN CERTIFICATE-----
MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK
DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw
OTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUwNDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK
DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw
OTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfS
egpnljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM03TP1YtHh
zRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6ZqQTMFexgaDbtCHu39b+T
7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lRp75mpoo6Kr3HGrHhFPC+Oh25z1uxav60
sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure35
11H3a6UCAwEAAaOCASQwggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyv
cop9NteaHNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFwOi8v
ZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xhc3MlMjAzJTIwQ0El
MjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1ERT9jZXJ0aWZpY2F0ZXJldm9jYXRp
b25saXN0MEagRKBChkBodHRwOi8vd3d3LmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xh
c3NfM19jYV8yX2V2XzIwMDkuY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+
PPoeUSbrh/Yp3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05
nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNFCSuGdXzfX2lX
ANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7naxpeG0ILD5EJt/rDiZE4OJudA
NCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqXKVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVv
w9y4AyHqnxbxLFS1
-----END CERTIFICATE-----
CA Disig Root R2
================
-----BEGIN CERTIFICATE-----
MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNVBAYTAlNLMRMw
EQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNp
ZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQyMDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sx
EzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERp
c2lnIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbC
w3OeNcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNHPWSb6Wia
xswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3Ix2ymrdMxp7zo5eFm1tL7
A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbeQTg06ov80egEFGEtQX6sx3dOy1FU+16S
GBsEWmjGycT6txOgmLcRK7fWV8x8nhfRyyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqV
g8NTEQxzHQuyRpDRQjrOQG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa
5Beny912H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJQfYE
koopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUDi/ZnWejBBhG93c+A
Ak9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORsnLMOPReisjQS1n6yqEm70XooQL6i
Fh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNV
HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5u
Qu0wDQYJKoZIhvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM
tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqfGopTpti72TVV
sRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkblvdhuDvEK7Z4bLQjb/D907Je
dR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka+elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W8
1k/BfDxujRNt+3vrMNDcTa/F1balTFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjx
mHHEt38OFdAlab0inSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01
utI3gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18DrG5gPcFw0
sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3OszMOl6W8KjptlwlCFtaOg
UxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8xL4ysEr3vQCj8KWefshNPZiTEUxnpHikV
7+ZtsH8tZ/3zbBt1RqPlShfppNcL
-----END CERTIFICATE-----
ACCVRAIZ1
=========
-----BEGIN CERTIFICATE-----
MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UEAwwJQUNDVlJB
SVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQswCQYDVQQGEwJFUzAeFw0xMTA1
MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQBgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwH
UEtJQUNDVjENMAsGA1UECgwEQUNDVjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4IC
DwAwggIKAoICAQCbqau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gM
jmoYHtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWoG2ioPej0
RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpAlHPrzg5XPAOBOp0KoVdD
aaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhrIA8wKFSVf+DuzgpmndFALW4ir50awQUZ
0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDG
WuzndN9wrqODJerWx5eHk6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs7
8yM2x/474KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMOm3WR
5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpacXpkatcnYGMN285J
9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPluUsXQA+xtrn13k/c4LOsOxFwYIRK
Q26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYIKwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRw
Oi8vd3d3LmFjY3YuZXMvZmlsZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEu
Y3J0MB8GCCsGAQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2
VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeTVfZW6oHlNsyM
Hj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIGCCsGAQUFBwICMIIBFB6CARAA
QQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUAcgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBh
AO0AegAgAGQAZQAgAGwAYQAgAEEAQwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUA
YwBuAG8AbABvAGcA7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBj
AHQAcgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAAQwBQAFMA
IABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUAczAwBggrBgEFBQcCARYk
aHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2MuaHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0
dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRtaW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2
MV9kZXIuY3JsMA4GA1UdDwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZI
hvcNAQEFBQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdpD70E
R9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gUJyCpZET/LtZ1qmxN
YEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+mAM/EKXMRNt6GGT6d7hmKG9Ww7Y49
nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepDvV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJ
TS+xJlsndQAJxGJ3KQhfnlmstn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3
sCPdK6jT2iWH7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h
I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szAh1xA2syVP1Xg
Nce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xFd3+YJ5oyXSrjhO7FmGYvliAd
3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2HpPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3p
EfbRD0tVNEYqi4Y7
-----END CERTIFICATE-----
TWCA Global Root CA
===================
-----BEGIN CERTIFICATE-----
MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcxEjAQBgNVBAoT
CVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMTVFdDQSBHbG9iYWwgUm9vdCBD
QTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQK
EwlUQUlXQU4tQ0ExEDAOBgNVBAsTB1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3Qg
Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2C
nJfF10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz0ALfUPZV
r2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfChMBwqoJimFb3u/Rk28OKR
Q4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbHzIh1HrtsBv+baz4X7GGqcXzGHaL3SekV
tTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1W
KKD+u4ZqyPpcC1jcxkt2yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99
sy2sbZCilaLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYPoA/p
yJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQABDzfuBSO6N+pjWxn
kjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcEqYSjMq+u7msXi7Kx/mzhkIyIqJdI
zshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMC
AQYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6g
cFGn90xHNcgL1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn
LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WFH6vPNOw/KP4M
8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNoRI2T9GRwoD2dKAXDOXC4Ynsg
/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlg
lPx4mI88k1HtQJAH32RjJMtOcQWh15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryP
A9gK8kxkRr05YuWW6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3m
i4TWnsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5jwa19hAM8
EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWzaGHQRiapIVJpLesux+t3
zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmyKwbQBM0=
-----END CERTIFICATE-----
TeliaSonera Root CA v1
======================
-----BEGIN CERTIFICATE-----
MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAwNzEUMBIGA1UE
CgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJvb3QgQ0EgdjEwHhcNMDcxMDE4
MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYDVQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwW
VGVsaWFTb25lcmEgUm9vdCBDQSB2MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+
6yfwIaPzaSZVfp3FVRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA
3GV17CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+XZ75Ljo1k
B1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+/jXh7VB7qTCNGdMJjmhn
Xb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxH
oLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkmdtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3
F0fUTPHSiXk+TT2YqGHeOh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJ
oWjiUIMusDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4pgd7
gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fsslESl1MpWtTwEhDc
TwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQarMCpgKIv7NHfirZ1fpoeDVNAgMB
AAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qW
DNXr+nuqF+gTEjANBgkqhkiG9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNm
zqjMDfz1mgbldxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx
0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1TjTQpgcmLNkQfW
pb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBedY2gea+zDTYa4EzAvXUYNR0PV
G6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpc
c41teyWRyu5FrgZLAMzTsVlQ2jqIOylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOT
JsjrDNYmiLbAJM+7vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2
qReWt88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcnHL/EVlP6
Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVxSK236thZiNSQvxaz2ems
WWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY=
-----END CERTIFICATE-----
E-Tugra Certification Authority
===============================
-----BEGIN CERTIFICATE-----
MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNVBAYTAlRSMQ8w
DQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamls
ZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN
ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMw
NTEyMDk0OFoXDTIzMDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmEx
QDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxl
cmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQD
DB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
MIICCgKCAgEA4vU/kwVRHoViVF56C/UYB4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vd
hQd2h8y/L5VMzH2nPbxHD5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5K
CKpbknSFQ9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEoq1+g
ElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3Dk14opz8n8Y4e0ypQ
BaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcHfC425lAcP9tDJMW/hkd5s3kc91r0
E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsutdEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gz
rt48Ue7LE3wBf4QOXVGUnhMMti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAq
jqFGOjGY5RH8zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn
rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUXU8u3Zg5mTPj5
dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6Jyr+zE7S6E5UMA8GA1UdEwEB
/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEG
MA0GCSqGSIb3DQEBCwUAA4ICAQAFNzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAK
kEh47U6YA5n+KGCRHTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jO
XKqYGwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c77NCR807
VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3+GbHeJAAFS6LrVE1Uweo
a2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WKvJUawSg5TB9D0pH0clmKuVb8P7Sd2nCc
dlqMQ1DujjByTd//SffGqWfZbawCEeI6FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEV
KV0jq9BgoRJP3vQXzTLlyb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gT
Dx4JnW2PAJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpDy4Q0
8ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8dNL/+I5c30jn6PQ0G
C7TbO6Orb1wdtn7os4I07QZcJA==
-----END CERTIFICATE-----
T-TeleSec GlobalRoot Class 2
============================
-----BEGIN CERTIFICATE-----
MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM
IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU
cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgx
MDAxMTA0MDE0WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz
dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD
ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUdAqSzm1nzHoqvNK38DcLZ
SBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiCFoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/F
vudocP05l03Sx5iRUKrERLMjfTlH6VJi1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx970
2cu+fjOlbpSD8DT6IavqjnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGV
WOHAD3bZwI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGjQjBA
MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/WSA2AHmgoCJrjNXy
YdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhyNsZt+U2e+iKo4YFWz827n+qrkRk4
r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPACuvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNf
vNoBYimipidx5joifsFvHZVwIEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR
3p1m0IvVVGb6g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN
9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlPBSeOE6Fuwg==
-----END CERTIFICATE-----
Atos TrustedRoot 2011
=====================
-----BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UEAwwVQXRvcyBU
cnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0xMTA3MDcxNDU4
MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMMFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsG
A1UECgwEQXRvczELMAkGA1UEBhMCREUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCV
hTuXbyo7LjvPpvMpNb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr
54rMVD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+SZFhyBH+
DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ4J7sVaE3IqKHBAUsR320
HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0Lcp2AMBYHlT8oDv3FdU9T1nSatCQujgKR
z3bFmx5VdJx4IbHwLfELn8LVlhgf8FQieowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7R
l+lwrrw7GWzbITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZ
bNshMBgGA1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB
CwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8jvZfza1zv7v1Apt+h
k6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kPDpFrdRbhIfzYJsdHt6bPWHJxfrrh
TZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pcmaHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a9
61qn8FYiqTxlVMYVqL2Gns2Dlmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G
3mB/ufNPRJLvKrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed
-----END CERTIFICATE-----
QuoVadis Root CA 1 G3
=====================
-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQELBQAwSDELMAkG
A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv
b3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJN
MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEg
RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakE
PBtVwedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWerNrwU8lm
PNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF34168Xfuw6cwI2H44g4hWf6
Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh4Pw5qlPafX7PGglTvF0FBM+hSo+LdoIN
ofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXpUhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/l
g6AnhF4EwfWQvTA9xO+oabw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV
7qJZjqlc3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/GKubX
9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSthfbZxbGL0eUQMk1f
iyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KOTk0k+17kBL5yG6YnLUlamXrXXAkg
t3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOtzCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD
AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZI
hvcNAQELBQADggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC
MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2cDMT/uFPpiN3
GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUNqXsCHKnQO18LwIE6PWThv6ct
Tr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP
+V04ikkwj+3x6xn0dxoxGE1nVGwvb2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh
3jRJjehZrJ3ydlo28hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fa
wx/kNSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNjZgKAvQU6
O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhpq1467HxpvMc7hU6eFbm0
FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFtnh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOV
hMJKzRwuJIczYOXD
-----END CERTIFICATE-----
QuoVadis Root CA 2 G3
=====================
-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQELBQAwSDELMAkG
A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv
b3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJN
MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIg
RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFh
ZiFfqq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMWn4rjyduY
NM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ymc5GQYaYDFCDy54ejiK2t
oIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+O7q414AB+6XrW7PFXmAqMaCvN+ggOp+o
MiwMzAkd056OXbxMmO7FGmh77FOm6RQ1o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+l
V0POKa2Mq1W/xPtbAd0jIaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZo
L1NesNKqIcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz8eQQ
sSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43ehvNURG3YBZwjgQQvD
6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l7ZizlWNof/k19N+IxWA1ksB8aRxh
lRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALGcC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTAD
AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZI
hvcNAQELBQADggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66
AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RCroijQ1h5fq7K
pVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0GaW/ZZGYjeVYg3UQt4XAoeo0L9
x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4nlv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgz
dWqTHBLmYF5vHX/JHyPLhGGfHoJE+V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6X
U/IyAgkwo1jwDQHVcsaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+Nw
mNtddbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNgKCLjsZWD
zYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeMHVOyToV7BjjHLPj4sHKN
JeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4WSr2Rz0ZiC3oheGe7IUIarFsNMkd7Egr
O3jtZsSOeWmD3n+M
-----END CERTIFICATE-----
QuoVadis Root CA 3 G3
=====================
-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQELBQAwSDELMAkG
A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv
b3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJN
MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMg
RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286
IxSR/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNuFoM7pmRL
Mon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXRU7Ox7sWTaYI+FrUoRqHe
6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+cra1AdHkrAj80//ogaX3T7mH1urPnMNA3
I4ZyYUUpSFlob3emLoG+B01vr87ERRORFHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3U
VDmrJqMz6nWB2i3ND0/kA9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f7
5li59wzweyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634RylsSqi
Md5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBpVzgeAVuNVejH38DM
dyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0QA4XN8f+MFrXBsj6IbGB/kE+V9/Yt
rQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD
AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZI
hvcNAQELBQADggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px
KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnIFUBhynLWcKzS
t/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5WvvoxXqA/4Ti2Tk08HS6IT7SdEQ
TXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFgu/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9Du
DcpmvJRPpq3t/O5jrFc/ZSXPsoaP0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGib
Ih6BJpsQBJFxwAYf3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmD
hPbl8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+DhcI00iX
0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HNPlopNLk9hM6xZdRZkZFW
dSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ywaZWWDYWGWVjUTR939+J399roD1B0y2
PpxxVJkES/1Y+Zj0
-----END CERTIFICATE-----
DigiCert Assured ID Root G2
===========================
-----BEGIN CERTIFICATE-----
MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQG
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw
IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgw
MTE1MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL
ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIw
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSAn61UQbVH
35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4HteccbiJVMWWXvdMX0h5i89vq
bFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9HpEgjAALAcKxHad3A2m67OeYfcgnDmCXRw
VWmvo2ifv922ebPynXApVfSr/5Vh88lAbx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OP
YLfykqGxvYmJHzDNw6YuYjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+Rn
lTGNAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTO
w0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPIQW5pJ6d1Ee88hjZv
0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I0jJmwYrA8y8678Dj1JGG0VDjA9tz
d29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4GnilmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAW
hsI6yLETcDbYz+70CjTVW0z9B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0M
jomZmWzwPDCvON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo
IhNzbM8m9Yop5w==
-----END CERTIFICATE-----
DigiCert Assured ID Root G3
===========================
-----BEGIN CERTIFICATE-----
MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV
UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYD
VQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1
MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQ
BgcqhkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJfZn4f5dwb
RXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17QRSAPWXYQ1qAk8C3eNvJs
KTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgF
UaFNN6KDec6NHSrkhDAKBggqhkjOPQQDAwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5Fy
YZ5eEJJZVrmDxxDnOOlYJjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy
1vUhZscv6pZjamVFkpUBtA==
-----END CERTIFICATE-----
DigiCert Global Root G2
=======================
-----BEGIN CERTIFICATE-----
MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQG
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw
HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUx
MjAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3
dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkq
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI2/Ou8jqJ
kTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx1x7e/dfgy5SDN67sH0NO
3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQq2EGnI/yuum06ZIya7XzV+hdG82MHauV
BJVJ8zUtluNJbd134/tJS7SsVQepj5WztCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyM
UNGPHgm+F6HmIcr9g+UQvIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQAB
o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV5uNu
5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY1Yl9PMWLSn/pvtsr
F9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4NeF22d+mQrvHRAiGfzZ0JFrabA0U
WTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NGFdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBH
QRFXGU7Aj64GxJUTFy8bJZ918rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/
iyK5S9kJRaTepLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl
MrY=
-----END CERTIFICATE-----
DigiCert Global Root G3
=======================
-----BEGIN CERTIFICATE-----
MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQswCQYDVQQGEwJV
UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYD
VQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAw
MDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5k
aWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0C
AQYFK4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FGfp4tn+6O
YwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPOZ9wj/wMco+I+o0IwQDAP
BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNp
Yim8S8YwCgYIKoZIzj0EAwMDaAAwZQIxAK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y
3maTD/HMsQmP3Wyr+mt/oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34
VOKa5Vt8sycX
-----END CERTIFICATE-----
DigiCert Trusted Root G4
========================
-----BEGIN CERTIFICATE-----
MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQG
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEw
HwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1
MTIwMDAwWjBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0G
CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3yithZwuEp
pz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1Ifxp4VpX6+n6lXFllVcq9o
k3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7Fsa
vOvJz82sNEBfsXpm7nfISKhmV1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGY
QJB5w3jHtrHEtWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6
MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCiEhtm
mnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADMfRyVw4/3IbKyEbe7
f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFH
dL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8
oR7FwI+isX4KJpn15GkvmB0t9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud
DwEB/wQEAwIBhjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD
ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2SV1EY+CtnJYY
ZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd+SeuMIW59mdNOj6PWTkiU0Tr
yF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWcfFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy
7zBZLq7gcfJW5GqXb5JQbZaNaHqasjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iah
ixTXTBmyUEFxPT9NcCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN
5r5N0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie4u1Ki7wb
/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mIr/OSmbaz5mEP0oUA51Aa
5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tK
G48BtieVU+i2iW1bvGjUI+iLUaJW+fCmgKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP
82Z+
-----END CERTIFICATE-----
COMODO RSA Certification Authority
==================================
-----BEGIN CERTIFICATE-----
MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCBhTELMAkGA1UE
BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG
A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkwHhcNMTAwMTE5MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMC
R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE
ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBB
dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR6FSS0gpWsawNJN3Fz0Rn
dJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8Xpz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZ
FGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+
5eNu/Nio5JIk2kNrYrhV/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pG
x8cgoLEfZd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z+pUX
2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7wqP/0uK3pN/u6uPQL
OvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZahSL0896+1DSJMwBGB7FY79tOi4lu3
sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVICu9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+C
GCe01a60y1Dma/RMhnEw6abfFobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5
WdYgGq/yapiqcrxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E
FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w
DQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvlwFTPoCWOAvn9sKIN9SCYPBMt
rFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+
nq6PK7o9mfjYcwlYRm6mnPTXJ9OV2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSg
tZx8jb8uk2IntznaFxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwW
sRqZCuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiKboHGhfKp
pC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmckejkk9u+UJueBPSZI9FoJA
zMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yLS0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHq
ZJx64SIDqZxubw5lT2yHh17zbqD5daWbQOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk52
7RH89elWsn2/x20Kk4yl0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7I
LaZRfyHBNVOFBkpdn627G190
-----END CERTIFICATE-----
USERTrust RSA Certification Authority
=====================================
-----BEGIN CERTIFICATE-----
MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCBiDELMAkGA1UE
BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK
ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UE
BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK
ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCAEmUXNg7D2wiz
0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2j
Y0K2dvKpOyuR+OJv0OwWIJAJPuLodMkYtJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFn
RghRy4YUVD+8M/5+bJz/Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O
+T23LLb2VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT79uq
/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6c0Plfg6lZrEpfDKE
Y1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmTYo61Zs8liM2EuLE/pDkP2QKe6xJM
lXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97lc6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8
yexDJtC/QV9AqURE9JnnV4eeUB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+
eLf8ZxXhyVeEHg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd
BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF
MAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPFUp/L+M+ZBn8b2kMVn54CVVeW
FPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KOVWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ
7l8wXEskEVX/JJpuXior7gtNn3/3ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQ
Eg9zKC7F4iRO/Fjs8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM
8WcRiQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYzeSf7dNXGi
FSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZXHlKYC6SQK5MNyosycdi
yA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9c
J2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRBVXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGw
sAvgnEzDHNb842m1R0aBL6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gx
Q+6IHdfGjjxDah2nGN59PRbxYvnKkKj9
-----END CERTIFICATE-----
USERTrust ECC Certification Authority
=====================================
-----BEGIN CERTIFICATE-----
MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDELMAkGA1UEBhMC
VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU
aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMC
VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU
aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqfloI+d61SRvU8Za2EurxtW2
0eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinngo4N+LZfQYcTxmdwlkWOrfzCjtHDix6Ez
nPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0GA1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNV
HQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBB
HU6+4WMBzzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbWRNZu
9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg=
-----END CERTIFICATE-----
GlobalSign ECC Root CA - R4
===========================
-----BEGIN CERTIFICATE-----
MIIB4TCCAYegAwIBAgIRKjikHJYKBN5CsiilC+g0mAIwCgYIKoZIzj0EAwIwUDEkMCIGA1UECxMb
R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD
EwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoXDTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMb
R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD
EwpHbG9iYWxTaWduMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMZ5049sJQ6fLjkZHAOkrprl
OQcJFspjsbmG+IpXwVfOQvpzofdlQv8ewQCybnMO/8ch5RikqtlxP6jUuc6MHaNCMEAwDgYDVR0P
AQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFSwe61FuOJAf/sKbvu+M8k8o4TV
MAoGCCqGSM49BAMCA0gAMEUCIQDckqGgE6bPA7DmxCGXkPoUVy0D7O48027KqGx2vKLeuwIgJ6iF
JzWbVsaj8kfSt24bAgAXqmemFZHe+pTsewv4n4Q=
-----END CERTIFICATE-----
GlobalSign ECC Root CA - R5
===========================
-----BEGIN CERTIFICATE-----
MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEkMCIGA1UECxMb
R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD
EwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoXDTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMb
R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD
EwpHbG9iYWxTaWduMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6
SFkc8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8kehOvRnkmS
h5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd
BgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYIKoZIzj0EAwMDaAAwZQIxAOVpEslu28Yx
uglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7
yFz9SO8NdCKoCOJuxUnOxwy8p2Fp8fc74SrL+SvzZpA3
-----END CERTIFICATE-----
Staat der Nederlanden Root CA - G3
==================================
-----BEGIN CERTIFICATE-----
MIIFdDCCA1ygAwIBAgIEAJiiOTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJOTDEeMBwGA1UE
CgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFhdCBkZXIgTmVkZXJsYW5kZW4g
Um9vdCBDQSAtIEczMB4XDTEzMTExNDExMjg0MloXDTI4MTExMzIzMDAwMFowWjELMAkGA1UEBhMC
TkwxHjAcBgNVBAoMFVN0YWF0IGRlciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5l
ZGVybGFuZGVuIFJvb3QgQ0EgLSBHMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL4y
olQPcPssXFnrbMSkUeiFKrPMSjTysF/zDsccPVMeiAho2G89rcKezIJnByeHaHE6n3WWIkYFsO2t
x1ueKt6c/DrGlaf1F2cY5y9JCAxcz+bMNO14+1Cx3Gsy8KL+tjzk7FqXxz8ecAgwoNzFs21v0IJy
EavSgWhZghe3eJJg+szeP4TrjTgzkApyI/o1zCZxMdFyKJLZWyNtZrVtB0LrpjPOktvA9mxjeM3K
Tj215VKb8b475lRgsGYeCasH/lSJEULR9yS6YHgamPfJEf0WwTUaVHXvQ9Plrk7O53vDxk5hUUur
mkVLoR9BvUhTFXFkC4az5S6+zqQbwSmEorXLCCN2QyIkHxcE1G6cxvx/K2Ya7Irl1s9N9WMJtxU5
1nus6+N86U78dULI7ViVDAZCopz35HCz33JvWjdAidiFpNfxC95DGdRKWCyMijmev4SH8RY7Ngzp
07TKbBlBUgmhHbBqv4LvcFEhMtwFdozL92TkA1CvjJFnq8Xy7ljY3r735zHPbMk7ccHViLVlvMDo
FxcHErVc0qsgk7TmgoNwNsXNo42ti+yjwUOH5kPiNL6VizXtBznaqB16nzaeErAMZRKQFWDZJkBE
41ZgpRDUajz9QdwOWke275dhdU/Z/seyHdTtXUmzqWrLZoQT1Vyg3N9udwbRcXXIV2+vD3dbAgMB
AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRUrfrHkleu
yjWcLhL75LpdINyUVzANBgkqhkiG9w0BAQsFAAOCAgEAMJmdBTLIXg47mAE6iqTnB/d6+Oea31BD
U5cqPco8R5gu4RV78ZLzYdqQJRZlwJ9UXQ4DO1t3ApyEtg2YXzTdO2PCwyiBwpwpLiniyMMB8jPq
KqrMCQj3ZWfGzd/TtiunvczRDnBfuCPRy5FOCvTIeuXZYzbB1N/8Ipf3YF3qKS9Ysr1YvY2WTxB1
v0h7PVGHoTx0IsL8B3+A3MSs/mrBcDCw6Y5p4ixpgZQJut3+TcCDjJRYwEYgr5wfAvg1VUkvRtTA
8KCWAg8zxXHzniN9lLf9OtMJgwYh/WA9rjLA0u6NpvDntIJ8CsxwyXmA+P5M9zWEGYox+wrZ13+b
8KKaa8MFSu1BYBQw0aoRQm7TIwIEC8Zl3d1Sd9qBa7Ko+gE4uZbqKmxnl4mUnrzhVNXkanjvSr0r
mj1AfsbAddJu+2gw7OyLnflJNZoaLNmzlTnVHpL3prllL+U9bTpITAjc5CgSKL59NVzq4BZ+Extq
1z7XnvwtdbLBFNUjA9tbbws+eC8N3jONFrdI54OagQ97wUNNVQQXOEpR1VmiiXTTn74eS9fGbbeI
JG9gkaSChVtWQbzQRKtqE77RLFi3EjNYsjdj3BP1lB0/QFH1T/U67cjF68IeHRaVesd+QnGTbksV
tzDfqu1XhUisHWrdOWnk4Xl4vs4Fv6EM94B7IWcnMFk=
-----END CERTIFICATE-----
Staat der Nederlanden EV Root CA
================================
-----BEGIN CERTIFICATE-----
MIIFcDCCA1igAwIBAgIEAJiWjTANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJOTDEeMBwGA1UE
CgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSkwJwYDVQQDDCBTdGFhdCBkZXIgTmVkZXJsYW5kZW4g
RVYgUm9vdCBDQTAeFw0xMDEyMDgxMTE5MjlaFw0yMjEyMDgxMTEwMjhaMFgxCzAJBgNVBAYTAk5M
MR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xKTAnBgNVBAMMIFN0YWF0IGRlciBOZWRl
cmxhbmRlbiBFViBSb290IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA48d+ifkk
SzrSM4M1LGns3Amk41GoJSt5uAg94JG6hIXGhaTK5skuU6TJJB79VWZxXSzFYGgEt9nCUiY4iKTW
O0Cmws0/zZiTs1QUWJZV1VD+hq2kY39ch/aO5ieSZxeSAgMs3NZmdO3dZ//BYY1jTw+bbRcwJu+r
0h8QoPnFfxZpgQNH7R5ojXKhTbImxrpsX23Wr9GxE46prfNeaXUmGD5BKyF/7otdBwadQ8QpCiv8
Kj6GyzyDOvnJDdrFmeK8eEEzduG/L13lpJhQDBXd4Pqcfzho0LKmeqfRMb1+ilgnQ7O6M5HTp5gV
XJrm0w912fxBmJc+qiXbj5IusHsMX/FjqTf5m3VpTCgmJdrV8hJwRVXj33NeN/UhbJCONVrJ0yPr
08C+eKxCKFhmpUZtcALXEPlLVPxdhkqHz3/KRawRWrUgUY0viEeXOcDPusBCAUCZSCELa6fS/ZbV
0b5GnUngC6agIk440ME8MLxwjyx1zNDFjFE7PZQIZCZhfbnDZY8UnCHQqv0XcgOPvZuM5l5Tnrmd
74K74bzickFbIZTTRTeU0d8JOV3nI6qaHcptqAqGhYqCvkIH1vI4gnPah1vlPNOePqc7nvQDs/nx
fRN0Av+7oeX6AHkcpmZBiFxgV6YuCcS6/ZrPpx9Aw7vMWgpVSzs4dlG4Y4uElBbmVvMCAwEAAaNC
MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFP6rAJCYniT8qcwa
ivsnuL8wbqg7MA0GCSqGSIb3DQEBCwUAA4ICAQDPdyxuVr5Os7aEAJSrR8kN0nbHhp8dB9O2tLsI
eK9p0gtJ3jPFrK3CiAJ9Brc1AsFgyb/E6JTe1NOpEyVa/m6irn0F3H3zbPB+po3u2dfOWBfoqSmu
c0iH55vKbimhZF8ZE/euBhD/UcabTVUlT5OZEAFTdfETzsemQUHSv4ilf0X8rLiltTMMgsT7B/Zq
5SWEXwbKwYY5EdtYzXc7LMJMD16a4/CrPmEbUCTCwPTxGfARKbalGAKb12NMcIxHowNDXLldRqAN
b/9Zjr7dn3LDWyvfjFvO5QxGbJKyCqNMVEIYFRIYvdr8unRu/8G2oGTYqV9Vrp9canaW2HNnh/tN
f1zuacpzEPuKqf2evTY4SUmH9A4U8OmHuD+nT3pajnnUk+S7aFKErGzp85hwVXIy+TSrK0m1zSBi
5Dp6Z2Orltxtrpfs/J92VoguZs9btsmksNcFuuEnL5O7Jiqik7Ab846+HUCjuTaPPoIaGl6I6lD4
WeKDRikL40Rc4ZW2aZCaFG+XroHPaO+Zmr615+F/+PoTRxZMzG0IQOeLeG9QgkRQP2YGiqtDhFZK
DyAthg710tvSeopLzaXoTvFeJiUBWSOgftL2fiFX1ye8FVdMpEbB4IMeDExNH08GGeL5qPQ6gqGy
eUN51q1veieQA6TqJIc/2b3Z6fJfUEkc7uzXLg==
-----END CERTIFICATE-----
IdenTrust Commercial Root CA 1
==============================
-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBKMQswCQYDVQQG
EwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBS
b290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQwMTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzES
MBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENB
IDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ld
hNlT3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU+ehcCuz/
mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gpS0l4PJNgiCL8mdo2yMKi
1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1bVoE/c40yiTcdCMbXTMTEl3EASX2MN0C
XZ/g1Ue9tOsbobtJSdifWwLziuQkkORiT0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl
3ZBWzvurpWCdxJ35UrCLvYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzy
NeVJSQjKVsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZKdHzV
WYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHTc+XvvqDtMwt0viAg
xGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hvl7yTmvmcEpB4eoCHFddydJxVdHix
uuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5NiGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMC
AQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZI
hvcNAQELBQADggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH
6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwtLRvM7Kqas6pg
ghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93nAbowacYXVKV7cndJZ5t+qnt
ozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3+wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmV
YjzlVYA211QC//G5Xc7UI2/YRYRKW2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUX
feu+h1sXIFRRk0pTAwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/ro
kTLql1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG4iZZRHUe
2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZmUlO+KWA2yUPHGNiiskz
Z2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7R
cGzM7vRX+Bi6hG6H
-----END CERTIFICATE-----
IdenTrust Public Sector Root CA 1
=================================
-----BEGIN CERTIFICATE-----
MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBNMQswCQYDVQQG
EwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3Rv
ciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcNMzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJV
UzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBS
b290IENBIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTy
P4o7ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGyRBb06tD6
Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlSbdsHyo+1W/CD80/HLaXI
rcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF/YTLNiCBWS2ab21ISGHKTN9T0a9SvESf
qy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoS
mJxZZoY+rfGwyj4GD3vwEUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFn
ol57plzy9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9VGxyh
LrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ2fjXctscvG29ZV/v
iDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsVWaFHVCkugyhfHMKiq3IXAAaOReyL
4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gDW/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8B
Af8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMw
DQYJKoZIhvcNAQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj
t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHVDRDtfULAj+7A
mgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9TaDKQGXSc3z1i9kKlT/YPyNt
GtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8GlwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFt
m6/n6J91eEyrRjuazr8FGF1NFTwWmhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMx
NRF4eKLg6TCMf4DfWN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4
Mhn5+bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJtshquDDI
ajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhAGaQdp/lLQzfcaFpPz+vC
ZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ
3Wl9af0AVqW3rLatt8o+Ae+c
-----END CERTIFICATE-----
Entrust Root Certification Authority - G2
=========================================
-----BEGIN CERTIFICATE-----
MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMCVVMxFjAUBgNV
BAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVy
bXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ug
b25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIw
HhcNMDkwNzA3MTcyNTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoT
DUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMx
OTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25s
eTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP
/vaCeb9zYQYKpSfYs1/TRU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXz
HHfV1IWNcCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hWwcKU
s/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1U1+cPvQXLOZprE4y
TGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0jaWvYkxN4FisZDQSA/i2jZRjJKRx
AgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ6
0B7vfec7aVHUbI2fkBJmqzANBgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5Z
iXMRrEPR9RP/jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ
Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v1fN2D807iDgi
nWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4RnAuknZoh8/CbCzB428Hch0P+
vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmHVHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xO
e4pIb4tF9g==
-----END CERTIFICATE-----
Entrust Root Certification Authority - EC1
==========================================
-----BEGIN CERTIFICATE-----
MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkGA1UEBhMCVVMx
FjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVn
YWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXpl
ZCB1c2Ugb25seTEzMDEGA1UEAxMqRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5
IC0gRUMxMB4XDTEyMTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYw
FAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2Fs
LXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQg
dXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAt
IEVDMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHy
AsWfoPZb1YsGGYZPUxBtByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef
9eNi1KlHBz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE
FLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVCR98crlOZF7ZvHH3h
vxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nXhTcGtXsI/esni0qU+eH6p44mCOh8
kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G
-----END CERTIFICATE-----
CFCA EV ROOT
============
-----BEGIN CERTIFICATE-----
MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJDTjEwMC4GA1UE
CgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNB
IEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkxMjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEw
MC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQD
DAxDRkNBIEVWIFJPT1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnV
BU03sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpLTIpTUnrD
7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5/ZOkVIBMUtRSqy5J35DN
uF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp7hZZLDRJGqgG16iI0gNyejLi6mhNbiyW
ZXvKWfry4t3uMCz7zEasxGPrb382KzRzEpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7
xzbh72fROdOXW3NiGUgthxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9f
py25IGvPa931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqotaK8K
gWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNgTnYGmE69g60dWIol
hdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfVPKPtl8MeNPo4+QgO48BdK4PRVmrJ
tqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hvcWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAf
BgNVHSMEGDAWgBTj/i39KNALtbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB
/wQEAwIBBjAdBgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB
ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObTej/tUxPQ4i9q
ecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdLjOztUmCypAbqTuv0axn96/Ua
4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBSESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sG
E5uPhnEFtC+NiWYzKXZUmhH4J/qyP5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfX
BDrDMlI1Dlb4pd19xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjn
aH9dCi77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN5mydLIhy
PDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe/v5WOaHIz16eGWRGENoX
kbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+ZAAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3C
ekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su
-----END CERTIFICATE-----
OISTE WISeKey Global Root GB CA
===============================
-----BEGIN CERTIFICATE-----
MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBtMQswCQYDVQQG
EwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl
ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAw
MzJaFw0zOTEyMDExNTEwMzFaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYD
VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEds
b2JhbCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3HEokKtaX
scriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGxWuR51jIjK+FTzJlFXHtP
rby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk
9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNku7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4o
Qnc/nSMbsrY9gBQHTC5P99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvg
GUpuuy9rM2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB
/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZI
hvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrghcViXfa43FK8+5/ea4n32cZiZBKpD
dHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0
VQreUGdNZtGn//3ZwLWoo4rOZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEui
HZeeevJuQHHfaPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic
Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM=
-----END CERTIFICATE-----
SZAFIR ROOT CA2
===============
-----BEGIN CERTIFICATE-----
MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQELBQAwUTELMAkG
A1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6ZW5pb3dhIFMuQS4xGDAWBgNV
BAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkwNzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJ
BgNVBAYTAlBMMSgwJgYDVQQKDB9LcmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYD
VQQDDA9TWkFGSVIgUk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5Q
qEvNQLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT3PSQ1hNK
DJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw3gAeqDRHu5rr/gsUvTaE
2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr63fE9biCloBK0TXC5ztdyO4mTp4CEHCdJ
ckm1/zuVnsHMyAHs6A6KCpbns6aH5db5BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwi
ieDhZNRnvDF5YTy7ykHNXGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P
AQH/BAQDAgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsFAAOC
AQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw8PRBEew/R40/cof5
O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOGnXkZ7/e7DDWQw4rtTw/1zBLZpD67
oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCPoky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul
4+vJhaAlIDf7js4MNIThPIGyd05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6
+/NNIxuZMzSgLvWpCz/UXeHPhJ/iGcJfitYgHuNztw==
-----END CERTIFICATE-----
Certum Trusted Network CA 2
===========================
-----BEGIN CERTIFICATE-----
MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCBgDELMAkGA1UE
BhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAlBgNVBAsTHkNlcnR1
bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29y
ayBDQSAyMCIYDzIwMTExMDA2MDgzOTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQ
TDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENl
cnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENB
IDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWADGSdhhuWZGc/IjoedQF9
7/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+o
CgCXhVqqndwpyeI1B+twTUrWwbNWuKFBOJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40b
Rr5HMNUuctHFY9rnY3lEfktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2p
uTRZCr+ESv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1mo130
GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02isx7QBlrd9pPPV3WZ
9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOWOZV7bIBaTxNyxtd9KXpEulKkKtVB
Rgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgezTv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pye
hizKV/Ma5ciSixqClnrDvFASadgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vM
BhBgu4M1t15n3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD
AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZI
hvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQF/xlhMcQSZDe28cmk4gmb3DW
Al45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTfCVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuA
L55MYIR4PSFk1vtBHxgP58l1cb29XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMo
clm2q8KMZiYcdywmdjWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tM
pkT/WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jbAoJnwTnb
w3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksqP/ujmv5zMnHCnsZy4Ypo
J/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Kob7a6bINDd82Kkhehnlt4Fj1F4jNy3eFm
ypnTycUm/Q1oBEauttmbjL4ZvrHG8hnjXALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLX
is7VmFxWlgPF7ncGNf/P5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7
zAYspsbiDrW5viSP
-----END CERTIFICATE-----
Hellenic Academic and Research Institutions RootCA 2015
=======================================================
-----BEGIN CERTIFICATE-----
MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcT
BkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0
aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl
YXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAx
MTIxWjCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMg
QWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNV
BAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIw
MTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDC+Kk/G4n8PDwEXT2QNrCROnk8Zlrv
bTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+eh
iGsxr/CL0BgzuNtFajT0AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+
6PAQZe104S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06CojXd
FPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV9Cz82XBST3i4vTwr
i5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrDgfgXy5I2XdGj2HUb4Ysn6npIQf1F
GQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2
fu/Z8VFRfS0myGlZYeCsargqNhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9mu
iNX6hME6wGkoLfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc
Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD
AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVdctA4GGqd83EkVAswDQYJKoZI
hvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0IXtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+
D1hYc2Ryx+hFjtyp8iY/xnmMsVMIM4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrM
d/K4kPFox/la/vot9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+y
d+2VZ5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/eaj8GsGsVn
82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnhX9izjFk0WaSrT2y7Hxjb
davYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQl033DlZdwJVqwjbDG2jJ9SrcR5q+ss7F
Jej6A7na+RZukYT1HCjI/CbM1xyQVqdfbzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVt
J94Cj8rDtSvK6evIIVM4pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGa
JI7ZjnHKe7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0vm9q
p/UsQu0yrbYhnr68
-----END CERTIFICATE-----
Hellenic Academic and Research Institutions ECC RootCA 2015
===========================================================
-----BEGIN CERTIFICATE-----
MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0
aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u
cyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj
aCBJbnN0aXR1dGlvbnMgRUNDIFJvb3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEw
MzcxMlowgaoxCzAJBgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmlj
IEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUQwQgYD
VQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIEVDQyBSb290
Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKgQehLgoRc4vgxEZmGZE4JJS+dQS8KrjVP
dJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJajq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoK
Vlp8aQuqgAkkbH7BRqNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O
BBYEFLQiC4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaeplSTA
GiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7SofTUwJCA3sS61kFyjn
dc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR
-----END CERTIFICATE-----
ISRG Root X1
============
-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAwTzELMAkGA1UE
BhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNoIEdyb3VwMRUwEwYDVQQD
EwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQG
EwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMT
DElTUkcgUm9vdCBYMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54r
Vygch77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+0TM8ukj1
3Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6UA5/TR5d8mUgjU+g4rk8K
b4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sWT8KOEUt+zwvo/7V3LvSye0rgTBIlDHCN
Aymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyHB5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ
4Q7e2RCOFvu396j3x+UCB5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf
1b0SHzUvKBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWnOlFu
hjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTnjh8BCNAw1FtxNrQH
usEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbwqHyGO0aoSCqI3Haadr8faqU9GY/r
OPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CIrU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4G
A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY
9umbbjANBgkqhkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ3BebYhtF8GaV
0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KKNFtY2PwByVS5uCbMiogziUwt
hDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJw
TdwJx4nLCgdNbOhdjsnvzqvHu7UrTkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nx
e5AW0wdeRlN8NwdCjNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZA
JzVcoyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq4RgqsahD
YVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPAmRGunUHBcnWEvgJBQl9n
JEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57demyPxgcYxn/eR44/KJ4EBs+lVDR3veyJ
m+kXQ99b21/+jh5Xos1AnX5iItreGCc=
-----END CERTIFICATE-----
AC RAIZ FNMT-RCM
================
-----BEGIN CERTIFICATE-----
MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsxCzAJBgNVBAYT
AkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTAeFw0wODEw
MjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJD
TTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
ggIBALpxgHpMhm5/yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcf
qQgfBBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAzWHFctPVr
btQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxFtBDXaEAUwED653cXeuYL
j2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z374jNUUeAlz+taibmSXaXvMiwzn15Cou
08YfxGyqxRxqAQVKL9LFwag0Jl1mpdICIfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mw
WsXmo8RZZUc1g16p6DULmbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnT
tOmlcYF7wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peSMKGJ
47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2ZSysV4999AeU14EC
ll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMetUqIJ5G+GR4of6ygnXYMgrwTJbFaa
i0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE
FPd9xf3E6Jobd2Sn9R2gzL+HYJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1o
dHRwOi8vd3d3LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD
nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1RXxlDPiyN8+s
D8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYMLVN0V2Ue1bLdI4E7pWYjJ2cJ
j+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrT
Qfv6MooqtyuGC2mDOL7Nii4LcK2NJpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW
+YJF1DngoABd15jmfZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7
Ixjp6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp1txyM/1d
8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B9kiABdcPUXmsEKvU7ANm
5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wokRqEIr9baRRmW1FMdW4R58MD3R++Lj8UG
rp1MYp3/RgT408m2ECVAdf4WqslKYIYvuu8wd+RU4riEmViAqhOLUTpPSPaLtrM=
-----END CERTIFICATE-----
Amazon Root CA 1
================
-----BEGIN CERTIFICATE-----
MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsFADA5MQswCQYD
VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAxMB4XDTE1
MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv
bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBALJ4gHHKeNXjca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgH
FzZM9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qwIFAGbHrQ
gLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6VOujw5H5SNz/0egwLX0t
dHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L93FcXmn/6pUCyziKrlA4b9v7LWIbxcce
VOF34GfID5yHI9Y/QCB/IIDEgEw+OyQmjgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB
/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3
DQEBCwUAA4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDIU5PM
CCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUsN+gDS63pYaACbvXy
8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vvo/ufQJVtMVT8QtPHRh8jrdkPSHCa
2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2
xJNDd2ZhwLnoQdeXeGADbkpyrqXRfboQnoZsG4q5WTP468SQvvG5
-----END CERTIFICATE-----
Amazon Root CA 2
================
-----BEGIN CERTIFICATE-----
MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwFADA5MQswCQYD
VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAyMB4XDTE1
MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv
bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
ggIBAK2Wny2cSkxKgXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4
kHbZW0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg1dKmSYXp
N+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K8nu+NQWpEjTj82R0Yiw9
AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvd
fLC6HM783k81ds8P+HgfajZRRidhW+mez/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAEx
kv8LV/SasrlX6avvDXbR8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSS
btqDT6ZjmUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz7Mt0
Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6+XUyo05f7O0oYtlN
c/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI0u1ufm8/0i2BWSlmy5A5lREedCf+
3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSw
DPBMMPQFWAJI/TPlUq9LhONmUjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oA
A7CXDpO8Wqj2LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY
+gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kSk5Nrp+gvU5LE
YFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl7uxMMne0nxrpS10gxdr9HIcW
xkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygmbtmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQ
gj9sAq+uEjonljYE1x2igGOpm/HlurR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbW
aQbLU8uz/mtBzUF+fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoV
Yh63n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE76KlXIx3
KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H9jVlpNMKVv/1F2Rs76gi
JUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT4PsJYGw=
-----END CERTIFICATE-----
Amazon Root CA 3
================
-----BEGIN CERTIFICATE-----
MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5MQswCQYDVQQG
EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAzMB4XDTE1MDUy
NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ
MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZB
f8ANm+gBG1bG8lKlui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjr
Zt6jQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSrttvXBp43
rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkrBqWTrBqYaGFy+uGh0Psc
eGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteMYyRIHN8wfdVoOw==
-----END CERTIFICATE-----
Amazon Root CA 4
================
-----BEGIN CERTIFICATE-----
MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5MQswCQYDVQQG
EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSA0MB4XDTE1MDUy
NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ
MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN
/sGKe0uoe0ZLY7Bi9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri
83BkM6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV
HQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WBMAoGCCqGSM49BAMDA2gA
MGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlwCkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1
AE47xDqUEpHJWEadIRNyp4iciuRMStuW1KyLa2tJElMzrdfkviT8tQp21KW8EA==
-----END CERTIFICATE-----
Symantec Class 1 Public Primary Certification Authority - G6
============================================================
-----BEGIN CERTIFICATE-----
MIID9jCCAt6gAwIBAgIQJDJ18h0v0gkz97RqytDzmDANBgkqhkiG9w0BAQsFADCBlDELMAkGA1UE
BhMCVVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMR8wHQYDVQQLExZTeW1hbnRlYyBU
cnVzdCBOZXR3b3JrMUUwQwYDVQQDEzxTeW1hbnRlYyBDbGFzcyAxIFB1YmxpYyBQcmltYXJ5IENl
cnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzYwHhcNMTExMDE4MDAwMDAwWhcNMzcxMjAxMjM1OTU5
WjCBlDELMAkGA1UEBhMCVVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMR8wHQYDVQQL
ExZTeW1hbnRlYyBUcnVzdCBOZXR3b3JrMUUwQwYDVQQDEzxTeW1hbnRlYyBDbGFzcyAxIFB1Ymxp
YyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzYwggEiMA0GCSqGSIb3DQEBAQUA
A4IBDwAwggEKAoIBAQDHOddJZKmZgiJM6kXZBxbje/SD6Jlz+muxNuCad6BAwoGNAcfMjL2Pffd5
43pMA03Z+/2HOCgs3ZqLVAjbZ/sbjP4oki++t7JIp4Gh2F6Iw8w5QEFa0dzl2hCfL9oBTf0uRnz5
LicKaTfukaMbasxEvxvHw9QRslBglwm9LiL1QYRmn81ApqkAgMEflZKf3vNI79sdd2H8f9/ulqRy
0LY+/3gnr8uSFWkI22MQ4uaXrG7crPaizh5HmbmJtxLmodTNWRFnw2+F2EJOKL5ZVVkElauPN4C/
DfD8HzpkMViBeNfiNfYgPym4jxZuPkjctUwH4fIa6n4KedaovetdhitNAgMBAAGjQjBAMA4GA1Ud
DwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQzQejIORIVk0jyljIuWvXalF9T
YDANBgkqhkiG9w0BAQsFAAOCAQEAFeNzV7EXtl9JaUSm9l56Z6zS3nVJq/4lVcc6yUQVEG6/MWvL
2QeTfxyFYwDjMhLgzMv7OWyP4lPiPEAz2aSMR+atWPuJr+PehilWNCxFuBL6RIluLRQlKCQBZdbq
UqwFblYSCT3QdPTXvQbKqDqNVkL6jXI+dPEDct+HG14OelWWLDi3mIXNTTNEyZSPWjEwN0ujOhKz
5zbRIWhLLTjmU64cJVYIVgNnhJ3Gw84kYsdMNs+wBkS39V8C3dlU6S+QTnrIToNADJqXPDe/v+z2
8LSFdyjBC8hnghAXOKK3Buqbvzr46SMHv3TgmDgVVXjucgBcGaP00jPg/73RVDkpDw==
-----END CERTIFICATE-----
Symantec Class 2 Public Primary Certification Authority - G6
============================================================
-----BEGIN CERTIFICATE-----
MIID9jCCAt6gAwIBAgIQZIKe/DcedF38l/+XyLH/QTANBgkqhkiG9w0BAQsFADCBlDELMAkGA1UE
BhMCVVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMR8wHQYDVQQLExZTeW1hbnRlYyBU
cnVzdCBOZXR3b3JrMUUwQwYDVQQDEzxTeW1hbnRlYyBDbGFzcyAyIFB1YmxpYyBQcmltYXJ5IENl
cnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzYwHhcNMTExMDE4MDAwMDAwWhcNMzcxMjAxMjM1OTU5
WjCBlDELMAkGA1UEBhMCVVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMR8wHQYDVQQL
ExZTeW1hbnRlYyBUcnVzdCBOZXR3b3JrMUUwQwYDVQQDEzxTeW1hbnRlYyBDbGFzcyAyIFB1Ymxp
YyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzYwggEiMA0GCSqGSIb3DQEBAQUA
A4IBDwAwggEKAoIBAQDNzOkFyGOFyz9AYxe9GPo15gRnV2WYKaRPyVyPDzTS+NqoE2KquB5QZ3iw
FkygOakVeq7t0qLA8JA3KRgmXOgNPLZsST/B4NzZS7YUGQum05bh1gnjGSYc+R9lS/kaQxwAg9bQ
qkmi1NvmYji6UBRDbfkx+FYW2TgCkc/rbN27OU6Z4TBnRfHU8I3D3/7yOAchfQBeVkSz5GC9kSuc
q1sEcg+yKNlyqwUgQiWpWwNqIBDMMfAr2jUs0Pual07wgksr2F82owstr2MNHSV/oW5cYqGNKD6h
/Bwg+AEvulWaEbAZ0shQeWsOagXXqgQ2sqPy4V93p3ec5R7c6d9qwWVdAgMBAAGjQjBAMA4GA1Ud
DwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSHjCCVyJhK0daABkqQNETfHE2/
sDANBgkqhkiG9w0BAQsFAAOCAQEAgY6ypWaWtyGltu9vI1pf24HFQqV4wWn99DzX+VxrcHIa/FqX
TQCAiIiCisNxDY7FiZss7Y0L0nJU9X3UXENX6fOupQIR9nYrgVfdfdp0MP1UR/bgFm6mtApI5ud1
Bw8pGTnOefS2bMVfmdUfS/rfbSw8DVSAcPCIC4DPxmiiuB1w2XaM/O6lyc+tHc+ZJVdaYkXLFmu9
Sc2lo4xpeSWuuExsi0BmSxY/zwIa3eFsawdhanYVKZl/G92IgMG/tY9zxaaWI4SmKIYkM2oBLldz
JbZev4/mHWGoQClnHYebHX+bn5nNMdZUvmK7OaxoEkiRIKXLsd3+b/xa5IJVWa8xqQ==
-----END CERTIFICATE-----
D-TRUST Root CA 3 2013
======================
-----BEGIN CERTIFICATE-----
MIIEDjCCAvagAwIBAgIDD92sMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNVBAYTAkRFMRUwEwYDVQQK
DAxELVRydXN0IEdtYkgxHzAdBgNVBAMMFkQtVFJVU1QgUm9vdCBDQSAzIDIwMTMwHhcNMTMwOTIw
MDgyNTUxWhcNMjgwOTIwMDgyNTUxWjBFMQswCQYDVQQGEwJERTEVMBMGA1UECgwMRC1UcnVzdCBH
bWJIMR8wHQYDVQQDDBZELVRSVVNUIFJvb3QgQ0EgMyAyMDEzMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAxHtCkoIf7O1UmI4SwMoJ35NuOpNcG+QQd55OaYhs9uFp8vabomGxvQcgdJhl
8YwmCM2oNcqANtFjbehEeoLDbF7eu+g20sRoNoyfMr2EIuDcwu4QRjltr5M5rofmw7wJySxrZ1vZ
m3Z1TAvgu8XXvD558l++0ZBX+a72Zl8xv9Ntj6e6SvMjZbu376Ml1wrqWLbviPr6ebJSWNXwrIyh
UXQplapRO5AyA58ccnSQ3j3tYdLl4/1kR+W5t0qp9x+uloYErC/jpIF3t1oW/9gPP/a3eMykr/pb
PBJbqFKJcu+I89VEgYaVI5973bzZNO98lDyqwEHC451QGsDkGSL8swIDAQABo4IBBTCCAQEwDwYD
VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUP5DIfccVb/Mkj6nDL0uiDyGyL+cwDgYDVR0PAQH/BAQD
AgEGMIG+BgNVHR8EgbYwgbMwdKByoHCGbmxkYXA6Ly9kaXJlY3RvcnkuZC10cnVzdC5uZXQvQ049
RC1UUlVTVCUyMFJvb3QlMjBDQSUyMDMlMjAyMDEzLE89RC1UcnVzdCUyMEdtYkgsQz1ERT9jZXJ0
aWZpY2F0ZXJldm9jYXRpb25saXN0MDugOaA3hjVodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9k
LXRydXN0X3Jvb3RfY2FfM18yMDEzLmNybDANBgkqhkiG9w0BAQsFAAOCAQEADlkOWOR0SCNEzzQh
tZwUGq2aS7eziG1cqRdw8CqfjXv5e4X6xznoEAiwNStfzwLS05zICx7uBVSuN5MECX1sj8J0vPgc
lL4xAUAt8yQgt4RVLFzI9XRKEBmLo8ftNdYJSNMOwLo5qLBGArDbxohZwr78e7Erz35ih1WWzAFv
m2chlTWL+BD8cRu3SzdppjvW7IvuwbDzJcmPkn2h6sPKRL8mpXSSnON065102ctNh9j8tGlsi6BD
B2B4l+nZk3zCRrybN1Kj7Yo8E6l7U0tJmhEFLAtuVqwfLoJs4GlntQ5tLdnkwBXxP/oYcuEVbSdb
LTAoK59ImmQrme/ydUlfXA==
-----END CERTIFICATE-----
TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1
=============================================
-----BEGIN CERTIFICATE-----
MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIxGDAWBgNVBAcT
D0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxpbXNlbCB2ZSBUZWtub2xvamlr
IEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0wKwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24g
TWVya2V6aSAtIEthbXUgU00xNjA0BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRp
ZmlrYXNpIC0gU3VydW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYD
VQQGEwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXllIEJpbGlt
c2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklUQUsxLTArBgNVBAsTJEth
bXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBTTTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11
IFNNIFNTTCBLb2sgU2VydGlmaWthc2kgLSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAr3UwM6q7a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y8
6Ij5iySrLqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INrN3wc
wv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2XYacQuFWQfw4tJzh0
3+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/iSIzL+aFCr2lqBs23tPcLG07xxO9
WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4fAJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQU
ZT/HiobGPN08VFw1+DrtUgxHV8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJ
KoZIhvcNAQELBQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh
AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPfIPP54+M638yc
lNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4lzwDGrpDxpa5RXI4s6ehlj2R
e37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0j
q5Rm+K37DwhuJi1/FwcJsoz7UMCflo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM=
-----END CERTIFICATE-----
GDCA TrustAUTH R5 ROOT
======================
-----BEGIN CERTIFICATE-----
MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UEBhMCQ04xMjAw
BgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZIENPLixMVEQuMR8wHQYDVQQD
DBZHRENBIFRydXN0QVVUSCBSNSBST09UMB4XDTE0MTEyNjA1MTMxNVoXDTQwMTIzMTE1NTk1OVow
YjELMAkGA1UEBhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ
IENPLixMVEQuMR8wHQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMIICIjANBgkqhkiG9w0B
AQEFAAOCAg8AMIICCgKCAgEA2aMW8Mh0dHeb7zMNOwZ+Vfy1YI92hhJCfVZmPoiC7XJjDp6L3TQs
AlFRwxn9WVSEyfFrs0yw6ehGXTjGoqcuEVe6ghWinI9tsJlKCvLriXBjTnnEt1u9ol2x8kECK62p
OqPseQrsXzrj/e+APK00mxqriCZ7VqKChh/rNYmDf1+uKU49tm7srsHwJ5uu4/Ts765/94Y9cnrr
pftZTqfrlYwiOXnhLQiPzLyRuEH3FMEjqcOtmkVEs7LXLM3GKeJQEK5cy4KOFxg2fZfmiJqwTTQJ
9Cy5WmYqsBebnh52nUpmMUHfP/vFBu8btn4aRjb3ZGM74zkYI+dndRTVdVeSN72+ahsmUPI2JgaQ
xXABZG12ZuGR224HwGGALrIuL4xwp9E7PLOR5G62xDtw8mySlwnNR30YwPO7ng/Wi64HtloPzgsM
R6flPri9fcebNaBhlzpBdRfMK5Z3KpIhHtmVdiBnaM8Nvd/WHwlqmuLMc3GkL30SgLdTMEZeS1SZ
D2fJpcjyIMGC7J0R38IC+xo70e0gmu9lZJIQDSri3nDxGGeCjGHeuLzRL5z7D9Ar7Rt2ueQ5Vfj4
oR24qoAATILnsn8JuLwwoC8N9VKejveSswoAHQBUlwbgsQfZxw9cZX08bVlX5O2ljelAU58VS6Bx
9hoh49pwBiFYFIeFd3mqgnkCAwEAAaNCMEAwHQYDVR0OBBYEFOLJQJ9NzuiaoXzPDj9lxSmIahlR
MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQDRSVfg
p8xoWLoBDysZzY2wYUWsEe1jUGn4H3++Fo/9nesLqjJHdtJnJO29fDMylyrHBYZmDRd9FBUb1Ov9
H5r2XpdptxolpAqzkT9fNqyL7FeoPueBihhXOYV0GkLH6VsTX4/5COmSdI31R9KrO9b7eGZONn35
6ZLpBN79SWP8bfsUcZNnL0dKt7n/HipzcEYwv1ryL3ml4Y0M2fmyYzeMN2WFcGpcWwlyua1jPLHd
+PwyvzeG5LuOmCd+uh8W4XAR8gPfJWIyJyYYMoSf/wA6E7qaTfRPuBRwIrHKK5DOKcFw9C+df/KQ
HtZa37dG/OaG+svgIHZ6uqbL9XzeYqWxi+7egmaKTjowHz+Ay60nugxe19CxVsp3cbK1daFQqUBD
F8Io2c9Si1vIY9RCPqAzekYu9wogRlR+ak8x8YF+QnQ4ZXMn7sZ8uI7XpTrXmKGcjBBV09tL7ECQ
8s1uV9JiDnxXk7Gnbc2dg7sq5+W2O3FYrf3RRbxake5TFW/TRQl1brqQXR4EzzffHqhmsYzmIGrv
/EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrqT8p+ck0LcIymSLumoRT2+1hEmRSuqguT
aaApJUqlyyvdimYHFngVV3Eb7PVHhPOeMTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g==
-----END CERTIFICATE-----
TrustCor RootCert CA-1
======================
-----BEGIN CERTIFICATE-----
MIIEMDCCAxigAwIBAgIJANqb7HHzA7AZMA0GCSqGSIb3DQEBCwUAMIGkMQswCQYDVQQGEwJQQTEP
MA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEkMCIGA1UECgwbVHJ1c3RDb3Ig
U3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3Jp
dHkxHzAdBgNVBAMMFlRydXN0Q29yIFJvb3RDZXJ0IENBLTEwHhcNMTYwMjA0MTIzMjE2WhcNMjkx
MjMxMTcyMzE2WjCBpDELMAkGA1UEBhMCUEExDzANBgNVBAgMBlBhbmFtYTEUMBIGA1UEBwwLUGFu
YW1hIENpdHkxJDAiBgNVBAoMG1RydXN0Q29yIFN5c3RlbXMgUy4gZGUgUi5MLjEnMCUGA1UECwwe
VHJ1c3RDb3IgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MR8wHQYDVQQDDBZUcnVzdENvciBSb290Q2Vy
dCBDQS0xMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv463leLCJhJrMxnHQFgKq1mq
jQCj/IDHUHuO1CAmujIS2CNUSSUQIpidRtLByZ5OGy4sDjjzGiVoHKZaBeYei0i/mJZ0PmnK6bV4
pQa81QBeCQryJ3pS/C3Vseq0iWEk8xoT26nPUu0MJLq5nux+AHT6k61sKZKuUbS701e/s/OojZz0
JEsq1pme9J7+wH5COucLlVPat2gOkEz7cD+PSiyU8ybdY2mplNgQTsVHCJCZGxdNuWxu72CVEY4h
gLW9oHPY0LJ3xEXqWib7ZnZ2+AYfYW0PVcWDtxBWcgYHpfOxGgMFZA6dWorWhnAbJN7+KIor0Gqw
/Hqi3LJ5DotlDwIDAQABo2MwYTAdBgNVHQ4EFgQU7mtJPHo/DeOxCbeKyKsZn3MzUOcwHwYDVR0j
BBgwFoAU7mtJPHo/DeOxCbeKyKsZn3MzUOcwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
AYYwDQYJKoZIhvcNAQELBQADggEBACUY1JGPE+6PHh0RU9otRCkZoB5rMZ5NDp6tPVxBb5UrJKF5
mDo4Nvu7Zp5I/5CQ7z3UuJu0h3U/IJvOcs+hVcFNZKIZBqEHMwwLKeXx6quj7LUKdJDHfXLy11yf
ke+Ri7fc7Waiz45mO7yfOgLgJ90WmMCV1Aqk5IGadZQ1nJBfiDcGrVmVCrDRZ9MZyonnMlo2HD6C
qFqTvsbQZJG2z9m2GM/bftJlo6bEjhcxwft+dtvTheNYsnd6djtsL1Ac59v2Z3kf9YKVmgenFK+P
3CghZwnS1k1aHBkcjndcw5QkPTJrS37UeJSDvjdNzl/HHk484IkzlQsPpTLWPFp5LBk=
-----END CERTIFICATE-----
TrustCor RootCert CA-2
======================
-----BEGIN CERTIFICATE-----
MIIGLzCCBBegAwIBAgIIJaHfyjPLWQIwDQYJKoZIhvcNAQELBQAwgaQxCzAJBgNVBAYTAlBBMQ8w
DQYDVQQIDAZQYW5hbWExFDASBgNVBAcMC1BhbmFtYSBDaXR5MSQwIgYDVQQKDBtUcnVzdENvciBT
eXN0ZW1zIFMuIGRlIFIuTC4xJzAlBgNVBAsMHlRydXN0Q29yIENlcnRpZmljYXRlIEF1dGhvcml0
eTEfMB0GA1UEAwwWVHJ1c3RDb3IgUm9vdENlcnQgQ0EtMjAeFw0xNjAyMDQxMjMyMjNaFw0zNDEy
MzExNzI2MzlaMIGkMQswCQYDVQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5h
bWEgQ2l0eTEkMCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5U
cnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxHzAdBgNVBAMMFlRydXN0Q29yIFJvb3RDZXJ0
IENBLTIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCnIG7CKqJiJJWQdsg4foDSq8Gb
ZQWU9MEKENUCrO2fk8eHyLAnK0IMPQo+QVqedd2NyuCb7GgypGmSaIwLgQ5WoD4a3SwlFIIvl9Nk
RvRUqdw6VC0xK5mC8tkq1+9xALgxpL56JAfDQiDyitSSBBtlVkxs1Pu2YVpHI7TYabS3OtB0PAx1
oYxOdqHp2yqlO/rOsP9+aij9JxzIsekp8VduZLTQwRVtDr4uDkbIXvRR/u8OYzo7cbrPb1nKDOOb
XUm4TOJXsZiKQlecdu/vvdFoqNL0Cbt3Nb4lggjEFixEIFapRBF37120Hapeaz6LMvYHL1cEksr1
/p3C6eizjkxLAjHZ5DxIgif3GIJ2SDpxsROhOdUuxTTCHWKF3wP+TfSvPd9cW436cOGlfifHhi5q
jxLGhF5DUVCcGZt45vz27Ud+ez1m7xMTiF88oWP7+ayHNZ/zgp6kPwqcMWmLmaSISo5uZk3vFsQP
eSghYA2FFn3XVDjxklb9tTNMg9zXEJ9L/cb4Qr26fHMC4P99zVvh1Kxhe1fVSntb1IVYJ12/+Ctg
rKAmrhQhJ8Z3mjOAPF5GP/fDsaOGM8boXg25NSyqRsGFAnWAoOsk+xWq5Gd/bnc/9ASKL3x74xdh
8N0JqSDIvgmk0H5Ew7IwSjiqqewYmgeCK9u4nBit2uBGF6zPXQIDAQABo2MwYTAdBgNVHQ4EFgQU
2f4hQG6UnrybPZx9mCAZ5YwwYrIwHwYDVR0jBBgwFoAU2f4hQG6UnrybPZx9mCAZ5YwwYrIwDwYD
VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggIBAJ5Fngw7tu/h
Osh80QA9z+LqBrWyOrsGS2h60COXdKcs8AjYeVrXWoSK2BKaG9l9XE1wxaX5q+WjiYndAfrs3fnp
kpfbsEZC89NiqpX+MWcUaViQCqoL7jcjx1BRtPV+nuN79+TMQjItSQzL/0kMmx40/W5ulop5A7Zv
2wnL/V9lFDfhOPXzYRZY5LVtDQsEGz9QLX+zx3oaFoBg+Iof6Rsqxvm6ARppv9JYx1RXCI/hOWB3
S6xZhBqI8d3LT3jX5+EzLfzuQfogsL7L9ziUwOHQhQ+77Sxzq+3+knYaZH9bDTMJBzN7Bj8RpFxw
PIXAz+OQqIN3+tvmxYxoZxBnpVIt8MSZj3+/0WvitUfW2dCFmU2Umw9Lje4AWkcdEQOsQRivh7dv
DDqPys/cA8GiCcjl/YBeyGBCARsaU1q7N6a3vLqE6R5sGtRk2tRD/pOLS/IseRYQ1JMLiI+h2IYU
RpFHmygk71dSTlxCnKr3Sewn6EAes6aJInKc9Q0ztFijMDvd1GpUk74aTfOTlPf8hAs/hCBcNANE
xdqtvArBAs8e5ZTZ845b2EzwnexhF7sUMlQMAimTHpKG9n/v55IFDlndmQguLvqcAFLTxWYp5KeX
RKQOKIETNcX2b2TmQcTVL8w0RSXPQQCWPUouwpaYT05KnJe32x+SMsj/D1Fu1uwJ
-----END CERTIFICATE-----
TrustCor ECA-1
==============
-----BEGIN CERTIFICATE-----
MIIEIDCCAwigAwIBAgIJAISCLF8cYtBAMA0GCSqGSIb3DQEBCwUAMIGcMQswCQYDVQQGEwJQQTEP
MA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEkMCIGA1UECgwbVHJ1c3RDb3Ig
U3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3Jp
dHkxFzAVBgNVBAMMDlRydXN0Q29yIEVDQS0xMB4XDTE2MDIwNDEyMzIzM1oXDTI5MTIzMTE3Mjgw
N1owgZwxCzAJBgNVBAYTAlBBMQ8wDQYDVQQIDAZQYW5hbWExFDASBgNVBAcMC1BhbmFtYSBDaXR5
MSQwIgYDVQQKDBtUcnVzdENvciBTeXN0ZW1zIFMuIGRlIFIuTC4xJzAlBgNVBAsMHlRydXN0Q29y
IENlcnRpZmljYXRlIEF1dGhvcml0eTEXMBUGA1UEAwwOVHJ1c3RDb3IgRUNBLTEwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPj+ARtZ+odnbb3w9U73NjKYKtR8aja+3+XzP4Q1HpGjOR
MRegdMTUpwHmspI+ap3tDvl0mEDTPwOABoJA6LHip1GnHYMma6ve+heRK9jGrB6xnhkB1Zem6g23
xFUfJ3zSCNV2HykVh0A53ThFEXXQmqc04L/NyFIduUd+Dbi7xgz2c1cWWn5DkR9VOsZtRASqnKmc
p0yJF4OuowReUoCLHhIlERnXDH19MURB6tuvsBzvgdAsxZohmz3tQjtQJvLsznFhBmIhVE5/wZ0+
fyCMgMsq2JdiyIMzkX2woloPV+g7zPIlstR8L+xNxqE6FXrntl019fZISjZFZtS6mFjBAgMBAAGj
YzBhMB0GA1UdDgQWBBREnkj1zG1I1KBLf/5ZJC+Dl5mahjAfBgNVHSMEGDAWgBREnkj1zG1I1KBL
f/5ZJC+Dl5mahjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsF
AAOCAQEABT41XBVwm8nHc2FvcivUwo/yQ10CzsSUuZQRg2dd4mdsdXa/uwyqNsatR5Nj3B5+1t4u
/ukZMjgDfxT2AHMsWbEhBuH7rBiVDKP/mZb3Kyeb1STMHd3BOuCYRLDE5D53sXOpZCz2HAF8P11F
hcCF5yWPldwX8zyfGm6wyuMdKulMY/okYWLW2n62HGz1Ah3UKt1VkOsqEUc8Ll50soIipX1TH0Xs
J5F95yIW6MBoNtjG8U+ARDL54dHRHareqKucBK+tIA5kmE2la8BIWJZpTdwHjFGTot+fDz2LYLSC
jaoITmJF4PkL0uDgPFveXHEnJcLmA4GLEFPjx1WitJ/X5g==
-----END CERTIFICATE-----
SSL.com Root Certification Authority RSA
========================================
-----BEGIN CERTIFICATE-----
MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxDjAM
BgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24x
MTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYw
MjEyMTczOTM5WhcNNDEwMjEyMTczOTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx
EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NM
LmNvbSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcNAQEBBQAD
ggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2RxFdHaxh3a3by/ZPkPQ/C
Fp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aXqhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8
P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcCC52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/ge
oeOy3ZExqysdBP+lSgQ36YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkp
k8zruFvh/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrFYD3Z
fBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93EJNyAKoFBbZQ+yODJ
gUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVcUS4cK38acijnALXRdMbX5J+tB5O2
UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi8
1xtZPCvM8hnIk2snYxnP/Okm+Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4s
bE6x/c+cCbqiM+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV
HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4GA1UdDwEB/wQE
AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGVcpNxJK1ok1iOMq8bs3AD/CUr
dIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBcHadm47GUBwwyOabqG7B52B2ccETjit3E+ZUf
ijhDPwGFpUenPUayvOUiaPd7nNgsPgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAsl
u1OJD7OAUN5F7kR/q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjq
erQ0cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jra6x+3uxj
MxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90IH37hVZkLId6Tngr75qNJ
vTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/YK9f1JmzJBjSWFupwWRoyeXkLtoh/D1JI
Pb9s2KJELtFOt3JY04kTlf5Eq/jXixtunLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406y
wKBjYZC6VWg3dGq2ktufoYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NI
WuuA8ShYIc2wBlX7Jz9TkHCpBB5XJ7k=
-----END CERTIFICATE-----
SSL.com Root Certification Authority ECC
========================================
-----BEGIN CERTIFICATE-----
MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMCVVMxDjAMBgNV
BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xMTAv
BgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEy
MTgxNDAzWhcNNDEwMjEyMTgxNDAzWjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAO
BgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv
bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuBBAAiA2IA
BEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI7Z4INcgn64mMU1jrYor+
8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPgCemB+vNH06NjMGEwHQYDVR0OBBYEFILR
hXMw5zUE044CkvvlpNHEIejNMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTT
jgKS++Wk0cQh6M0wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCW
e+0F+S8Tkdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+gA0z
5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl
-----END CERTIFICATE-----
SSL.com EV Root Certification Authority RSA R2
==============================================
-----BEGIN CERTIFICATE-----
MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNVBAYTAlVTMQ4w
DAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9u
MTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy
MB4XDTE3MDUzMTE4MTQzN1oXDTQyMDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQI
DAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYD
VQQDDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMIICIjAN
BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvqM0fNTPl9fb69LT3w23jh
hqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssufOePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7w
cXHswxzpY6IXFJ3vG2fThVUCAtZJycxa4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTO
Zw+oz12WGQvE43LrrdF9HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+
B6KjBSYRaZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcAb9Zh
CBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQGp8hLH94t2S42Oim
9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQVPWKchjgGAGYS5Fl2WlPAApiiECto
RHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMOpgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+Slm
JuwgUHfbSguPvuUCYHBBXtSuUDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48
+qvWBkofZ6aYMBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV
HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa49QaAJadz20Zp
qJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBWs47LCp1Jjr+kxJG7ZhcFUZh1
++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nx
Y/hoLVUE0fKNsKTPvDxeH3jnpaAgcLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2G
guDKBAdRUNf/ktUM79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDz
OFSz/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXtll9ldDz7
CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEmKf7GUmG6sXP/wwyc5Wxq
lD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKKQbNmC1r7fSOl8hqw/96bg5Qu0T/fkreR
rwU7ZcegbLHNYhLDkBvjJc40vG93drEQw/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1
hlMYegouCRw2n5H9gooiS9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX
9hwJ1C07mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w==
-----END CERTIFICATE-----
SSL.com EV Root Certification Authority ECC
===========================================
-----BEGIN CERTIFICATE-----
MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMCVVMxDjAMBgNV
BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xNDAy
BgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYw
MjEyMTgxNTIzWhcNNDEwMjEyMTgxNTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx
EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NM
LmNvbSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB
BAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMAVIbc/R/fALhBYlzccBYy
3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1KthkuWnBaBu2+8KGwytAJKaNjMGEwHQYDVR0O
BBYEFFvKXuXe0oGqzagtZFG22XKbl+ZPMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe
5d7SgarNqC1kUbbZcpuX5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJ
N+vp1RPZytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZgh5Mm
m7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg==
-----END CERTIFICATE-----
GlobalSign Root CA - R6
=======================
-----BEGIN CERTIFICATE-----
MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEgMB4GA1UECxMX
R2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds
b2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQxMjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9i
YWxTaWduIFJvb3QgQ0EgLSBSNjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFs
U2lnbjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQss
grRIxutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1kZguSgMpE
3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxDaNc9PIrFsmbVkJq3MQbF
vuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJwLnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqM
PKq0pPbzlUoSB239jLKJz9CgYXfIWHSw1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+
azayOeSsJDa38O+2HBNXk7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05O
WgtH8wY2SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/hbguy
CLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4nWUx2OVvq+aWh2IMP
0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpYrZxCRXluDocZXFSxZba/jJvcE+kN
b7gu3GduyYsRtYQUigAZcIN5kZeR1BonvzceMgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQE
AwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNV
HSMEGDAWgBSubAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN
nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGtIxg93eFyRJa0
lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr6155wsTLxDKZmOMNOsIeDjHfrY
BzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLjvUYAGm0CuiVdjaExUd1URhxN25mW7xocBFym
Fe944Hn+Xds+qkxV/ZoVqW/hpvvfcDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr
3TsTjxKM4kEaSHpzoHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB1
0jZpnOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfspA9MRf/T
uTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+vJJUEeKgDu+6B5dpffItK
oZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+t
JDfLRVpOoERIyNiwmcUVhAn21klJwGW45hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA=
-----END CERTIFICATE-----
OISTE WISeKey Global Root GC CA
===============================
-----BEGIN CERTIFICATE-----
MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQswCQYDVQQGEwJD
SDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNlZDEo
MCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRa
Fw00MjA1MDkwOTU4MzNaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQL
ExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh
bCBSb290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4nieUqjFqdr
VCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4Wp2OQ0jnUsYd4XxiWD1Ab
NTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd
BgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7TrYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0E
AwMDaAAwZQIwJsdpW9zV57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtk
AjEA2zQgMgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9
-----END CERTIFICATE-----
GTS Root R1
===========
-----BEGIN CERTIFICATE-----
MIIFWjCCA0KgAwIBAgIQbkepxUtHDA3sM9CJuRz04TANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQG
EwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJv
b3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAG
A1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIi
MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx
9vaMf/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7wCl7r
aKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjwTcLCeoiKu7rPWRnW
r4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0PfyblqAj+lug8aJRT7oM6iCsVlgmy4HqM
LnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly
4cpk9+aCEI3oncKKiPo4Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr
06zqkUspzBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92
wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70paDPvOmbsB4om
3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrNVjzRlwW5y0vtOUucxD/SVRNu
JLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD
VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEM
BQADggIBADiWCu49tJYeX++dnAsznyvgyv3SjgofQXSlfKqE1OXyHuY3UjKcC9FhHb8owbZEKTV1
d5iyfNm9dKyKaOOpMQkpAWBz40d8U6iQSifvS9efk+eCNs6aaAyC58/UEBZvXw6ZXPYfcX3v73sv
fuo21pdwCxXu11xWajOl40k4DLh9+42FpLFZXvRq4d2h9mREruZRgyFmxhE+885H7pwoHyXa/6xm
ld01D1zvICxi/ZG6qcz8WpyTgYMpl0p8WnK0OdC3d8t5/Wk6kjftbjhlRn7pYL15iJdfOBL07q9b
gsiG1eGZbYwE8na6SfZu6W0eX6DvJ4J2QPim01hcDyxC2kLGe4g0x8HYRZvBPsVhHdljUEn2NIVq
4BjFbkerQUIpm/ZgDdIx02OYI5NaAIFItO/Nis3Jz5nu2Z6qNuFoS3FJFDYoOj0dzpqPJeaAcWEr
tXvM+SUWgeExX6GjfhaknBZqlxi9dnKlC54dNuYvoS++cJEPqOba+MSSQGwlfnuzCdyyF62ARPBo
pY+Udf90WuioAnwMCeKpSwughQtiue+hMZL77/ZRBIls6Kl0obsXs7X9SQ98POyDGCBDTtWTurQ0
sR8WNh8M5mQ5Fkzc4P4dyKliPUDqysU0ArSuiYgzNdwsE3PYJ/HQcu51OyLemGhmW/HGY0dVHLql
CFF1pkgl
-----END CERTIFICATE-----
GTS Root R2
===========
-----BEGIN CERTIFICATE-----
MIIFWjCCA0KgAwIBAgIQbkepxlqz5yDFMJo/aFLybzANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQG
EwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJv
b3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAG
A1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIi
MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTuk
k3LvCvptnfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY6Dlo
7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAuMC6C/Pq8tBcKSOWI
m8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7kRXuJVfeKH2JShBKzwkCX44ofR5Gm
dFrS+LFjKBC4swm4VndAoiaYecb+3yXuPuWgf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbu
ak7MkogwTZq9TwtImoS1mKPV+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscsz
cTJGr61K8YzodDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RW
Ir9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKaG73Vululycsl
aVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCqgc7dGtxRcw1PcOnlthYhGXmy
5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD
VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEM
BQADggIBALZp8KZ3/p7uC4Gt4cCpx/k1HUCCq+YEtN/L9x0Pg/B+E02NjO7jMyLDOfxA325BS0JT
vhaI8dI4XsRomRyYUpOM52jtG2pzegVATX9lO9ZY8c6DR2Dj/5epnGB3GFW1fgiTz9D2PGcDFWEJ
+YF59exTpJ/JjwGLc8R3dtyDovUMSRqodt6Sm2T4syzFJ9MHwAiApJiS4wGWAqoC7o87xdFtCjMw
c3i5T1QWvwsHoaRc5svJXISPD+AVdyx+Jn7axEvbpxZ3B7DNdehyQtaVhJ2Gg/LkkM0JR9SLA3Da
WsYDQvTtN6LwG1BUSw7YhN4ZKJmBR64JGz9I0cNv4rBgF/XuIwKl2gBbbZCr7qLpGzvpx0QnRY5r
n/WkhLx3+WuXrD5RRaIRpsyF7gpo8j5QOHokYh4XIDdtak23CZvJ/KRY9bb7nE4Yu5UC56Gtmwfu
Nmsk0jmGwZODUNKBRqhfYlcsu2xkiAhu7xNUX90txGdj08+JN7+dIPT7eoOboB6BAFDC5AwiWVIQ
7UNWhwD4FFKnHYuTjKJNRn8nxnGbJN7k2oaLDX5rIMHAnuFl2GqjpuiFizoHCBy69Y9Vmhh1fuXs
gWbRIXOhNUQLgD1bnF5vKheW0YMjiGZt5obicDIvUiLnyOd/xCxgXS/Dr55FBcOEArf9LAhST4Ld
o/DUhgkC
-----END CERTIFICATE-----
GTS Root R3
===========
-----BEGIN CERTIFICATE-----
MIICDDCCAZGgAwIBAgIQbkepx2ypcyRAiQ8DVd2NHTAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJV
UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg
UjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE
ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcq
hkjOPQIBBgUrgQQAIgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUU
Rout736GjOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL24Cej
QjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTB8Sa6oC2uhYHP
0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEAgFukfCPAlaUs3L6JbyO5o91lAFJekazInXJ0
glMLfalAvWhgxeG4VDvBNhcl2MG9AjEAnjWSdIUlUfUk7GRSJFClH9voy8l27OyCbvWFGFPouOOa
KaqW04MjyaR7YbPMAuhd
-----END CERTIFICATE-----
GTS Root R4
===========
-----BEGIN CERTIFICATE-----
MIICCjCCAZGgAwIBAgIQbkepyIuUtui7OyrYorLBmTAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJV
UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg
UjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE
ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcq
hkjOPQIBBgUrgQQAIgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa
6zzuhXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvRHYqj
QjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSATNbrdP9JNqPV
2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNnADBkAjBqUFJ0CMRw3J5QdCHojXohw0+WbhXRIjVhLfoI
N+4Zba3bssx9BzT1YBkstTTZbyACMANxsbqjYAuG7ZoIapVon+Kz4ZNkfF6Tpt95LY2F45TPI11x
zPKwTdb+mciUqXWi4w==
-----END CERTIFICATE-----
UCA Global G2 Root
==================
-----BEGIN CERTIFICATE-----
MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9MQswCQYDVQQG
EwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBHbG9iYWwgRzIgUm9vdDAeFw0x
NjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0xCzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlU
cnVzdDEbMBkGA1UEAwwSVUNBIEdsb2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
MIICCgKCAgEAxeYrb3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmT
oni9kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzmVHqUwCoV
8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/RVogvGjqNO7uCEeBHANBS
h6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDcC/Vkw85DvG1xudLeJ1uK6NjGruFZfc8o
LTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIjtm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/
R+zvWr9LesGtOxdQXGLYD0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBe
KW4bHAyvj5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6DlNaBa
4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6iIis7nCs+dwp4wwc
OxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznPO6Q0ibd5Ei9Hxeepl2n8pndntd97
8XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O
BBYEFIHEjMz15DD/pQwIX4wVZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo
5sOASD0Ee/ojL3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5
1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl1qnN3e92mI0A
Ds0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oUb3n09tDh05S60FdRvScFDcH9
yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LVPtateJLbXDzz2K36uGt/xDYotgIVilQsnLAX
c47QN6MUPJiVAAwpBVueSUmxX8fjy88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHo
jhJi6IjMtX9Gl8CbEGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZk
bxqgDMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI+Vg7RE+x
ygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGyYiGqhkCyLmTTX8jjfhFn
RR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bXUB+K+wb1whnw0A==
-----END CERTIFICATE-----
UCA Extended Validation Root
============================
-----BEGIN CERTIFICATE-----
MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBHMQswCQYDVQQG
EwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9u
IFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMxMDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8G
A1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIi
MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrs
iWogD4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvSsPGP2KxF
Rv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aopO2z6+I9tTcg1367r3CTu
eUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dksHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR
59mzLC52LqGj3n5qiAno8geK+LLNEOfic0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH
0mK1lTnj8/FtDw5lhIpjVMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KR
el7sFsLzKuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/TuDv
B0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41Gsx2VYVdWf6/wFlth
WG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs1+lvK9JKBZP8nm9rZ/+I8U6laUpS
NwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQDfwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS
3H5aBZ8eNJr34RQwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQEL
BQADggIBADaNl8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR
ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQVBcZEhrxH9cM
aVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5c6sq1WnIeJEmMX3ixzDx/BR4
dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb
+7lsq+KePRXBOy5nAliRn+/4Qh8st2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOW
F3sGPjLtx7dCvHaj2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwi
GpWOvpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2CxR9GUeOc
GMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmxcmtpzyKEC2IPrNkZAJSi
djzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbMfjKaiJUINlK73nZfdklJrX+9ZSCyycEr
dhh2n1ax
-----END CERTIFICATE-----
Certigna Root CA
================
-----BEGIN CERTIFICATE-----
MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAwWjELMAkGA1UE
BhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAwMiA0ODE0NjMwODEwMDAzNjEZ
MBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0xMzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjda
MFoxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYz
MDgxMDAwMzYxGTAXBgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4IC
DwAwggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sOty3tRQgX
stmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9MCiBtnyN6tMbaLOQdLNyz
KNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPuI9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8
JXrJhFwLrN1CTivngqIkicuQstDuI7pmTLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16
XdG+RCYyKfHx9WzMfgIhC59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq
4NYKpkDfePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3YzIoej
wpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWTCo/1VTp2lc5ZmIoJ
lXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1kJWumIWmbat10TWuXekG9qxf5kBdI
jzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp/
/TBt2dzhauH8XwIDAQABo4IBGjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw
HQYDVR0OBBYEFBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of
1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczovL3d3d3cuY2Vy
dGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilodHRwOi8vY3JsLmNlcnRpZ25h
LmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYraHR0cDovL2NybC5kaGlteW90aXMuY29tL2Nl
cnRpZ25hcm9vdGNhLmNybDANBgkqhkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOIt
OoldaDgvUSILSo3L6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxP
TGRGHVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH60BGM+RFq
7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncBlA2c5uk5jR+mUYyZDDl3
4bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdio2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd
8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS
6Cvu5zHbugRqh5jnxV/vfaci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaY
tlu3zM63Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayhjWZS
aX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw3kAP+HwV96LOPNde
E4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0=
-----END CERTIFICATE-----
emSign Root CA - G1
===================
-----BEGIN CERTIFICATE-----
MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYDVQQGEwJJTjET
MBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRl
ZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBHMTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgx
ODMwMDBaMGcxCzAJBgNVBAYTAklOMRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVk
aHJhIFRlY2hub2xvZ2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIB
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQzf2N4aLTN
LnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO8oG0x5ZOrRkVUkr+PHB1
cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aqd7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHW
DV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhMtTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ
6DqS0hdW5TUaQBw+jSztOd9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrH
hQIDAQABo0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQDAgEG
MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31xPaOfG1vR2vjTnGs2
vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjMwiI/aTvFthUvozXGaCocV685743Q
NcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6dGNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q
+Mri/Tm3R7nrft8EI6/6nAYH6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeih
U80Bv2noWgbyRQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx
iN66zB+Afko=
-----END CERTIFICATE-----
emSign ECC Root CA - G3
=======================
-----BEGIN CERTIFICATE-----
MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQGEwJJTjETMBEG
A1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEg
MB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4
MTgzMDAwWjBrMQswCQYDVQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11
ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g
RzMwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0WXTsuwYc
58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xySfvalY8L1X44uT6EYGQIr
MgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuBzhccLikenEhjQjAOBgNVHQ8BAf8EBAMC
AQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+D
CBeQyh+KTOgNG3qxrdWBCUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7
jHvrZQnD+JbNR6iC8hZVdyR+EhCVBCyj
-----END CERTIFICATE-----
emSign Root CA - C1
===================
-----BEGIN CERTIFICATE-----
MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkGA1UEBhMCVVMx
EzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNp
Z24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UE
BhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQD
ExNlbVNpZ24gUm9vdCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+up
ufGZBczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZHdPIWoU/
Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH3DspVpNqs8FqOp099cGX
OFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvHGPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4V
I5b2P/AgNBbeCsbEBEV5f6f9vtKppa+cxSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleooms
lMuoaJuvimUnzYnu3Yy1aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+
XJGFehiqTbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQAD
ggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87/kOXSTKZEhVb3xEp
/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4kqNPEjE2NuLe/gDEo2APJ62gsIq1
NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrGYQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9
wC68AivTxEDkigcxHpvOJpkT+xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQ
BmIMMMAVSKeoWXzhriKi4gp6D/piq1JM4fHfyr6DDUI=
-----END CERTIFICATE-----
emSign ECC Root CA - C3
=======================
-----BEGIN CERTIFICATE-----
MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQGEwJVUzETMBEG
A1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMxIDAeBgNVBAMTF2VtU2lnbiBF
Q0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UE
BhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQD
ExdlbVNpZ24gRUNDIFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd
6bciMK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4OjavtisIGJAnB9
SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0OBBYEFPtaSNCAIEDyqOkA
B2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gA
MGUCMQC02C8Cif22TGK6Q04ThHK1rt0c3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwU
ZOR8loMRnLDRWmFLpg9J0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ==
-----END CERTIFICATE-----
Hongkong Post Root CA 3
=======================
-----BEGIN CERTIFICATE-----
MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQELBQAwbzELMAkG
A1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJSG9uZyBLb25nMRYwFAYDVQQK
Ew1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25na29uZyBQb3N0IFJvb3QgQ0EgMzAeFw0xNzA2
MDMwMjI5NDZaFw00MjA2MDMwMjI5NDZaMG8xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtv
bmcxEjAQBgNVBAcTCUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEgMB4GA1UEAxMX
SG9uZ2tvbmcgUG9zdCBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz
iNfqzg8gTr7m1gNt7ln8wlffKWihgw4+aMdoWJwcYEuJQwy51BWy7sFOdem1p+/l6TWZ5Mwc50tf
jTMwIDNT2aa71T4Tjukfh0mtUC1Qyhi+AViiE3CWu4mIVoBc+L0sPOFMV4i707mV78vH9toxdCim
5lSJ9UExyuUmGs2C4HDaOym71QP1mbpV9WTRYA6ziUm4ii8F0oRFKHyPaFASePwLtVPLwpgchKOe
sL4jpNrcyCse2m5FHomY2vkALgbpDDtw1VAliJnLzXNg99X/NWfFobxeq81KuEXryGgeDQ0URhLj
0mRiikKYvLTGCAj4/ahMZJx2Ab0vqWwzD9g/KLg8aQFChn5pwckGyuV6RmXpwtZQQS4/t+TtbNe/
JgERohYpSms0BpDsE9K2+2p20jzt8NYt3eEV7KObLyzJPivkaTv/ciWxNoZbx39ri1UbSsUgYT2u
y1DhCDq+sI9jQVMwCFk8mB13umOResoQUGC/8Ne8lYePl8X+l2oBlKN8W4UdKjk60FSh0Tlxnf0h
+bV78OLgAo9uliQlLKAeLKjEiafv7ZkGL7YKTE/bosw3Gq9HhS2KX8Q0NEwA/RiTZxPRN+ZItIsG
xVd7GYYKecsAyVKvQv83j+GjHno9UKtjBucVtT+2RTeUN7F+8kjDf8V1/peNRY8apxpyKBpADwID
AQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQXnc0e
i9Y5K3DTXNSguB+wAPzFYTAdBgNVHQ4EFgQUF53NHovWOStw01zUoLgfsAD8xWEwDQYJKoZIhvcN
AQELBQADggIBAFbVe27mIgHSQpsY1Q7XZiNc4/6gx5LS6ZStS6LG7BJ8dNVI0lkUmcDrudHr9Egw
W62nV3OZqdPlt9EuWSRY3GguLmLYauRwCy0gUCCkMpXRAJi70/33MvJJrsZ64Ee+bs7Lo3I6LWld
y8joRTnU+kLBEUx3XZL7av9YROXrgZ6voJmtvqkBZss4HTzfQx/0TW60uhdG/H39h4F5ag0zD/ov
+BS5gLNdTaqX4fnkGMX41TiMJjz98iji7lpJiCzfeT2OnpA8vUFKOt1b9pq0zj8lMH8yfaIDlNDc
eqFS3m6TjRgm/VWsvY+b0s+v54Ysyx8Jb6NvqYTUc79NoXQbTiNg8swOqn+knEwlqLJmOzj/2ZQw
9nKEvmhVEA/GcywWaZMH/rFF7buiVWqw2rVKAiUnhde3t4ZEFolsgCs+l6mc1X5VTMbeRRAc6uk7
nwNT7u56AQIWeNTowr5GdogTPyK7SBIdUgC0An4hGh6cJfTzPV4e0hz5sy229zdcxsshTrD3mUcY
hcErulWuBurQB7Lcq9CClnXO0lD+mefPL5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB
60PZ2Pierc+xYw5F9KBaLJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fq
dBb9HxEGmpv0
-----END CERTIFICATE-----
Entrust Root Certification Authority - G4
=========================================
-----BEGIN CERTIFICATE-----
MIIGSzCCBDOgAwIBAgIRANm1Q3+vqTkPAAAAAFVlrVgwDQYJKoZIhvcNAQELBQAwgb4xCzAJBgNV
BAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3Qu
bmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1
dGhvcml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1
dGhvcml0eSAtIEc0MB4XDTE1MDUyNzExMTExNloXDTM3MTIyNzExNDExNlowgb4xCzAJBgNVBAYT
AlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0
L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhv
cml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhv
cml0eSAtIEc0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsewsQu7i0TD/pZJH4i3D
umSXbcr3DbVZwbPLqGgZ2K+EbTBwXX7zLtJTmeH+H17ZSK9dE43b/2MzTdMAArzE+NEGCJR5WIoV
3imz/f3ET+iq4qA7ec2/a0My3dl0ELn39GjUu9CH1apLiipvKgS1sqbHoHrmSKvS0VnM1n4j5pds
8ELl3FFLFUHtSUrJ3hCX1nbB76W1NhSXNdh4IjVS70O92yfbYVaCNNzLiGAMC1rlLAHGVK/XqsEQ
e9IFWrhAnoanw5CGAlZSCXqc0ieCU0plUmr1POeo8pyvi73TDtTUXm6Hnmo9RR3RXRv06QqsYJn7
ibT/mCzPfB3pAqoEmh643IhuJbNsZvc8kPNXwbMv9W3y+8qh+CmdRouzavbmZwe+LGcKKh9asj5X
xNMhIWNlUpEbsZmOeX7m640A2Vqq6nPopIICR5b+W45UYaPrL0swsIsjdXJ8ITzI9vF01Bx7owVV
7rtNOzK+mndmnqxpkCIHH2E6lr7lmk/MBTwoWdPBDFSoWWG9yHJM6Nyfh3+9nEg2XpWjDrk4JFX8
dWbrAuMINClKxuMrLzOg2qOGpRKX/YAr2hRC45K9PvJdXmd0LhyIRyk0X+IyqJwlN4y6mACXi0mW
Hv0liqzc2thddG5msP9E36EYxr5ILzeUePiVSj9/E15dWf10hkNjc0kCAwEAAaNCMEAwDwYDVR0T
AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJ84xFYjwznooHFs6FRM5Og6sb9n
MA0GCSqGSIb3DQEBCwUAA4ICAQAS5UKme4sPDORGpbZgQIeMJX6tuGguW8ZAdjwD+MlZ9POrYs4Q
jbRaZIxowLByQzTSGwv2LFPSypBLhmb8qoMi9IsabyZIrHZ3CL/FmFz0Jomee8O5ZDIBf9PD3Vht
7LGrhFV0d4QEJ1JrhkzO3bll/9bGXp+aEJlLdWr+aumXIOTkdnrG0CSqkM0gkLpHZPt/B7NTeLUK
YvJzQ85BK4FqLoUWlFPUa19yIqtRLULVAJyZv967lDtX/Zr1hstWO1uIAeV8KEsD+UmDfLJ/fOPt
jqF/YFOOVZ1QNBIPt5d7bIdKROf1beyAN/BYGW5KaHbwH5Lk6rWS02FREAutp9lfx1/cH6NcjKF+
m7ee01ZvZl4HliDtC3T7Zk6LERXpgUl+b7DUUH8i119lAg2m9IUe2K4GS0qn0jFmwvjO5QimpAKW
RGhXxNUzzxkvFMSUHHuk2fCfDrGA4tGeEWSpiBE6doLlYsKA2KSD7ZPvfC+QsDJMlhVoSFLUmQjA
JOgc47OlIQ6SwJAfzyBfyjs4x7dtOvPmRLgOMWuIjnDrnBdSqEGULoe256YSxXXfW8AKbnuk5F6G
+TaU33fD6Q3AOfF5u0aOq0NZJ7cguyPpVkAh7DE9ZapD8j3fcEThuk0mEDuYn/PIjhs4ViFqUZPT
kcpG2om3PVODLAgfi49T3f+sHw==
-----END CERTIFICATE-----
Microsoft ECC Root Certificate Authority 2017
=============================================
-----BEGIN CERTIFICATE-----
MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV
UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQgRUND
IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4
MjMxNjA0WjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw
NAYDVQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQ
BgcqhkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZRogPZnZH6
thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYbhGBKia/teQ87zvH2RPUB
eMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTIy5lycFIM
+Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlf
Xu5gKcs68tvWMoQZP3zVL8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaR
eNtUjGUBiudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M=
-----END CERTIFICATE-----
Microsoft RSA Root Certificate Authority 2017
=============================================
-----BEGIN CERTIFICATE-----
MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBlMQswCQYDVQQG
EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQg
UlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIw
NzE4MjMwMDIzWjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u
MTYwNAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcw
ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZNt9GkMml
7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0ZdDMbRnMlfl7rEqUrQ7e
S0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw7
1VdyvD/IybLeS2v4I2wDwAW9lcfNcztmgGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+
dkC0zVJhUXAoP8XFWvLJjEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49F
yGcohJUcaDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaGYaRS
MLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6W6IYZVcSn2i51BVr
lMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4KUGsTuqwPN1q3ErWQgR5WrlcihtnJ
0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH+FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJ
ClTUFLkqqNfs+avNJVgyeY+QW5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYw
DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC
NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZCLgLNFgVZJ8og
6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OCgMNPOsduET/m4xaRhPtthH80
dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk
+ONVFT24bcMKpBLBaYVu32TxU5nhSnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex
/2kskZGT4d9Mozd2TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDy
AmH3pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGRxpl/j8nW
ZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiAppGWSZI1b7rCoucL5mxAyE
7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKT
c0QWbej09+CVgI+WXTik9KveCjCHk9hNAHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D
5KbvtwEwXlGjefVwaaZBRA+GsCyRxj3qrg+E
-----END CERTIFICATE-----
e-Szigno Root CA 2017
=====================
-----BEGIN CERTIFICATE-----
MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNVBAYTAkhVMREw
DwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUt
MjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJvb3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZa
Fw00MjA4MjIxMjA3MDZaMHExCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UE
CgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3pp
Z25vIFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtvxie+RJCx
s1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+HWyx7xf58etqjYzBhMA8G
A1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSHERUI0arBeAyxr87GyZDv
vzAEwDAfBgNVHSMEGDAWgBSHERUI0arBeAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEA
tVfd14pVCzbhhkT61NlojbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxO
svxyqltZ+efcMQ==
-----END CERTIFICATE-----
certSIGN Root CA G2
===================
-----BEGIN CERTIFICATE-----
MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNVBAYTAlJPMRQw
EgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjAeFw0xNzAy
MDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJBgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lH
TiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIP
ADCCAgoCggIBAMDFdRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05
N0IwvlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZuIt4Imfk
abBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhpn+Sc8CnTXPnGFiWeI8Mg
wT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKscpc/I1mbySKEwQdPzH/iV8oScLumZfNp
dWO9lfsbl83kqK/20U6o2YpxJM02PbyWxPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91Qqh
ngLjYl/rNUssuHLoPj1PrCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732
jcZZroiFDsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fxDTvf
95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgyLcsUDFDYg2WD7rlc
z8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6CeWRgKRM+o/1Pcmqr4tTluCRVLERL
iohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1Ud
DgQWBBSCIS1mxteg4BXrzkwJd8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOB
ywaK8SJJ6ejqkX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC
b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQlqiCA2ClV9+BB
/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0OJD7uNGzcgbJceaBxXntC6Z5
8hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+cNywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5
BiKDUyUM/FHE5r7iOZULJK2v0ZXkltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklW
atKcsWMy5WHgUyIOpwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tU
Sxfj03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZkPuXaTH4M
NMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE1LlSVHJ7liXMvGnjSG4N
0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MXQRBdJ3NghVdJIgc=
-----END CERTIFICATE-----
Trustwave Global Certification Authority
========================================
-----BEGIN CERTIFICATE-----
MIIF2jCCA8KgAwIBAgIMBfcOhtpJ80Y1LrqyMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJV
UzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2
ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9u
IEF1dGhvcml0eTAeFw0xNzA4MjMxOTM0MTJaFw00MjA4MjMxOTM0MTJaMIGIMQswCQYDVQQGEwJV
UzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2
ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9u
IEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALldUShLPDeS0YLOvR29
zd24q88KPuFd5dyqCblXAj7mY2Hf8g+CY66j96xz0XznswuvCAAJWX/NKSqIk4cXGIDtiLK0thAf
LdZfVaITXdHG6wZWiYj+rDKd/VzDBcdu7oaJuogDnXIhhpCujwOl3J+IKMujkkkP7NAP4m1ET4Bq
stTnoApTAbqOl5F2brz81Ws25kCI1nsvXwXoLG0R8+eyvpJETNKXpP7ScoFDB5zpET71ixpZfR9o
WN0EACyW80OzfpgZdNmcc9kYvkHHNHnZ9GLCQ7mzJ7Aiy/k9UscwR7PJPrhq4ufogXBeQotPJqX+
OsIgbrv4Fo7NDKm0G2x2EOFYeUY+VM6AqFcJNykbmROPDMjWLBz7BegIlT1lRtzuzWniTY+HKE40
Cz7PFNm73bZQmq131BnW2hqIyE4bJ3XYsgjxroMwuREOzYfwhI0Vcnyh78zyiGG69Gm7DIwLdVcE
uE4qFC49DxweMqZiNu5m4iK4BUBjECLzMx10coos9TkpoNPnG4CELcU9402x/RpvumUHO1jsQkUm
+9jaJXLE9gCxInm943xZYkqcBW89zubWR2OZxiRvchLIrH+QtAuRcOi35hYQcRfO3gZPSEF9NUqj
ifLJS3tBEW1ntwiYTOURGa5CgNz7kAXU+FDKvuStx8KU1xad5hePrzb7AgMBAAGjQjBAMA8GA1Ud
EwEB/wQFMAMBAf8wHQYDVR0OBBYEFJngGWcNYtt2s9o9uFvo/ULSMQ6HMA4GA1UdDwEB/wQEAwIB
BjANBgkqhkiG9w0BAQsFAAOCAgEAmHNw4rDT7TnsTGDZqRKGFx6W0OhUKDtkLSGm+J1WE2pIPU/H
PinbbViDVD2HfSMF1OQc3Og4ZYbFdada2zUFvXfeuyk3QAUHw5RSn8pk3fEbK9xGChACMf1KaA0H
ZJDmHvUqoai7PF35owgLEQzxPy0QlG/+4jSHg9bP5Rs1bdID4bANqKCqRieCNqcVtgimQlRXtpla
4gt5kNdXElE1GYhBaCXUNxeEFfsBctyV3lImIJgm4nb1J2/6ADtKYdkNy1GTKv0WBpanI5ojSP5R
vbbEsLFUzt5sQa0WZ37b/TjNuThOssFgy50X31ieemKyJo90lZvkWx3SD92YHJtZuSPTMaCm/zjd
zyBP6VhWOmfD0faZmZ26NraAL4hHT4a/RDqA5Dccprrql5gR0IRiR2Qequ5AvzSxnI9O4fKSTx+O
856X3vOmeWqJcU9LJxdI/uz0UA9PSX3MReO9ekDFQdxhVicGaeVyQYHTtgGJoC86cnn+OjC/QezH
Yj6RS8fZMXZC+fc8Y+wmjHMMfRod6qh8h6jCJ3zhM0EPz8/8AKAigJ5Kp28AsEFFtyLKaEjFQqKu
3R3y4G5OBVixwJAWKqQ9EEC+j2Jjg6mcgn0tAumDMHzLJ8n9HmYAsC7TIS+OMxZsmO0QqAfWzJPP
29FpHOTKyeC2nOnOcXHebD8WpHk=
-----END CERTIFICATE-----
Trustwave Global ECC P256 Certification Authority
=================================================
-----BEGIN CERTIFICATE-----
MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYDVQQGEwJVUzER
MA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBI
b2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDI1NiBDZXJ0aWZp
Y2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMxOTM1MTBaFw00MjA4MjMxOTM1MTBaMIGRMQswCQYD
VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRy
dXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDI1
NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABH77bOYj
43MyCMpg5lOcunSNGLB4kFKA3TjASh3RqMyTpJcGOMoNFWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqm
P62jQzBBMA8GA1UdEwEB/wQFMAMBAf8wDwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt
0UrrdaVKEJmzsaGLSvcwCgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjz
RM4q3wghDDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7
-----END CERTIFICATE-----
Trustwave Global ECC P384 Certification Authority
=================================================
-----BEGIN CERTIFICATE-----
MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYDVQQGEwJVUzER
MA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBI
b2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDM4NCBDZXJ0aWZp
Y2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMxOTM2NDNaFw00MjA4MjMxOTM2NDNaMIGRMQswCQYD
VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRy
dXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDM4
NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTB2MBAGByqGSM49AgEGBSuBBAAiA2IABGvaDXU1CDFH
Ba5FmVXxERMuSvgQMSOjfoPTfygIOiYaOs+Xgh+AtycJj9GOMMQKmw6sWASr9zZ9lCOkmwqKi6vr
/TklZvFe/oyujUF5nQlgziip04pt89ZF1PKYhDhloKNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNV
HQ8BAf8EBQMDBwYAMB0GA1UdDgQWBBRVqYSJ0sEyvRjLbKYHTsjnnb6CkDAKBggqhkjOPQQDAwNn
ADBkAjA3AZKXRRJ+oPM+rRk6ct30UJMDEr5E0k9BpIycnR+j9sKS50gU/k6bpZFXrsY3crsCMGcl
CrEMXu6pY5Jv5ZAL/mYiykf9ijH3g/56vxC+GCsej/YpHpRZ744hN8tRmKVuSw==
-----END CERTIFICATE-----
NAVER Global Root Certification Authority
=========================================
-----BEGIN CERTIFICATE-----
MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEMBQAwaTELMAkG
A1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRGT1JNIENvcnAuMTIwMAYDVQQD
DClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4
NDJaFw0zNzA4MTgyMzU5NTlaMGkxCzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVT
UyBQTEFURk9STSBDb3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVAiQqrDZBb
UGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH38dq6SZeWYp34+hInDEW
+j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lEHoSTGEq0n+USZGnQJoViAbbJAh2+g1G7
XNr4rRVqmfeSVPc0W+m/6imBEtRTkZazkVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2
aacp+yPOiNgSnABIqKYPszuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4
Yb8ObtoqvC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHfnZ3z
VHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaGYQ5fG8Ir4ozVu53B
A0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo0es+nPxdGoMuK8u180SdOqcXYZai
cdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3aCJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejy
YhbLgGvtPe31HzClrkvJE+2KAQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNV
HQ4EFgQU0p+I36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB
Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoNqo0hV4/GPnrK
21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatjcu3cvuzHV+YwIHHW1xDBE1UB
jCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm+LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bx
hYTeodoS76TiEJd6eN4MUZeoIUCLhr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTg
E34h5prCy8VCZLQelHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTH
D8z7p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8piKCk5XQ
A76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLRLBT/DShycpWbXgnbiUSY
qqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oG
I/hGoiLtk/bdmuYqh7GYVPEi92tF4+KOdh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmg
kpzNNIaRkPpkUZ3+/uul9XXeifdy
-----END CERTIFICATE-----
AC RAIZ FNMT-RCM SERVIDORES SEGUROS
===================================
-----BEGIN CERTIFICATE-----
MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQswCQYDVQQGEwJF
UzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgwFgYDVQRhDA9WQVRFUy1RMjgy
NjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1SQ00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4
MTIyMDA5MzczM1oXDTQzMTIyMDA5MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQt
UkNNMQ4wDAYDVQQLDAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNB
QyBSQUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuBBAAiA2IA
BPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LHsbI6GA60XYyzZl2hNPk2
LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oKUm8BA06Oi6NCMEAwDwYDVR0TAQH/BAUw
AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqG
SM49BAMDA2kAMGYCMQCuSuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoD
zBOQn5ICMQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJyv+c=
-----END CERTIFICATE-----
GlobalSign Secure Mail Root R45
===============================
-----BEGIN CERTIFICATE-----
MIIFcDCCA1igAwIBAgIQdlP+qExQq5+NMrUdA49X3DANBgkqhkiG9w0BAQwFADBSMQswCQYDVQQG
EwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEoMCYGA1UEAxMfR2xvYmFsU2lnbiBTZWN1
cmUgTWFpbCBSb290IFI0NTAeFw0yMDAzMTgwMDAwMDBaFw00NTAzMTgwMDAwMDBaMFIxCzAJBgNV
BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSgwJgYDVQQDEx9HbG9iYWxTaWduIFNl
Y3VyZSBNYWlsIFJvb3QgUjQ1MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA3HnMbQb5
bbvgVgRsf+B1zC0FSehL3FTsW3eVcr9/Yp2FqYokUF9T5dt0b6QpWxMqCa2axS/C93Y7oUVGqkPm
JP4rsG8ycBlGWnkmL/w9fV9ky1fMYWGo2ZVu45Wgbn9HEhjW7wPJ+4r6mr2CFalVd0sRT1nga8Nx
8wzYVNWBaD4TuRUuh4o8RCc2YiRu+CwFcjBhvUKRI8SdJafZVJoUozGtgHkMp2NsmKOsV0czH2WW
4dDSNdr5cfehpiW1QV3fPmDY0fafpfK4zBOqj/mybuGDLZPdPoUa3eixXCYBy0mF/PzS1H+FYoZ0
+cvsNSKiDDCPO6t561by+kLz7fkfRYlAKa3qknTqUv1WtCvaou11wm6rzlKQS/be8EmPmkjUiBlt
RebMjLndZGBgAkD4uc+8WOs9hbnGCtOcB2aPxxg5I0bhPB6jL1Bhkgs9K2zxo0c4V5GrDY/GnU0E
0iZSXOWl/SotFioBaeepfeE2t7Eqxdmxjb25i87Mi6E+C0jNUJU0xNgIWdhrJvS+9dQiFwBXya6b
BDAznwv731aiyW5Udtqxl2InWQ8RiiIbZJY/qPG3JEqNPFN8bYN2PbImSHP1RBYBLQkqjhaWUNBz
Bl27IkiCTApGWj+A/1zy8pqsLAjg1urwEjiBT6YQ7UarzBacC89kppkChURnRq39TecCAwEAAaNC
MEAwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFKCTFShu7o8IsjXG
nmJ5dKexDit7MA0GCSqGSIb3DQEBDAUAA4ICAQBFCvjRXKxigdAE17b/V1GJCwzL3iRlN/urnu1m
9OoMGWmJuBmxMFa02fb3vsaul8tF9hGMOjBkTMGfWcBGQggGR2QXeOCVBwbWjKKsqdk/03tWT/zE
hyjftisWI8CfH1vj1kReIk8jBIw1FrV5B4ZcL5fi9ghkptzbqIrjpHt3DdEpkyggtFOjS05f3sH2
dSP8Hzx4T3AxeC+iNVRxBKzIxG3D9pGx/s3uRG6B9kDFPioBv6tMsQM/DRHkD9Ik4yKIm59fRz1R
SeAJN34XITF2t2dxSChLJdcQ6J9hWRbFPjJOHwzOo8wP5McRByIvOAjdW5frQmxZmpruetCd38Xb
CUMuCqoZPWvoajB6V+a/s2o5qY/j8U9laLa9nyiPoRZaCVA6Mi4dL0QRQqYA5jGY/y2hD+akYFbP
edeyTtew+m4MVyPHzh+lsUxtGUmeDn9wj3E/WCifdd1h4Dq3Obbul9Q1UfuLSWDIPGaul+6NJllX
u3jwelAwCbBgqp9O3Mk+HjrcYpMzsDpUdG8sMUXRaxEyamh29j32ahNeJJjn6h2az3iCB2D3TRDT
gZpFjZ6vm9yAx0OylWikww7oCkcVv1Qz3AHn1aYec9h6sr8vreNVMJ7fDkG84BH1oQyoIuHjAKNO
cHyS4wTRekKKdZBZ45vRTKJkvXN5m2/ys8H2PA==
-----END CERTIFICATE-----
GlobalSign Secure Mail Root E45
===============================
-----BEGIN CERTIFICATE-----
MIICITCCAaegAwIBAgIQdlP+qicdlUZd1vGe5biQCjAKBggqhkjOPQQDAzBSMQswCQYDVQQGEwJC
RTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEoMCYGA1UEAxMfR2xvYmFsU2lnbiBTZWN1cmUg
TWFpbCBSb290IEU0NTAeFw0yMDAzMTgwMDAwMDBaFw00NTAzMTgwMDAwMDBaMFIxCzAJBgNVBAYT
AkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSgwJgYDVQQDEx9HbG9iYWxTaWduIFNlY3Vy
ZSBNYWlsIFJvb3QgRTQ1MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE+XmLgUc3iZY/RUlQfxomC5My
fi7AwKcImsNuj5s+CyLsN1O3b4qwvCc3S22pRjvZH/+loUS7LXO/nkEHXFObUQg6WrtvOMcWkXjC
ShNpHYLfWi8AiJaiLhx0+Z1+ZjeKo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB
/zAdBgNVHQ4EFgQU3xNei1/CQAL9VreUTLYe1aaxFJYwCgYIKoZIzj0EAwMDaAAwZQIwE7C+13Eg
PuSrnM42En1fTB8qtWlFM1/TLVqy5IjH3go2QjJ5naZruuH5RCp7isMSAjEAoGYcToedh8ntmUwb
Cu4tYMM3xx3NtXKw2cbvvPL/P/BS3QjnqmR5w+RpV5EvpMt8
-----END CERTIFICATE-----
GlobalSign Root R46
===================
-----BEGIN CERTIFICATE-----
MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUAMEYxCzAJBgNV
BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJv
b3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAX
BgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIi
MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08Es
CVeJOaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQGvGIFAha/
r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud316HCkD7rRlr+/fKYIje
2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo0q3v84RLHIf8E6M6cqJaESvWJ3En7YEt
bWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSEy132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvj
K8Cd+RTyG/FWaha/LIWFzXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD4
12lPFzYE+cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCNI/on
ccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzsx2sZy/N78CsHpdls
eVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqaByFrgY/bxFn63iLABJzjqls2k+g9
vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYD
VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEM
BQADggIBAHx47PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg
JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti2kM3S+LGteWy
gxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIkpnnpHs6i58FZFZ8d4kuaPp92
CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRFFRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZm
OUdkLG5NrmJ7v2B0GbhWrJKsFjLtrWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qq
JZ4d16GLuc1CLgSkZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwye
qiv5u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP4vkYxboz
nxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6N3ec592kD3ZDZopD8p/7
DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3vouXsXgxT7PntgMTzlSdriVZzH81Xwj3
QEUxeCp6
-----END CERTIFICATE-----
GlobalSign Root E46
===================
-----BEGIN CERTIFICATE-----
MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYxCzAJBgNVBAYT
AkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3Qg
RTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNV
BAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcq
hkjOPQIBBgUrgQQAIgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkB
jtjqR+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGddyXqBPCCj
QjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQxCpCPtsad0kRL
gLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZk
vLtoURMMA/cVi4RguYv/Uo7njLwcAjA8+RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+
CAezNIm8BZ/3Hobui3A=
-----END CERTIFICATE-----
GLOBALTRUST 2020
================
-----BEGIN CERTIFICATE-----
MIIFgjCCA2qgAwIBAgILWku9WvtPilv6ZeUwDQYJKoZIhvcNAQELBQAwTTELMAkGA1UEBhMCQVQx
IzAhBgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVT
VCAyMDIwMB4XDTIwMDIxMDAwMDAwMFoXDTQwMDYxMDAwMDAwMFowTTELMAkGA1UEBhMCQVQxIzAh
BgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVTVCAy
MDIwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAri5WrRsc7/aVj6B3GyvTY4+ETUWi
D59bRatZe1E0+eyLinjF3WuvvcTfk0Uev5E4C64OFudBc/jbu9G4UeDLgztzOG53ig9ZYybNpyrO
VPu44sB8R85gfD+yc/LAGbaKkoc1DZAoouQVBGM+uq/ufF7MpotQsjj3QWPKzv9pj2gOlTblzLmM
CcpL3TGQlsjMH/1WljTbjhzqLL6FLmPdqqmV0/0plRPwyJiT2S0WR5ARg6I6IqIoV6Lr/sCMKKCm
fecqQjuCgGOlYx8ZzHyyZqjC0203b+J+BlHZRYQfEs4kUmSFC0iAToexIiIwquuuvuAC4EDosEKA
A1GqtH6qRNdDYfOiaxaJSaSjpCuKAsR49GiKweR6NrFvG5Ybd0mN1MkGco/PU+PcF4UgStyYJ9OR
JitHHmkHr96i5OTUawuzXnzUJIBHKWk7buis/UDr2O1xcSvy6Fgd60GXIsUf1DnQJ4+H4xj04KlG
DfV0OoIu0G4skaMxXDtG6nsEEFZegB31pWXogvziB4xiRfUg3kZwhqG8k9MedKZssCz3AwyIDMvU
clOGvGBG85hqwvG/Q/lwIHfKN0F5VVJjjVsSn8VoxIidrPIwq7ejMZdnrY8XD2zHc+0klGvIg5rQ
mjdJBKuxFshsSUktq6HQjJLyQUp5ISXbY9e2nKd+Qmn7OmMCAwEAAaNjMGEwDwYDVR0TAQH/BAUw
AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFNwuH9FhN3nkq9XVsxJxaD1qaJwiMB8GA1Ud
IwQYMBaAFNwuH9FhN3nkq9XVsxJxaD1qaJwiMA0GCSqGSIb3DQEBCwUAA4ICAQCR8EICaEDuw2jA
VC/f7GLDw56KoDEoqoOOpFaWEhCGVrqXctJUMHytGdUdaG/7FELYjQ7ztdGl4wJCXtzoRlgHNQIw
4Lx0SsFDKv/bGtCwr2zD/cuz9X9tAy5ZVp0tLTWMstZDFyySCstd6IwPS3BD0IL/qMy/pJTAvoe9
iuOTe8aPmxadJ2W8esVCgmxcB9CpwYhgROmYhRZf+I/KARDOJcP5YBugxZfD0yyIMaK9MOzQ0MAS
8cE54+X1+NZK3TTN+2/BT+MAi1bikvcoskJ3ciNnxz8RFbLEAwW+uxF7Cr+obuf/WEPPm2eggAe2
HcqtbepBEX4tdJP7wry+UUTF72glJ4DjyKDUEuzZpTcdN3y0kcra1LGWge9oXHYQSa9+pTeAsRxS
vTOBTI/53WXZFM2KJVj04sWDpQmQ1GwUY7VA3+vA/MRYfg0UFodUJ25W5HCEuGwyEn6CMUO+1918
oa2u1qsgEu8KwxCMSZY13At1XrFP1U80DhEgB3VDRemjEdqso5nCtnkn4rnvyOL2NSl6dPrFf4IF
YqYK6miyeUcGbvJXqBUzxvd4Sj1Ce2t+/vdG6tHrju+IaFvowdlxfv1k7/9nR4hYJS8+hge9+6jl
gqispdNpQ80xiEmEU5LAsTkbOYMBMMTyqfrQA71yN2BWHzZ8vTmR9W0Nv3vXkg==
-----END CERTIFICATE-----
ANF Secure Server Root CA
=========================
-----BEGIN CERTIFICATE-----
MIIF7zCCA9egAwIBAgIIDdPjvGz5a7EwDQYJKoZIhvcNAQELBQAwgYQxEjAQBgNVBAUTCUc2MzI4
NzUxMDELMAkGA1UEBhMCRVMxJzAlBgNVBAoTHkFORiBBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lv
bjEUMBIGA1UECxMLQU5GIENBIFJhaXoxIjAgBgNVBAMTGUFORiBTZWN1cmUgU2VydmVyIFJvb3Qg
Q0EwHhcNMTkwOTA0MTAwMDM4WhcNMzkwODMwMTAwMDM4WjCBhDESMBAGA1UEBRMJRzYzMjg3NTEw
MQswCQYDVQQGEwJFUzEnMCUGA1UEChMeQU5GIEF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uMRQw
EgYDVQQLEwtBTkYgQ0EgUmFpejEiMCAGA1UEAxMZQU5GIFNlY3VyZSBTZXJ2ZXIgUm9vdCBDQTCC
AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANvrayvmZFSVgpCjcqQZAZ2cC4Ffc0m6p6zz
BE57lgvsEeBbphzOG9INgxwruJ4dfkUyYA8H6XdYfp9qyGFOtibBTI3/TO80sh9l2Ll49a2pcbnv
T1gdpd50IJeh7WhM3pIXS7yr/2WanvtH2Vdy8wmhrnZEE26cLUQ5vPnHO6RYPUG9tMJJo8gN0pcv
B2VSAKduyK9o7PQUlrZXH1bDOZ8rbeTzPvY1ZNoMHKGESy9LS+IsJJ1tk0DrtSOOMspvRdOoiXse
zx76W0OLzc2oD2rKDF65nkeP8Nm2CgtYZRczuSPkdxl9y0oukntPLxB3sY0vaJxizOBQ+OyRp1RM
VwnVdmPF6GUe7m1qzwmd+nxPrWAI/VaZDxUse6mAq4xhj0oHdkLePfTdsiQzW7i1o0TJrH93PB0j
7IKppuLIBkwC/qxcmZkLLxCKpvR/1Yd0DVlJRfbwcVw5Kda/SiOL9V8BY9KHcyi1Swr1+KuCLH5z
JTIdC2MKF4EA/7Z2Xue0sUDKIbvVgFHlSFJnLNJhiQcND85Cd8BEc5xEUKDbEAotlRyBr+Qc5RQe
8TZBAQIvfXOn3kLMTOmJDVb3n5HUA8ZsyY/b2BzgQJhdZpmYgG4t/wHFzstGH6wCxkPmrqKEPMVO
Hj1tyRRM4y5Bu8o5vzY8KhmqQYdOpc5LMnndkEl/AgMBAAGjYzBhMB8GA1UdIwQYMBaAFJxf0Gxj
o1+TypOYCK2Mh6UsXME3MB0GA1UdDgQWBBScX9BsY6Nfk8qTmAitjIelLFzBNzAOBgNVHQ8BAf8E
BAMCAYYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEATh65isagmD9uw2nAalxJ
UqzLK114OMHVVISfk/CHGT0sZonrDUL8zPB1hT+L9IBdeeUXZ701guLyPI59WzbLWoAAKfLOKyzx
j6ptBZNscsdW699QIyjlRRA96Gejrw5VD5AJYu9LWaL2U/HANeQvwSS9eS9OICI7/RogsKQOLHDt
dD+4E5UGUcjohybKpFtqFiGS3XNgnhAY3jyB6ugYw3yJ8otQPr0R4hUDqDZ9MwFsSBXXiJCZBMXM
5gf0vPSQ7RPi6ovDj6MzD8EpTBNO2hVWcXNyglD2mjN8orGoGjR0ZVzO0eurU+AagNjqOknkJjCb
5RyKqKkVMoaZkgoQI1YS4PbOTOK7vtuNknMBZi9iPrJyJ0U27U1W45eZ/zo1PqVUSlJZS2Db7v54
EX9K3BR5YLZrZAPbFYPhor72I5dQ8AkzNqdxliXzuUJ92zg/LFis6ELhDtjTO0wugumDLmsx2d1H
hk9tl5EuT+IocTUW0fJz/iUrB0ckYyfI+PbZa/wSMVYIwFNCr5zQM378BvAxRAMU8Vjq8moNqRGy
g77FGr8H6lnco4g175x2MjxNBiLOFeXdntiP2t7SxDnlF4HPOEfrf4htWRvfn0IUrn7PqLBmZdo3
r5+qPeoott7VMVgWglvquxl1AnMaykgaIZOQCo6ThKd9OyMYkomgjaw=
-----END CERTIFICATE-----
Certum EC-384 CA
================
-----BEGIN CERTIFICATE-----
MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQswCQYDVQQGEwJQ
TDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2Vy
dGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2
MDcyNDU0WhcNNDMwMzI2MDcyNDU0WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERh
dGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkx
GTAXBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATEKI6rGFtq
vm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7TmFy8as10CW4kjPMIRBSqn
iBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68KjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD
VR0OBBYEFI0GZnQkdjrzife81r1HfS+8EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNo
ADBlAjADVS2m5hjEfO/JUG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0
QoSZ/6vnnvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k=
-----END CERTIFICATE-----
Certum Trusted Root CA
======================
-----BEGIN CERTIFICATE-----
MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6MQswCQYDVQQG
EwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0g
Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0Ew
HhcNMTgwMzE2MTIxMDEzWhcNNDMwMzE2MTIxMDEzWjB6MQswCQYDVQQGEwJQTDEhMB8GA1UEChMY
QXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBB
dXRob3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEB
AQUAA4ICDwAwggIKAoICAQDRLY67tzbqbTeRn06TpwXkKQMlzhyC93yZn0EGze2jusDbCSzBfN8p
fktlL5On1AFrAygYo9idBcEq2EXxkd7fO9CAAozPOA/qp1x4EaTByIVcJdPTsuclzxFUl6s1wB52
HO8AU5853BSlLCIls3Jy/I2z5T4IHhQqNwuIPMqw9MjCoa68wb4pZ1Xi/K1ZXP69VyywkI3C7Te2
fJmItdUDmj0VDT06qKhF8JVOJVkdzZhpu9PMMsmN74H+rX2Ju7pgE8pllWeg8xn2A1bUatMn4qGt
g/BKEiJ3HAVz4hlxQsDsdUaakFjgao4rpUYwBI4Zshfjvqm6f1bxJAPXsiEodg42MEx51UGamqi4
NboMOvJEGyCI98Ul1z3G4z5D3Yf+xOr1Uz5MZf87Sst4WmsXXw3Hw09Omiqi7VdNIuJGmj8PkTQk
fVXjjJU30xrwCSss0smNtA0Aq2cpKNgB9RkEth2+dv5yXMSFytKAQd8FqKPVhJBPC/PgP5sZ0jeJ
P/J7UhyM9uH3PAeXjA6iWYEMspA90+NZRu0PqafegGtaqge2Gcu8V/OXIXoMsSt0Puvap2ctTMSY
njYJdmZm/Bo/6khUHL4wvYBQv3y1zgD2DGHZ5yQD4OMBgQ692IU0iL2yNqh7XAjlRICMb/gv1SHK
HRzQ+8S1h9E6Tsd2tTVItQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSM+xx1
vALTn04uSNn5YFSqxLNP+jAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQENBQADggIBAEii1QAL
LtA/vBzVtVRJHlpr9OTy4EA34MwUe7nJ+jW1dReTagVphZzNTxl4WxmB82M+w85bj/UvXgF2Ez8s
ALnNllI5SW0ETsXpD4YN4fqzX4IS8TrOZgYkNCvozMrnadyHncI013nR03e4qllY/p0m+jiGPp2K
h2RX5Rc64vmNueMzeMGQ2Ljdt4NR5MTMI9UGfOZR0800McD2RrsLrfw9EAUqO0qRJe6M1ISHgCq8
CYyqOhNf6DR5UMEQGfnTKB7U0VEwKbOukGfWHwpjscWpxkIxYxeU72nLL/qMFH3EQxiJ2fAyQOaA
4kZf5ePBAFmo+eggvIksDkc0C+pXwlM2/KfUrzHN/gLldfq5Jwn58/U7yn2fqSLLiMmq0Uc9Nneo
WWRrJ8/vJ8HjJLWG965+Mk2weWjROeiQWMODvA8s1pfrzgzhIMfatz7DP78v3DSk+yshzWePS/Tj
6tQ/50+6uaWTRRxmHyH6ZF5v4HaUMst19W7l9o/HuKTMqJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmT
OPQD8rv7gmsHINFSH5pkAnuYZttcTVoP0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZck
bxJF0WddCajJFdr60qZfE2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb
-----END CERTIFICATE-----
TunTrust Root CA
================
-----BEGIN CERTIFICATE-----
MIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQELBQAwYTELMAkG
A1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUgQ2VydGlmaWNhdGlvbiBFbGVj
dHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJvb3QgQ0EwHhcNMTkwNDI2MDg1NzU2WhcNNDQw
NDI2MDg1NzU2WjBhMQswCQYDVQQGEwJUTjE3MDUGA1UECgwuQWdlbmNlIE5hdGlvbmFsZSBkZSBD
ZXJ0aWZpY2F0aW9uIEVsZWN0cm9uaXF1ZTEZMBcGA1UEAwwQVHVuVHJ1c3QgUm9vdCBDQTCCAiIw
DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMPN0/y9BFPdDCA61YguBUtB9YOCfvdZn56eY+hz
2vYGqU8ftPkLHzmMmiDQfgbU7DTZhrx1W4eI8NLZ1KMKsmwb60ksPqxd2JQDoOw05TDENX37Jk0b
bjBU2PWARZw5rZzJJQRNmpA+TkBuimvNKWfGzC3gdOgFVwpIUPp6Q9p+7FuaDmJ2/uqdHYVy7BG7
NegfJ7/Boce7SBbdVtfMTqDhuazb1YMZGoXRlJfXyqNlC/M4+QKu3fZnz8k/9YosRxqZbwUN/dAd
gjH8KcwAWJeRTIAAHDOFli/LQcKLEITDCSSJH7UP2dl3RxiSlGBcx5kDPP73lad9UKGAwqmDrViW
VSHbhlnUr8a83YFuB9tgYv7sEG7aaAH0gxupPqJbI9dkxt/con3YS7qC0lH4Zr8GRuR5KiY2eY8f
Tpkdso8MDhz/yV3A/ZAQprE38806JG60hZC/gLkMjNWb1sjxVj8agIl6qeIbMlEsPvLfe/ZdeikZ
juXIvTZxi11Mwh0/rViizz1wTaZQmCXcI/m4WEEIcb9PuISgjwBUFfyRbVinljvrS5YnzWuioYas
DXxU5mZMZl+QviGaAkYt5IPCgLnPSz7ofzwB7I9ezX/SKEIBlYrilz0QIX32nRzFNKHsLA4KUiwS
VXAkPcvCFDVDXSdOvsC9qnyW5/yeYa1E0wCXAgMBAAGjYzBhMB0GA1UdDgQWBBQGmpsfU33x9aTI
04Y+oXNZtPdEITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFAaamx9TffH1pMjThj6hc1m0
90QhMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAqgVutt0Vyb+zxiD2BkewhpMl
0425yAA/l/VSJ4hxyXT968pk21vvHl26v9Hr7lxpuhbI87mP0zYuQEkHDVneixCwSQXi/5E/S7fd
Ao74gShczNxtr18UnH1YeA32gAm56Q6XKRm4t+v4FstVEuTGfbvE7Pi1HE4+Z7/FXxttbUcoqgRY
YdZ2vyJ/0Adqp2RT8JeNnYA/u8EH22Wv5psymsNUk8QcCMNE+3tjEUPRahphanltkE8pjkcFwRJp
adbGNjHh/PqAulxPxOu3Mqz4dWEX1xAZufHSCe96Qp1bWgvUxpVOKs7/B9dPfhgGiPEZtdmYu65x
xBzndFlY7wyJz4sfdZMaBBSSSFCp61cpABbjNhzI+L/wM9VBD8TMPN3pM0MBkRArHtG5Xc0yGYuP
jCB31yLEQtyEFpslbei0VXF/sHyz03FJuc9SpAQ/3D2gu68zngowYI7bnV2UqL1g52KAdoGDDIzM
MEZJ4gzSqK/rYXHv5yJiqfdcZGyfFoxnNidF9Ql7v/YQCvGwjVRDjAS6oz/v4jXH+XTgbzRB0L9z
ZVcg+ZtnemZoJE6AZb0QmQZZ8mWvuMZHu/2QeItBcy6vVR/cO5JyboTT0GFMDcx2V+IthSIVNg3r
AZ3r2OvEhJn7wAzMMujjd9qDRIueVSjAi1jTkD5OGwDxFa2DK5o=
-----END CERTIFICATE-----
HARICA TLS RSA Root CA 2021
===========================
-----BEGIN CERTIFICATE-----
MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQG
EwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u
cyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0EgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUz
OFoXDTQ1MDIxMzEwNTUzN1owbDELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRl
bWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNB
IFJvb3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569lmwVnlskN
JLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE4VGC/6zStGndLuwRo0Xu
a2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uva9of08WRiFukiZLRgeaMOVig1mlDqa2Y
Ulhu2wr7a89o+uOkXjpFc5gH6l8Cct4MpbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K
5FrZx40d/JiZ+yykgmvwKh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEv
dmn8kN3bLW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcYAuUR
0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqBAGMUuTNe3QvboEUH
GjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYqE613TBoYm5EPWNgGVMWX+Ko/IIqm
haZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHrW2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQ
CPxrvrNQKlr9qEgYRtaQQJKQCoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8G
A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE
AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAUX15QvWiWkKQU
EapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3f5Z2EMVGpdAgS1D0NTsY9FVq
QRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxajaH6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxD
QpSbIPDRzbLrLFPCU3hKTwSUQZqPJzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcR
j88YxeMn/ibvBZ3PzzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5
vZStjBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0/L5H9MG0
qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pTBGIBnfHAT+7hOtSLIBD6
Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79aPib8qXPMThcFarmlwDB31qlpzmq6YR/
PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YWxw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnn
kf3/W9b3raYvAwtt41dU63ZTGI0RmLo=
-----END CERTIFICATE-----
HARICA TLS ECC Root CA 2021
===========================
-----BEGIN CERTIFICATE-----
MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQswCQYDVQQGEwJH
UjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBD
QTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoX
DTQ1MDIxMzExMDEwOVowbDELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWlj
IGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJv
b3QgQ0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7KKrxcm1l
AEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9YSTHMmE5gEYd103KUkE+b
ECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW
0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAi
rcJRQO9gcS3ujwLEXQNwSaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/Qw
CZ61IygNnxS2PFOiTAZpffpskcYqSUXm7LcT4Tps
-----END CERTIFICATE-----
HARICA Client RSA Root CA 2021
==============================
-----BEGIN CERTIFICATE-----
MIIFqjCCA5KgAwIBAgIQVVL4HtsbJCyeu5YYzQIoPjANBgkqhkiG9w0BAQsFADBvMQswCQYDVQQG
EwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u
cyBDQTEnMCUGA1UEAwweSEFSSUNBIENsaWVudCBSU0EgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTEw
NTg0NloXDTQ1MDIxMzEwNTg0NVowbzELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFj
YWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJzAlBgNVBAMMHkhBUklDQSBDbGll
bnQgUlNBIFJvb3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIHbV0KQ
LHQ19Pi4dBlNqwlad0WBc2KwNZ/40LczAIcTtparDlQSMAe8m7dI19EZg66O2KnxqQCEsIxenugM
j1Rpv/bUCE8mcP4YQWMaszKLQPgHq1cx8MYWdmeatN0v8tFrxdCShJFxbg8uY+kfU6TdUhPMCYMp
gQzFU3VEsQ5nUxjQwx+IS5+UJLQpvLvoTv1v0hUdSdyNcPIRGiBRVRG6iG/E91B51qox4oQ9XjLI
dypQceULL+m26u+rCjM5Dv2PpWdDgo6YaQkJG0DNOGdH6snsl3ES3iT1cjzR90NMJveQsonpRUtV
PTEFekHilbpDwBfFtoU9GY1kcPNbrM2f0yl1h0uVZ2qm+NHdvJCGiUMpqTdb9V2wJlpTQnaQK8+e
VmwrVM9cmmXfW4tIYDh8+8ULz3YEYwIzKn31g2fn+sZD/SsP1CYvd6QywSTqZJ2/szhxMUTyR7ii
ZkGh+5t7vMdGanW/WqKM6GpEwbiWtcAyCC17dDVzssrG/q8Rchj258jCz6Uq6nvWWeh8oLJqQAlp
DqWW29EAufGIbjbwiLKd8VLyw3y/MIk8Cmn5IqRl4ZvgdMaxhZeWLK6Uj1CmORIfvkfygXjTdTae
fVogl+JSrpmfxnybZvP+2M/uvZcGHS2F3D42U5Z7ILroyOGtlmI+EXyzAISep0xxq0o3AgMBAAGj
QjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFKDWBz1eJPd7oEQuJFINGaorBJGnMA4GA1Ud
DwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEADUf5CWYxUux57sKo8mg+7ZZFyzqmmGM/6itN
TgPQHILhy9Pl1qtbZyi8nf4MmQqAVafOGyNhDbBX8P7gyr7mkNuDLL6DjvR5tv7QDUKnWB9p6oH1
BaX+RmjrbHjJ4Orn5t4xxdLVLIJjKJ1dqBp+iObnK/Es1dAFntwtvTdm1ASip62/OsKoO63/jZ0z
4LmahKGHH3b0gnTXDvkwSD5biD6qXGvWLwzojnPCGJGDObZmWtAfYCddTeP2Og1mUJx4e6vzExCu
Dy+r6GSzGCCdRjVkJXPqmxBcWDWJsUZIp/Ss1B2eW8yppRoTTyRQqtkbbbFA+53dWHTEwm8Ucuzb
NZ+4VHVFw6bIGig1Oq5l8qmYzq9byTiMMTt/zNyW/eJb1tBZ9Ha6C8tPgxDHQNAdYOkq5UhYdwxF
ab4ZcQQk4uMkH0rIwT6Z9ZaYOEgloRWwG9fihBhb9nE1mmh7QMwYXAwkndSV9ZmqRuqurL/0FBkk
6Izs4/W8BmiKKgwFXwqXdafcfsD913oY3zDROEsfsJhwv8x8c/BuxDGlpJcdrL/ObCFKvicjZ/MG
VoEKkY624QMFMyzaNAhNTlAjrR+lxdR6/uoJ7KcoYItGfLXqm91P+edrFcaIz0Pb5SfcBFZub0YV
8VYt6FwMc8MjgTggy8kMac8sqzuEYDMZUv1pFDM=
-----END CERTIFICATE-----
HARICA Client ECC Root CA 2021
==============================
-----BEGIN CERTIFICATE-----
MIICWjCCAeGgAwIBAgIQMWjZ2OFiVx7SGUSI5hB98DAKBggqhkjOPQQDAzBvMQswCQYDVQQGEwJH
UjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBD
QTEnMCUGA1UEAwweSEFSSUNBIENsaWVudCBFQ0MgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTExMDMz
NFoXDTQ1MDIxMzExMDMzM1owbzELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRl
bWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJzAlBgNVBAMMHkhBUklDQSBDbGllbnQg
RUNDIFJvb3QgQ0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABAcYrZWWlNBcD4L3KkD6AsnJ
PTamowRqwW2VAYhgElRsXKIrbhM6iJUMHCaGNkqJGbcY3jvoqFAfyt9bv0mAFdvjMOEdWscqigEH
/m0sNO8oKJe8wflXhpWLNc+eWtFolaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUUgjS
vjKBJf31GpfsTl8au1PNkK0wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2cAMGQCMEwxRUZP
qOa+w3eyGhhLLYh7WOarlGtEA7AX/9+Cc0RRLP2THQZ7FNKJ7EAM7yEBLgIwL8kuWmwsHdmV4J6w
uVxSfPb4OMou8dQd8qJJopX4wVheT/5zCu8xsKsjWBOMi947
-----END CERTIFICATE-----
proftpd-mod_proxy-0.9.5/config.guess 0000775 0000000 0000000 00000141422 14757370167 0017566 0 ustar 00root root 0000000 0000000 #! /bin/sh
# Attempt to guess a canonical system name.
# Copyright 1992-2023 Free Software Foundation, Inc.
# shellcheck disable=SC2006,SC2268 # see below for rationale
timestamp='2023-01-01'
# This file is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see .
#
# As a special exception to the GNU General Public License, if you
# distribute this file as part of a program that contains a
# configuration script generated by Autoconf, you may include it under
# the same distribution terms that you use for the rest of that
# program. This Exception is an additional permission under section 7
# of the GNU General Public License, version 3 ("GPLv3").
#
# Originally written by Per Bothner; maintained since 2000 by Ben Elliston.
#
# You can get the latest version of this script from:
# https://git.savannah.gnu.org/cgit/config.git/plain/config.guess
#
# Please send patches to .
# The "shellcheck disable" line above the timestamp inhibits complaints
# about features and limitations of the classic Bourne shell that were
# superseded or lifted in POSIX. However, this script identifies a wide
# variety of pre-POSIX systems that do not have POSIX shells at all, and
# even some reasonably current systems (Solaris 10 as case-in-point) still
# have a pre-POSIX /bin/sh.
me=`echo "$0" | sed -e 's,.*/,,'`
usage="\
Usage: $0 [OPTION]
Output the configuration name of the system \`$me' is run on.
Options:
-h, --help print this help, then exit
-t, --time-stamp print date of last modification, then exit
-v, --version print version number, then exit
Report bugs and patches to ."
version="\
GNU config.guess ($timestamp)
Originally written by Per Bothner.
Copyright 1992-2023 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
help="
Try \`$me --help' for more information."
# Parse command line
while test $# -gt 0 ; do
case $1 in
--time-stamp | --time* | -t )
echo "$timestamp" ; exit ;;
--version | -v )
echo "$version" ; exit ;;
--help | --h* | -h )
echo "$usage"; exit ;;
-- ) # Stop option processing
shift; break ;;
- ) # Use stdin as input.
break ;;
-* )
echo "$me: invalid option $1$help" >&2
exit 1 ;;
* )
break ;;
esac
done
if test $# != 0; then
echo "$me: too many arguments$help" >&2
exit 1
fi
# Just in case it came from the environment.
GUESS=
# CC_FOR_BUILD -- compiler used by this script. Note that the use of a
# compiler to aid in system detection is discouraged as it requires
# temporary files to be created and, as you can see below, it is a
# headache to deal with in a portable fashion.
# Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still
# use `HOST_CC' if defined, but it is deprecated.
# Portable tmp directory creation inspired by the Autoconf team.
tmp=
# shellcheck disable=SC2172
trap 'test -z "$tmp" || rm -fr "$tmp"' 0 1 2 13 15
set_cc_for_build() {
# prevent multiple calls if $tmp is already set
test "$tmp" && return 0
: "${TMPDIR=/tmp}"
# shellcheck disable=SC2039,SC3028
{ tmp=`(umask 077 && mktemp -d "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } ||
{ test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir "$tmp" 2>/dev/null) ; } ||
{ tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir "$tmp" 2>/dev/null) && echo "Warning: creating insecure temp directory" >&2 ; } ||
{ echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; }
dummy=$tmp/dummy
case ${CC_FOR_BUILD-},${HOST_CC-},${CC-} in
,,) echo "int x;" > "$dummy.c"
for driver in cc gcc c89 c99 ; do
if ($driver -c -o "$dummy.o" "$dummy.c") >/dev/null 2>&1 ; then
CC_FOR_BUILD=$driver
break
fi
done
if test x"$CC_FOR_BUILD" = x ; then
CC_FOR_BUILD=no_compiler_found
fi
;;
,,*) CC_FOR_BUILD=$CC ;;
,*,*) CC_FOR_BUILD=$HOST_CC ;;
esac
}
# This is needed to find uname on a Pyramid OSx when run in the BSD universe.
# (ghazi@noc.rutgers.edu 1994-08-24)
if test -f /.attbin/uname ; then
PATH=$PATH:/.attbin ; export PATH
fi
UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown
UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown
UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown
UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown
case $UNAME_SYSTEM in
Linux|GNU|GNU/*)
LIBC=unknown
set_cc_for_build
cat <<-EOF > "$dummy.c"
#include
#if defined(__UCLIBC__)
LIBC=uclibc
#elif defined(__dietlibc__)
LIBC=dietlibc
#elif defined(__GLIBC__)
LIBC=gnu
#else
#include
/* First heuristic to detect musl libc. */
#ifdef __DEFINED_va_list
LIBC=musl
#endif
#endif
EOF
cc_set_libc=`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^LIBC' | sed 's, ,,g'`
eval "$cc_set_libc"
# Second heuristic to detect musl libc.
if [ "$LIBC" = unknown ] &&
command -v ldd >/dev/null &&
ldd --version 2>&1 | grep -q ^musl; then
LIBC=musl
fi
# If the system lacks a compiler, then just pick glibc.
# We could probably try harder.
if [ "$LIBC" = unknown ]; then
LIBC=gnu
fi
;;
esac
# Note: order is significant - the case branches are not exclusive.
case $UNAME_MACHINE:$UNAME_SYSTEM:$UNAME_RELEASE:$UNAME_VERSION in
*:NetBSD:*:*)
# NetBSD (nbsd) targets should (where applicable) match one or
# more of the tuples: *-*-netbsdelf*, *-*-netbsdaout*,
# *-*-netbsdecoff* and *-*-netbsd*. For targets that recently
# switched to ELF, *-*-netbsd* would select the old
# object file format. This provides both forward
# compatibility and a consistent mechanism for selecting the
# object file format.
#
# Note: NetBSD doesn't particularly care about the vendor
# portion of the name. We always set it to "unknown".
UNAME_MACHINE_ARCH=`(uname -p 2>/dev/null || \
/sbin/sysctl -n hw.machine_arch 2>/dev/null || \
/usr/sbin/sysctl -n hw.machine_arch 2>/dev/null || \
echo unknown)`
case $UNAME_MACHINE_ARCH in
aarch64eb) machine=aarch64_be-unknown ;;
armeb) machine=armeb-unknown ;;
arm*) machine=arm-unknown ;;
sh3el) machine=shl-unknown ;;
sh3eb) machine=sh-unknown ;;
sh5el) machine=sh5le-unknown ;;
earmv*)
arch=`echo "$UNAME_MACHINE_ARCH" | sed -e 's,^e\(armv[0-9]\).*$,\1,'`
endian=`echo "$UNAME_MACHINE_ARCH" | sed -ne 's,^.*\(eb\)$,\1,p'`
machine=${arch}${endian}-unknown
;;
*) machine=$UNAME_MACHINE_ARCH-unknown ;;
esac
# The Operating System including object format, if it has switched
# to ELF recently (or will in the future) and ABI.
case $UNAME_MACHINE_ARCH in
earm*)
os=netbsdelf
;;
arm*|i386|m68k|ns32k|sh3*|sparc|vax)
set_cc_for_build
if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \
| grep -q __ELF__
then
# Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout).
# Return netbsd for either. FIX?
os=netbsd
else
os=netbsdelf
fi
;;
*)
os=netbsd
;;
esac
# Determine ABI tags.
case $UNAME_MACHINE_ARCH in
earm*)
expr='s/^earmv[0-9]/-eabi/;s/eb$//'
abi=`echo "$UNAME_MACHINE_ARCH" | sed -e "$expr"`
;;
esac
# The OS release
# Debian GNU/NetBSD machines have a different userland, and
# thus, need a distinct triplet. However, they do not need
# kernel version information, so it can be replaced with a
# suitable tag, in the style of linux-gnu.
case $UNAME_VERSION in
Debian*)
release='-gnu'
;;
*)
release=`echo "$UNAME_RELEASE" | sed -e 's/[-_].*//' | cut -d. -f1,2`
;;
esac
# Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM:
# contains redundant information, the shorter form:
# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used.
GUESS=$machine-${os}${release}${abi-}
;;
*:Bitrig:*:*)
UNAME_MACHINE_ARCH=`arch | sed 's/Bitrig.//'`
GUESS=$UNAME_MACHINE_ARCH-unknown-bitrig$UNAME_RELEASE
;;
*:OpenBSD:*:*)
UNAME_MACHINE_ARCH=`arch | sed 's/OpenBSD.//'`
GUESS=$UNAME_MACHINE_ARCH-unknown-openbsd$UNAME_RELEASE
;;
*:SecBSD:*:*)
UNAME_MACHINE_ARCH=`arch | sed 's/SecBSD.//'`
GUESS=$UNAME_MACHINE_ARCH-unknown-secbsd$UNAME_RELEASE
;;
*:LibertyBSD:*:*)
UNAME_MACHINE_ARCH=`arch | sed 's/^.*BSD\.//'`
GUESS=$UNAME_MACHINE_ARCH-unknown-libertybsd$UNAME_RELEASE
;;
*:MidnightBSD:*:*)
GUESS=$UNAME_MACHINE-unknown-midnightbsd$UNAME_RELEASE
;;
*:ekkoBSD:*:*)
GUESS=$UNAME_MACHINE-unknown-ekkobsd$UNAME_RELEASE
;;
*:SolidBSD:*:*)
GUESS=$UNAME_MACHINE-unknown-solidbsd$UNAME_RELEASE
;;
*:OS108:*:*)
GUESS=$UNAME_MACHINE-unknown-os108_$UNAME_RELEASE
;;
macppc:MirBSD:*:*)
GUESS=powerpc-unknown-mirbsd$UNAME_RELEASE
;;
*:MirBSD:*:*)
GUESS=$UNAME_MACHINE-unknown-mirbsd$UNAME_RELEASE
;;
*:Sortix:*:*)
GUESS=$UNAME_MACHINE-unknown-sortix
;;
*:Twizzler:*:*)
GUESS=$UNAME_MACHINE-unknown-twizzler
;;
*:Redox:*:*)
GUESS=$UNAME_MACHINE-unknown-redox
;;
mips:OSF1:*.*)
GUESS=mips-dec-osf1
;;
alpha:OSF1:*:*)
# Reset EXIT trap before exiting to avoid spurious non-zero exit code.
trap '' 0
case $UNAME_RELEASE in
*4.0)
UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'`
;;
*5.*)
UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'`
;;
esac
# According to Compaq, /usr/sbin/psrinfo has been available on
# OSF/1 and Tru64 systems produced since 1995. I hope that
# covers most systems running today. This code pipes the CPU
# types through head -n 1, so we only detect the type of CPU 0.
ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1`
case $ALPHA_CPU_TYPE in
"EV4 (21064)")
UNAME_MACHINE=alpha ;;
"EV4.5 (21064)")
UNAME_MACHINE=alpha ;;
"LCA4 (21066/21068)")
UNAME_MACHINE=alpha ;;
"EV5 (21164)")
UNAME_MACHINE=alphaev5 ;;
"EV5.6 (21164A)")
UNAME_MACHINE=alphaev56 ;;
"EV5.6 (21164PC)")
UNAME_MACHINE=alphapca56 ;;
"EV5.7 (21164PC)")
UNAME_MACHINE=alphapca57 ;;
"EV6 (21264)")
UNAME_MACHINE=alphaev6 ;;
"EV6.7 (21264A)")
UNAME_MACHINE=alphaev67 ;;
"EV6.8CB (21264C)")
UNAME_MACHINE=alphaev68 ;;
"EV6.8AL (21264B)")
UNAME_MACHINE=alphaev68 ;;
"EV6.8CX (21264D)")
UNAME_MACHINE=alphaev68 ;;
"EV6.9A (21264/EV69A)")
UNAME_MACHINE=alphaev69 ;;
"EV7 (21364)")
UNAME_MACHINE=alphaev7 ;;
"EV7.9 (21364A)")
UNAME_MACHINE=alphaev79 ;;
esac
# A Pn.n version is a patched version.
# A Vn.n version is a released version.
# A Tn.n version is a released field test version.
# A Xn.n version is an unreleased experimental baselevel.
# 1.2 uses "1.2" for uname -r.
OSF_REL=`echo "$UNAME_RELEASE" | sed -e 's/^[PVTX]//' | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz`
GUESS=$UNAME_MACHINE-dec-osf$OSF_REL
;;
Amiga*:UNIX_System_V:4.0:*)
GUESS=m68k-unknown-sysv4
;;
*:[Aa]miga[Oo][Ss]:*:*)
GUESS=$UNAME_MACHINE-unknown-amigaos
;;
*:[Mm]orph[Oo][Ss]:*:*)
GUESS=$UNAME_MACHINE-unknown-morphos
;;
*:OS/390:*:*)
GUESS=i370-ibm-openedition
;;
*:z/VM:*:*)
GUESS=s390-ibm-zvmoe
;;
*:OS400:*:*)
GUESS=powerpc-ibm-os400
;;
arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*)
GUESS=arm-acorn-riscix$UNAME_RELEASE
;;
arm*:riscos:*:*|arm*:RISCOS:*:*)
GUESS=arm-unknown-riscos
;;
SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*)
GUESS=hppa1.1-hitachi-hiuxmpp
;;
Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*)
# akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE.
case `(/bin/universe) 2>/dev/null` in
att) GUESS=pyramid-pyramid-sysv3 ;;
*) GUESS=pyramid-pyramid-bsd ;;
esac
;;
NILE*:*:*:dcosx)
GUESS=pyramid-pyramid-svr4
;;
DRS?6000:unix:4.0:6*)
GUESS=sparc-icl-nx6
;;
DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*)
case `/usr/bin/uname -p` in
sparc) GUESS=sparc-icl-nx7 ;;
esac
;;
s390x:SunOS:*:*)
SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'`
GUESS=$UNAME_MACHINE-ibm-solaris2$SUN_REL
;;
sun4H:SunOS:5.*:*)
SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'`
GUESS=sparc-hal-solaris2$SUN_REL
;;
sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*)
SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'`
GUESS=sparc-sun-solaris2$SUN_REL
;;
i86pc:AuroraUX:5.*:* | i86xen:AuroraUX:5.*:*)
GUESS=i386-pc-auroraux$UNAME_RELEASE
;;
i86pc:SunOS:5.*:* | i86xen:SunOS:5.*:*)
set_cc_for_build
SUN_ARCH=i386
# If there is a compiler, see if it is configured for 64-bit objects.
# Note that the Sun cc does not turn __LP64__ into 1 like gcc does.
# This test works for both compilers.
if test "$CC_FOR_BUILD" != no_compiler_found; then
if (echo '#ifdef __amd64'; echo IS_64BIT_ARCH; echo '#endif') | \
(CCOPTS="" $CC_FOR_BUILD -m64 -E - 2>/dev/null) | \
grep IS_64BIT_ARCH >/dev/null
then
SUN_ARCH=x86_64
fi
fi
SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'`
GUESS=$SUN_ARCH-pc-solaris2$SUN_REL
;;
sun4*:SunOS:6*:*)
# According to config.sub, this is the proper way to canonicalize
# SunOS6. Hard to guess exactly what SunOS6 will be like, but
# it's likely to be more like Solaris than SunOS4.
SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'`
GUESS=sparc-sun-solaris3$SUN_REL
;;
sun4*:SunOS:*:*)
case `/usr/bin/arch -k` in
Series*|S4*)
UNAME_RELEASE=`uname -v`
;;
esac
# Japanese Language versions have a version number like `4.1.3-JL'.
SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/-/_/'`
GUESS=sparc-sun-sunos$SUN_REL
;;
sun3*:SunOS:*:*)
GUESS=m68k-sun-sunos$UNAME_RELEASE
;;
sun*:*:4.2BSD:*)
UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null`
test "x$UNAME_RELEASE" = x && UNAME_RELEASE=3
case `/bin/arch` in
sun3)
GUESS=m68k-sun-sunos$UNAME_RELEASE
;;
sun4)
GUESS=sparc-sun-sunos$UNAME_RELEASE
;;
esac
;;
aushp:SunOS:*:*)
GUESS=sparc-auspex-sunos$UNAME_RELEASE
;;
# The situation for MiNT is a little confusing. The machine name
# can be virtually everything (everything which is not
# "atarist" or "atariste" at least should have a processor
# > m68000). The system name ranges from "MiNT" over "FreeMiNT"
# to the lowercase version "mint" (or "freemint"). Finally
# the system name "TOS" denotes a system which is actually not
# MiNT. But MiNT is downward compatible to TOS, so this should
# be no problem.
atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*)
GUESS=m68k-atari-mint$UNAME_RELEASE
;;
atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*)
GUESS=m68k-atari-mint$UNAME_RELEASE
;;
*falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*)
GUESS=m68k-atari-mint$UNAME_RELEASE
;;
milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*)
GUESS=m68k-milan-mint$UNAME_RELEASE
;;
hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*)
GUESS=m68k-hades-mint$UNAME_RELEASE
;;
*:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*)
GUESS=m68k-unknown-mint$UNAME_RELEASE
;;
m68k:machten:*:*)
GUESS=m68k-apple-machten$UNAME_RELEASE
;;
powerpc:machten:*:*)
GUESS=powerpc-apple-machten$UNAME_RELEASE
;;
RISC*:Mach:*:*)
GUESS=mips-dec-mach_bsd4.3
;;
RISC*:ULTRIX:*:*)
GUESS=mips-dec-ultrix$UNAME_RELEASE
;;
VAX*:ULTRIX*:*:*)
GUESS=vax-dec-ultrix$UNAME_RELEASE
;;
2020:CLIX:*:* | 2430:CLIX:*:*)
GUESS=clipper-intergraph-clix$UNAME_RELEASE
;;
mips:*:*:UMIPS | mips:*:*:RISCos)
set_cc_for_build
sed 's/^ //' << EOF > "$dummy.c"
#ifdef __cplusplus
#include /* for printf() prototype */
int main (int argc, char *argv[]) {
#else
int main (argc, argv) int argc; char *argv[]; {
#endif
#if defined (host_mips) && defined (MIPSEB)
#if defined (SYSTYPE_SYSV)
printf ("mips-mips-riscos%ssysv\\n", argv[1]); exit (0);
#endif
#if defined (SYSTYPE_SVR4)
printf ("mips-mips-riscos%ssvr4\\n", argv[1]); exit (0);
#endif
#if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD)
printf ("mips-mips-riscos%sbsd\\n", argv[1]); exit (0);
#endif
#endif
exit (-1);
}
EOF
$CC_FOR_BUILD -o "$dummy" "$dummy.c" &&
dummyarg=`echo "$UNAME_RELEASE" | sed -n 's/\([0-9]*\).*/\1/p'` &&
SYSTEM_NAME=`"$dummy" "$dummyarg"` &&
{ echo "$SYSTEM_NAME"; exit; }
GUESS=mips-mips-riscos$UNAME_RELEASE
;;
Motorola:PowerMAX_OS:*:*)
GUESS=powerpc-motorola-powermax
;;
Motorola:*:4.3:PL8-*)
GUESS=powerpc-harris-powermax
;;
Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*)
GUESS=powerpc-harris-powermax
;;
Night_Hawk:Power_UNIX:*:*)
GUESS=powerpc-harris-powerunix
;;
m88k:CX/UX:7*:*)
GUESS=m88k-harris-cxux7
;;
m88k:*:4*:R4*)
GUESS=m88k-motorola-sysv4
;;
m88k:*:3*:R3*)
GUESS=m88k-motorola-sysv3
;;
AViiON:dgux:*:*)
# DG/UX returns AViiON for all architectures
UNAME_PROCESSOR=`/usr/bin/uname -p`
if test "$UNAME_PROCESSOR" = mc88100 || test "$UNAME_PROCESSOR" = mc88110
then
if test "$TARGET_BINARY_INTERFACE"x = m88kdguxelfx || \
test "$TARGET_BINARY_INTERFACE"x = x
then
GUESS=m88k-dg-dgux$UNAME_RELEASE
else
GUESS=m88k-dg-dguxbcs$UNAME_RELEASE
fi
else
GUESS=i586-dg-dgux$UNAME_RELEASE
fi
;;
M88*:DolphinOS:*:*) # DolphinOS (SVR3)
GUESS=m88k-dolphin-sysv3
;;
M88*:*:R3*:*)
# Delta 88k system running SVR3
GUESS=m88k-motorola-sysv3
;;
XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3)
GUESS=m88k-tektronix-sysv3
;;
Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD)
GUESS=m68k-tektronix-bsd
;;
*:IRIX*:*:*)
IRIX_REL=`echo "$UNAME_RELEASE" | sed -e 's/-/_/g'`
GUESS=mips-sgi-irix$IRIX_REL
;;
????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX.
GUESS=romp-ibm-aix # uname -m gives an 8 hex-code CPU id
;; # Note that: echo "'`uname -s`'" gives 'AIX '
i*86:AIX:*:*)
GUESS=i386-ibm-aix
;;
ia64:AIX:*:*)
if test -x /usr/bin/oslevel ; then
IBM_REV=`/usr/bin/oslevel`
else
IBM_REV=$UNAME_VERSION.$UNAME_RELEASE
fi
GUESS=$UNAME_MACHINE-ibm-aix$IBM_REV
;;
*:AIX:2:3)
if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then
set_cc_for_build
sed 's/^ //' << EOF > "$dummy.c"
#include
main()
{
if (!__power_pc())
exit(1);
puts("powerpc-ibm-aix3.2.5");
exit(0);
}
EOF
if $CC_FOR_BUILD -o "$dummy" "$dummy.c" && SYSTEM_NAME=`"$dummy"`
then
GUESS=$SYSTEM_NAME
else
GUESS=rs6000-ibm-aix3.2.5
fi
elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then
GUESS=rs6000-ibm-aix3.2.4
else
GUESS=rs6000-ibm-aix3.2
fi
;;
*:AIX:*:[4567])
IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'`
if /usr/sbin/lsattr -El "$IBM_CPU_ID" | grep ' POWER' >/dev/null 2>&1; then
IBM_ARCH=rs6000
else
IBM_ARCH=powerpc
fi
if test -x /usr/bin/lslpp ; then
IBM_REV=`/usr/bin/lslpp -Lqc bos.rte.libc | \
awk -F: '{ print $3 }' | sed s/[0-9]*$/0/`
else
IBM_REV=$UNAME_VERSION.$UNAME_RELEASE
fi
GUESS=$IBM_ARCH-ibm-aix$IBM_REV
;;
*:AIX:*:*)
GUESS=rs6000-ibm-aix
;;
ibmrt:4.4BSD:*|romp-ibm:4.4BSD:*)
GUESS=romp-ibm-bsd4.4
;;
ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and
GUESS=romp-ibm-bsd$UNAME_RELEASE # 4.3 with uname added to
;; # report: romp-ibm BSD 4.3
*:BOSX:*:*)
GUESS=rs6000-bull-bosx
;;
DPX/2?00:B.O.S.:*:*)
GUESS=m68k-bull-sysv3
;;
9000/[34]??:4.3bsd:1.*:*)
GUESS=m68k-hp-bsd
;;
hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*)
GUESS=m68k-hp-bsd4.4
;;
9000/[34678]??:HP-UX:*:*)
HPUX_REV=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*.[0B]*//'`
case $UNAME_MACHINE in
9000/31?) HP_ARCH=m68000 ;;
9000/[34]??) HP_ARCH=m68k ;;
9000/[678][0-9][0-9])
if test -x /usr/bin/getconf; then
sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null`
sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null`
case $sc_cpu_version in
523) HP_ARCH=hppa1.0 ;; # CPU_PA_RISC1_0
528) HP_ARCH=hppa1.1 ;; # CPU_PA_RISC1_1
532) # CPU_PA_RISC2_0
case $sc_kernel_bits in
32) HP_ARCH=hppa2.0n ;;
64) HP_ARCH=hppa2.0w ;;
'') HP_ARCH=hppa2.0 ;; # HP-UX 10.20
esac ;;
esac
fi
if test "$HP_ARCH" = ""; then
set_cc_for_build
sed 's/^ //' << EOF > "$dummy.c"
#define _HPUX_SOURCE
#include
#include
int main ()
{
#if defined(_SC_KERNEL_BITS)
long bits = sysconf(_SC_KERNEL_BITS);
#endif
long cpu = sysconf (_SC_CPU_VERSION);
switch (cpu)
{
case CPU_PA_RISC1_0: puts ("hppa1.0"); break;
case CPU_PA_RISC1_1: puts ("hppa1.1"); break;
case CPU_PA_RISC2_0:
#if defined(_SC_KERNEL_BITS)
switch (bits)
{
case 64: puts ("hppa2.0w"); break;
case 32: puts ("hppa2.0n"); break;
default: puts ("hppa2.0"); break;
} break;
#else /* !defined(_SC_KERNEL_BITS) */
puts ("hppa2.0"); break;
#endif
default: puts ("hppa1.0"); break;
}
exit (0);
}
EOF
(CCOPTS="" $CC_FOR_BUILD -o "$dummy" "$dummy.c" 2>/dev/null) && HP_ARCH=`"$dummy"`
test -z "$HP_ARCH" && HP_ARCH=hppa
fi ;;
esac
if test "$HP_ARCH" = hppa2.0w
then
set_cc_for_build
# hppa2.0w-hp-hpux* has a 64-bit kernel and a compiler generating
# 32-bit code. hppa64-hp-hpux* has the same kernel and a compiler
# generating 64-bit code. GNU and HP use different nomenclature:
#
# $ CC_FOR_BUILD=cc ./config.guess
# => hppa2.0w-hp-hpux11.23
# $ CC_FOR_BUILD="cc +DA2.0w" ./config.guess
# => hppa64-hp-hpux11.23
if echo __LP64__ | (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) |
grep -q __LP64__
then
HP_ARCH=hppa2.0w
else
HP_ARCH=hppa64
fi
fi
GUESS=$HP_ARCH-hp-hpux$HPUX_REV
;;
ia64:HP-UX:*:*)
HPUX_REV=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*.[0B]*//'`
GUESS=ia64-hp-hpux$HPUX_REV
;;
3050*:HI-UX:*:*)
set_cc_for_build
sed 's/^ //' << EOF > "$dummy.c"
#include
int
main ()
{
long cpu = sysconf (_SC_CPU_VERSION);
/* The order matters, because CPU_IS_HP_MC68K erroneously returns
true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct
results, however. */
if (CPU_IS_PA_RISC (cpu))
{
switch (cpu)
{
case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break;
case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break;
case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break;
default: puts ("hppa-hitachi-hiuxwe2"); break;
}
}
else if (CPU_IS_HP_MC68K (cpu))
puts ("m68k-hitachi-hiuxwe2");
else puts ("unknown-hitachi-hiuxwe2");
exit (0);
}
EOF
$CC_FOR_BUILD -o "$dummy" "$dummy.c" && SYSTEM_NAME=`"$dummy"` &&
{ echo "$SYSTEM_NAME"; exit; }
GUESS=unknown-hitachi-hiuxwe2
;;
9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:*)
GUESS=hppa1.1-hp-bsd
;;
9000/8??:4.3bsd:*:*)
GUESS=hppa1.0-hp-bsd
;;
*9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*)
GUESS=hppa1.0-hp-mpeix
;;
hp7??:OSF1:*:* | hp8?[79]:OSF1:*:*)
GUESS=hppa1.1-hp-osf
;;
hp8??:OSF1:*:*)
GUESS=hppa1.0-hp-osf
;;
i*86:OSF1:*:*)
if test -x /usr/sbin/sysversion ; then
GUESS=$UNAME_MACHINE-unknown-osf1mk
else
GUESS=$UNAME_MACHINE-unknown-osf1
fi
;;
parisc*:Lites*:*:*)
GUESS=hppa1.1-hp-lites
;;
C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*)
GUESS=c1-convex-bsd
;;
C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*)
if getsysinfo -f scalar_acc
then echo c32-convex-bsd
else echo c2-convex-bsd
fi
exit ;;
C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*)
GUESS=c34-convex-bsd
;;
C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*)
GUESS=c38-convex-bsd
;;
C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*)
GUESS=c4-convex-bsd
;;
CRAY*Y-MP:*:*:*)
CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'`
GUESS=ymp-cray-unicos$CRAY_REL
;;
CRAY*[A-Z]90:*:*:*)
echo "$UNAME_MACHINE"-cray-unicos"$UNAME_RELEASE" \
| sed -e 's/CRAY.*\([A-Z]90\)/\1/' \
-e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \
-e 's/\.[^.]*$/.X/'
exit ;;
CRAY*TS:*:*:*)
CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'`
GUESS=t90-cray-unicos$CRAY_REL
;;
CRAY*T3E:*:*:*)
CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'`
GUESS=alphaev5-cray-unicosmk$CRAY_REL
;;
CRAY*SV1:*:*:*)
CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'`
GUESS=sv1-cray-unicos$CRAY_REL
;;
*:UNICOS/mp:*:*)
CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'`
GUESS=craynv-cray-unicosmp$CRAY_REL
;;
F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*)
FUJITSU_PROC=`uname -m | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz`
FUJITSU_SYS=`uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///'`
FUJITSU_REL=`echo "$UNAME_RELEASE" | sed -e 's/ /_/'`
GUESS=${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}
;;
5000:UNIX_System_V:4.*:*)
FUJITSU_SYS=`uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///'`
FUJITSU_REL=`echo "$UNAME_RELEASE" | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/ /_/'`
GUESS=sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}
;;
i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*)
GUESS=$UNAME_MACHINE-pc-bsdi$UNAME_RELEASE
;;
sparc*:BSD/OS:*:*)
GUESS=sparc-unknown-bsdi$UNAME_RELEASE
;;
*:BSD/OS:*:*)
GUESS=$UNAME_MACHINE-unknown-bsdi$UNAME_RELEASE
;;
arm:FreeBSD:*:*)
UNAME_PROCESSOR=`uname -p`
set_cc_for_build
if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \
| grep -q __ARM_PCS_VFP
then
FREEBSD_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'`
GUESS=$UNAME_PROCESSOR-unknown-freebsd$FREEBSD_REL-gnueabi
else
FREEBSD_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'`
GUESS=$UNAME_PROCESSOR-unknown-freebsd$FREEBSD_REL-gnueabihf
fi
;;
*:FreeBSD:*:*)
UNAME_PROCESSOR=`/usr/bin/uname -p`
case $UNAME_PROCESSOR in
amd64)
UNAME_PROCESSOR=x86_64 ;;
i386)
UNAME_PROCESSOR=i586 ;;
esac
FREEBSD_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'`
GUESS=$UNAME_PROCESSOR-unknown-freebsd$FREEBSD_REL
;;
i*:CYGWIN*:*)
GUESS=$UNAME_MACHINE-pc-cygwin
;;
*:MINGW64*:*)
GUESS=$UNAME_MACHINE-pc-mingw64
;;
*:MINGW*:*)
GUESS=$UNAME_MACHINE-pc-mingw32
;;
*:MSYS*:*)
GUESS=$UNAME_MACHINE-pc-msys
;;
i*:PW*:*)
GUESS=$UNAME_MACHINE-pc-pw32
;;
*:SerenityOS:*:*)
GUESS=$UNAME_MACHINE-pc-serenity
;;
*:Interix*:*)
case $UNAME_MACHINE in
x86)
GUESS=i586-pc-interix$UNAME_RELEASE
;;
authenticamd | genuineintel | EM64T)
GUESS=x86_64-unknown-interix$UNAME_RELEASE
;;
IA64)
GUESS=ia64-unknown-interix$UNAME_RELEASE
;;
esac ;;
i*:UWIN*:*)
GUESS=$UNAME_MACHINE-pc-uwin
;;
amd64:CYGWIN*:*:* | x86_64:CYGWIN*:*:*)
GUESS=x86_64-pc-cygwin
;;
prep*:SunOS:5.*:*)
SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'`
GUESS=powerpcle-unknown-solaris2$SUN_REL
;;
*:GNU:*:*)
# the GNU system
GNU_ARCH=`echo "$UNAME_MACHINE" | sed -e 's,[-/].*$,,'`
GNU_REL=`echo "$UNAME_RELEASE" | sed -e 's,/.*$,,'`
GUESS=$GNU_ARCH-unknown-$LIBC$GNU_REL
;;
*:GNU/*:*:*)
# other systems with GNU libc and userland
GNU_SYS=`echo "$UNAME_SYSTEM" | sed 's,^[^/]*/,,' | tr "[:upper:]" "[:lower:]"`
GNU_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'`
GUESS=$UNAME_MACHINE-unknown-$GNU_SYS$GNU_REL-$LIBC
;;
x86_64:[Mm]anagarm:*:*|i?86:[Mm]anagarm:*:*)
GUESS="$UNAME_MACHINE-pc-managarm-mlibc"
;;
*:[Mm]anagarm:*:*)
GUESS="$UNAME_MACHINE-unknown-managarm-mlibc"
;;
*:Minix:*:*)
GUESS=$UNAME_MACHINE-unknown-minix
;;
aarch64:Linux:*:*)
GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
;;
aarch64_be:Linux:*:*)
UNAME_MACHINE=aarch64_be
GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
;;
alpha:Linux:*:*)
case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' /proc/cpuinfo 2>/dev/null` in
EV5) UNAME_MACHINE=alphaev5 ;;
EV56) UNAME_MACHINE=alphaev56 ;;
PCA56) UNAME_MACHINE=alphapca56 ;;
PCA57) UNAME_MACHINE=alphapca56 ;;
EV6) UNAME_MACHINE=alphaev6 ;;
EV67) UNAME_MACHINE=alphaev67 ;;
EV68*) UNAME_MACHINE=alphaev68 ;;
esac
objdump --private-headers /bin/sh | grep -q ld.so.1
if test "$?" = 0 ; then LIBC=gnulibc1 ; fi
GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
;;
arc:Linux:*:* | arceb:Linux:*:* | arc32:Linux:*:* | arc64:Linux:*:*)
GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
;;
arm*:Linux:*:*)
set_cc_for_build
if echo __ARM_EABI__ | $CC_FOR_BUILD -E - 2>/dev/null \
| grep -q __ARM_EABI__
then
GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
else
if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \
| grep -q __ARM_PCS_VFP
then
GUESS=$UNAME_MACHINE-unknown-linux-${LIBC}eabi
else
GUESS=$UNAME_MACHINE-unknown-linux-${LIBC}eabihf
fi
fi
;;
avr32*:Linux:*:*)
GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
;;
cris:Linux:*:*)
GUESS=$UNAME_MACHINE-axis-linux-$LIBC
;;
crisv32:Linux:*:*)
GUESS=$UNAME_MACHINE-axis-linux-$LIBC
;;
e2k:Linux:*:*)
GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
;;
frv:Linux:*:*)
GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
;;
hexagon:Linux:*:*)
GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
;;
i*86:Linux:*:*)
GUESS=$UNAME_MACHINE-pc-linux-$LIBC
;;
ia64:Linux:*:*)
GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
;;
k1om:Linux:*:*)
GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
;;
loongarch32:Linux:*:* | loongarch64:Linux:*:*)
GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
;;
m32r*:Linux:*:*)
GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
;;
m68*:Linux:*:*)
GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
;;
mips:Linux:*:* | mips64:Linux:*:*)
set_cc_for_build
IS_GLIBC=0
test x"${LIBC}" = xgnu && IS_GLIBC=1
sed 's/^ //' << EOF > "$dummy.c"
#undef CPU
#undef mips
#undef mipsel
#undef mips64
#undef mips64el
#if ${IS_GLIBC} && defined(_ABI64)
LIBCABI=gnuabi64
#else
#if ${IS_GLIBC} && defined(_ABIN32)
LIBCABI=gnuabin32
#else
LIBCABI=${LIBC}
#endif
#endif
#if ${IS_GLIBC} && defined(__mips64) && defined(__mips_isa_rev) && __mips_isa_rev>=6
CPU=mipsisa64r6
#else
#if ${IS_GLIBC} && !defined(__mips64) && defined(__mips_isa_rev) && __mips_isa_rev>=6
CPU=mipsisa32r6
#else
#if defined(__mips64)
CPU=mips64
#else
CPU=mips
#endif
#endif
#endif
#if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL)
MIPS_ENDIAN=el
#else
#if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB)
MIPS_ENDIAN=
#else
MIPS_ENDIAN=
#endif
#endif
EOF
cc_set_vars=`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^CPU\|^MIPS_ENDIAN\|^LIBCABI'`
eval "$cc_set_vars"
test "x$CPU" != x && { echo "$CPU${MIPS_ENDIAN}-unknown-linux-$LIBCABI"; exit; }
;;
mips64el:Linux:*:*)
GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
;;
openrisc*:Linux:*:*)
GUESS=or1k-unknown-linux-$LIBC
;;
or32:Linux:*:* | or1k*:Linux:*:*)
GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
;;
padre:Linux:*:*)
GUESS=sparc-unknown-linux-$LIBC
;;
parisc64:Linux:*:* | hppa64:Linux:*:*)
GUESS=hppa64-unknown-linux-$LIBC
;;
parisc:Linux:*:* | hppa:Linux:*:*)
# Look for CPU level
case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in
PA7*) GUESS=hppa1.1-unknown-linux-$LIBC ;;
PA8*) GUESS=hppa2.0-unknown-linux-$LIBC ;;
*) GUESS=hppa-unknown-linux-$LIBC ;;
esac
;;
ppc64:Linux:*:*)
GUESS=powerpc64-unknown-linux-$LIBC
;;
ppc:Linux:*:*)
GUESS=powerpc-unknown-linux-$LIBC
;;
ppc64le:Linux:*:*)
GUESS=powerpc64le-unknown-linux-$LIBC
;;
ppcle:Linux:*:*)
GUESS=powerpcle-unknown-linux-$LIBC
;;
riscv32:Linux:*:* | riscv32be:Linux:*:* | riscv64:Linux:*:* | riscv64be:Linux:*:*)
GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
;;
s390:Linux:*:* | s390x:Linux:*:*)
GUESS=$UNAME_MACHINE-ibm-linux-$LIBC
;;
sh64*:Linux:*:*)
GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
;;
sh*:Linux:*:*)
GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
;;
sparc:Linux:*:* | sparc64:Linux:*:*)
GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
;;
tile*:Linux:*:*)
GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
;;
vax:Linux:*:*)
GUESS=$UNAME_MACHINE-dec-linux-$LIBC
;;
x86_64:Linux:*:*)
set_cc_for_build
CPU=$UNAME_MACHINE
LIBCABI=$LIBC
if test "$CC_FOR_BUILD" != no_compiler_found; then
ABI=64
sed 's/^ //' << EOF > "$dummy.c"
#ifdef __i386__
ABI=x86
#else
#ifdef __ILP32__
ABI=x32
#endif
#endif
EOF
cc_set_abi=`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^ABI' | sed 's, ,,g'`
eval "$cc_set_abi"
case $ABI in
x86) CPU=i686 ;;
x32) LIBCABI=${LIBC}x32 ;;
esac
fi
GUESS=$CPU-pc-linux-$LIBCABI
;;
xtensa*:Linux:*:*)
GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
;;
i*86:DYNIX/ptx:4*:*)
# ptx 4.0 does uname -s correctly, with DYNIX/ptx in there.
# earlier versions are messed up and put the nodename in both
# sysname and nodename.
GUESS=i386-sequent-sysv4
;;
i*86:UNIX_SV:4.2MP:2.*)
# Unixware is an offshoot of SVR4, but it has its own version
# number series starting with 2...
# I am not positive that other SVR4 systems won't match this,
# I just have to hope. -- rms.
# Use sysv4.2uw... so that sysv4* matches it.
GUESS=$UNAME_MACHINE-pc-sysv4.2uw$UNAME_VERSION
;;
i*86:OS/2:*:*)
# If we were able to find `uname', then EMX Unix compatibility
# is probably installed.
GUESS=$UNAME_MACHINE-pc-os2-emx
;;
i*86:XTS-300:*:STOP)
GUESS=$UNAME_MACHINE-unknown-stop
;;
i*86:atheos:*:*)
GUESS=$UNAME_MACHINE-unknown-atheos
;;
i*86:syllable:*:*)
GUESS=$UNAME_MACHINE-pc-syllable
;;
i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.[02]*:*)
GUESS=i386-unknown-lynxos$UNAME_RELEASE
;;
i*86:*DOS:*:*)
GUESS=$UNAME_MACHINE-pc-msdosdjgpp
;;
i*86:*:4.*:*)
UNAME_REL=`echo "$UNAME_RELEASE" | sed 's/\/MP$//'`
if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then
GUESS=$UNAME_MACHINE-univel-sysv$UNAME_REL
else
GUESS=$UNAME_MACHINE-pc-sysv$UNAME_REL
fi
;;
i*86:*:5:[678]*)
# UnixWare 7.x, OpenUNIX and OpenServer 6.
case `/bin/uname -X | grep "^Machine"` in
*486*) UNAME_MACHINE=i486 ;;
*Pentium) UNAME_MACHINE=i586 ;;
*Pent*|*Celeron) UNAME_MACHINE=i686 ;;
esac
GUESS=$UNAME_MACHINE-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION}
;;
i*86:*:3.2:*)
if test -f /usr/options/cb.name; then
UNAME_REL=`sed -n 's/.*Version //p' /dev/null >/dev/null ; then
UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')`
(/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486
(/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \
&& UNAME_MACHINE=i586
(/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \
&& UNAME_MACHINE=i686
(/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \
&& UNAME_MACHINE=i686
GUESS=$UNAME_MACHINE-pc-sco$UNAME_REL
else
GUESS=$UNAME_MACHINE-pc-sysv32
fi
;;
pc:*:*:*)
# Left here for compatibility:
# uname -m prints for DJGPP always 'pc', but it prints nothing about
# the processor, so we play safe by assuming i586.
# Note: whatever this is, it MUST be the same as what config.sub
# prints for the "djgpp" host, or else GDB configure will decide that
# this is a cross-build.
GUESS=i586-pc-msdosdjgpp
;;
Intel:Mach:3*:*)
GUESS=i386-pc-mach3
;;
paragon:*:*:*)
GUESS=i860-intel-osf1
;;
i860:*:4.*:*) # i860-SVR4
if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then
GUESS=i860-stardent-sysv$UNAME_RELEASE # Stardent Vistra i860-SVR4
else # Add other i860-SVR4 vendors below as they are discovered.
GUESS=i860-unknown-sysv$UNAME_RELEASE # Unknown i860-SVR4
fi
;;
mini*:CTIX:SYS*5:*)
# "miniframe"
GUESS=m68010-convergent-sysv
;;
mc68k:UNIX:SYSTEM5:3.51m)
GUESS=m68k-convergent-sysv
;;
M680?0:D-NIX:5.3:*)
GUESS=m68k-diab-dnix
;;
M68*:*:R3V[5678]*:*)
test -r /sysV68 && { echo 'm68k-motorola-sysv'; exit; } ;;
3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0)
OS_REL=''
test -r /etc/.relid \
&& OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid`
/bin/uname -p 2>/dev/null | grep 86 >/dev/null \
&& { echo i486-ncr-sysv4.3"$OS_REL"; exit; }
/bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \
&& { echo i586-ncr-sysv4.3"$OS_REL"; exit; } ;;
3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*)
/bin/uname -p 2>/dev/null | grep 86 >/dev/null \
&& { echo i486-ncr-sysv4; exit; } ;;
NCR*:*:4.2:* | MPRAS*:*:4.2:*)
OS_REL='.3'
test -r /etc/.relid \
&& OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid`
/bin/uname -p 2>/dev/null | grep 86 >/dev/null \
&& { echo i486-ncr-sysv4.3"$OS_REL"; exit; }
/bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \
&& { echo i586-ncr-sysv4.3"$OS_REL"; exit; }
/bin/uname -p 2>/dev/null | /bin/grep pteron >/dev/null \
&& { echo i586-ncr-sysv4.3"$OS_REL"; exit; } ;;
m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*)
GUESS=m68k-unknown-lynxos$UNAME_RELEASE
;;
mc68030:UNIX_System_V:4.*:*)
GUESS=m68k-atari-sysv4
;;
TSUNAMI:LynxOS:2.*:*)
GUESS=sparc-unknown-lynxos$UNAME_RELEASE
;;
rs6000:LynxOS:2.*:*)
GUESS=rs6000-unknown-lynxos$UNAME_RELEASE
;;
PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.[02]*:*)
GUESS=powerpc-unknown-lynxos$UNAME_RELEASE
;;
SM[BE]S:UNIX_SV:*:*)
GUESS=mips-dde-sysv$UNAME_RELEASE
;;
RM*:ReliantUNIX-*:*:*)
GUESS=mips-sni-sysv4
;;
RM*:SINIX-*:*:*)
GUESS=mips-sni-sysv4
;;
*:SINIX-*:*:*)
if uname -p 2>/dev/null >/dev/null ; then
UNAME_MACHINE=`(uname -p) 2>/dev/null`
GUESS=$UNAME_MACHINE-sni-sysv4
else
GUESS=ns32k-sni-sysv
fi
;;
PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort
# says
GUESS=i586-unisys-sysv4
;;
*:UNIX_System_V:4*:FTX*)
# From Gerald Hewes .
# How about differentiating between stratus architectures? -djm
GUESS=hppa1.1-stratus-sysv4
;;
*:*:*:FTX*)
# From seanf@swdc.stratus.com.
GUESS=i860-stratus-sysv4
;;
i*86:VOS:*:*)
# From Paul.Green@stratus.com.
GUESS=$UNAME_MACHINE-stratus-vos
;;
*:VOS:*:*)
# From Paul.Green@stratus.com.
GUESS=hppa1.1-stratus-vos
;;
mc68*:A/UX:*:*)
GUESS=m68k-apple-aux$UNAME_RELEASE
;;
news*:NEWS-OS:6*:*)
GUESS=mips-sony-newsos6
;;
R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*)
if test -d /usr/nec; then
GUESS=mips-nec-sysv$UNAME_RELEASE
else
GUESS=mips-unknown-sysv$UNAME_RELEASE
fi
;;
BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only.
GUESS=powerpc-be-beos
;;
BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only.
GUESS=powerpc-apple-beos
;;
BePC:BeOS:*:*) # BeOS running on Intel PC compatible.
GUESS=i586-pc-beos
;;
BePC:Haiku:*:*) # Haiku running on Intel PC compatible.
GUESS=i586-pc-haiku
;;
ppc:Haiku:*:*) # Haiku running on Apple PowerPC
GUESS=powerpc-apple-haiku
;;
*:Haiku:*:*) # Haiku modern gcc (not bound by BeOS compat)
GUESS=$UNAME_MACHINE-unknown-haiku
;;
SX-4:SUPER-UX:*:*)
GUESS=sx4-nec-superux$UNAME_RELEASE
;;
SX-5:SUPER-UX:*:*)
GUESS=sx5-nec-superux$UNAME_RELEASE
;;
SX-6:SUPER-UX:*:*)
GUESS=sx6-nec-superux$UNAME_RELEASE
;;
SX-7:SUPER-UX:*:*)
GUESS=sx7-nec-superux$UNAME_RELEASE
;;
SX-8:SUPER-UX:*:*)
GUESS=sx8-nec-superux$UNAME_RELEASE
;;
SX-8R:SUPER-UX:*:*)
GUESS=sx8r-nec-superux$UNAME_RELEASE
;;
SX-ACE:SUPER-UX:*:*)
GUESS=sxace-nec-superux$UNAME_RELEASE
;;
Power*:Rhapsody:*:*)
GUESS=powerpc-apple-rhapsody$UNAME_RELEASE
;;
*:Rhapsody:*:*)
GUESS=$UNAME_MACHINE-apple-rhapsody$UNAME_RELEASE
;;
arm64:Darwin:*:*)
GUESS=aarch64-apple-darwin$UNAME_RELEASE
;;
*:Darwin:*:*)
UNAME_PROCESSOR=`uname -p`
case $UNAME_PROCESSOR in
unknown) UNAME_PROCESSOR=powerpc ;;
esac
if command -v xcode-select > /dev/null 2> /dev/null && \
! xcode-select --print-path > /dev/null 2> /dev/null ; then
# Avoid executing cc if there is no toolchain installed as
# cc will be a stub that puts up a graphical alert
# prompting the user to install developer tools.
CC_FOR_BUILD=no_compiler_found
else
set_cc_for_build
fi
if test "$CC_FOR_BUILD" != no_compiler_found; then
if (echo '#ifdef __LP64__'; echo IS_64BIT_ARCH; echo '#endif') | \
(CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \
grep IS_64BIT_ARCH >/dev/null
then
case $UNAME_PROCESSOR in
i386) UNAME_PROCESSOR=x86_64 ;;
powerpc) UNAME_PROCESSOR=powerpc64 ;;
esac
fi
# On 10.4-10.6 one might compile for PowerPC via gcc -arch ppc
if (echo '#ifdef __POWERPC__'; echo IS_PPC; echo '#endif') | \
(CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \
grep IS_PPC >/dev/null
then
UNAME_PROCESSOR=powerpc
fi
elif test "$UNAME_PROCESSOR" = i386 ; then
# uname -m returns i386 or x86_64
UNAME_PROCESSOR=$UNAME_MACHINE
fi
GUESS=$UNAME_PROCESSOR-apple-darwin$UNAME_RELEASE
;;
*:procnto*:*:* | *:QNX:[0123456789]*:*)
UNAME_PROCESSOR=`uname -p`
if test "$UNAME_PROCESSOR" = x86; then
UNAME_PROCESSOR=i386
UNAME_MACHINE=pc
fi
GUESS=$UNAME_PROCESSOR-$UNAME_MACHINE-nto-qnx$UNAME_RELEASE
;;
*:QNX:*:4*)
GUESS=i386-pc-qnx
;;
NEO-*:NONSTOP_KERNEL:*:*)
GUESS=neo-tandem-nsk$UNAME_RELEASE
;;
NSE-*:NONSTOP_KERNEL:*:*)
GUESS=nse-tandem-nsk$UNAME_RELEASE
;;
NSR-*:NONSTOP_KERNEL:*:*)
GUESS=nsr-tandem-nsk$UNAME_RELEASE
;;
NSV-*:NONSTOP_KERNEL:*:*)
GUESS=nsv-tandem-nsk$UNAME_RELEASE
;;
NSX-*:NONSTOP_KERNEL:*:*)
GUESS=nsx-tandem-nsk$UNAME_RELEASE
;;
*:NonStop-UX:*:*)
GUESS=mips-compaq-nonstopux
;;
BS2000:POSIX*:*:*)
GUESS=bs2000-siemens-sysv
;;
DS/*:UNIX_System_V:*:*)
GUESS=$UNAME_MACHINE-$UNAME_SYSTEM-$UNAME_RELEASE
;;
*:Plan9:*:*)
# "uname -m" is not consistent, so use $cputype instead. 386
# is converted to i386 for consistency with other x86
# operating systems.
if test "${cputype-}" = 386; then
UNAME_MACHINE=i386
elif test "x${cputype-}" != x; then
UNAME_MACHINE=$cputype
fi
GUESS=$UNAME_MACHINE-unknown-plan9
;;
*:TOPS-10:*:*)
GUESS=pdp10-unknown-tops10
;;
*:TENEX:*:*)
GUESS=pdp10-unknown-tenex
;;
KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*)
GUESS=pdp10-dec-tops20
;;
XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*)
GUESS=pdp10-xkl-tops20
;;
*:TOPS-20:*:*)
GUESS=pdp10-unknown-tops20
;;
*:ITS:*:*)
GUESS=pdp10-unknown-its
;;
SEI:*:*:SEIUX)
GUESS=mips-sei-seiux$UNAME_RELEASE
;;
*:DragonFly:*:*)
DRAGONFLY_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'`
GUESS=$UNAME_MACHINE-unknown-dragonfly$DRAGONFLY_REL
;;
*:*VMS:*:*)
UNAME_MACHINE=`(uname -p) 2>/dev/null`
case $UNAME_MACHINE in
A*) GUESS=alpha-dec-vms ;;
I*) GUESS=ia64-dec-vms ;;
V*) GUESS=vax-dec-vms ;;
esac ;;
*:XENIX:*:SysV)
GUESS=i386-pc-xenix
;;
i*86:skyos:*:*)
SKYOS_REL=`echo "$UNAME_RELEASE" | sed -e 's/ .*$//'`
GUESS=$UNAME_MACHINE-pc-skyos$SKYOS_REL
;;
i*86:rdos:*:*)
GUESS=$UNAME_MACHINE-pc-rdos
;;
i*86:Fiwix:*:*)
GUESS=$UNAME_MACHINE-pc-fiwix
;;
*:AROS:*:*)
GUESS=$UNAME_MACHINE-unknown-aros
;;
x86_64:VMkernel:*:*)
GUESS=$UNAME_MACHINE-unknown-esx
;;
amd64:Isilon\ OneFS:*:*)
GUESS=x86_64-unknown-onefs
;;
*:Unleashed:*:*)
GUESS=$UNAME_MACHINE-unknown-unleashed$UNAME_RELEASE
;;
esac
# Do we have a guess based on uname results?
if test "x$GUESS" != x; then
echo "$GUESS"
exit
fi
# No uname command or uname output not recognized.
set_cc_for_build
cat > "$dummy.c" <
#include
#endif
#if defined(ultrix) || defined(_ultrix) || defined(__ultrix) || defined(__ultrix__)
#if defined (vax) || defined (__vax) || defined (__vax__) || defined(mips) || defined(__mips) || defined(__mips__) || defined(MIPS) || defined(__MIPS__)
#include
#if defined(_SIZE_T_) || defined(SIGLOST)
#include
#endif
#endif
#endif
main ()
{
#if defined (sony)
#if defined (MIPSEB)
/* BFD wants "bsd" instead of "newsos". Perhaps BFD should be changed,
I don't know.... */
printf ("mips-sony-bsd\n"); exit (0);
#else
#include
printf ("m68k-sony-newsos%s\n",
#ifdef NEWSOS4
"4"
#else
""
#endif
); exit (0);
#endif
#endif
#if defined (NeXT)
#if !defined (__ARCHITECTURE__)
#define __ARCHITECTURE__ "m68k"
#endif
int version;
version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`;
if (version < 4)
printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version);
else
printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version);
exit (0);
#endif
#if defined (MULTIMAX) || defined (n16)
#if defined (UMAXV)
printf ("ns32k-encore-sysv\n"); exit (0);
#else
#if defined (CMU)
printf ("ns32k-encore-mach\n"); exit (0);
#else
printf ("ns32k-encore-bsd\n"); exit (0);
#endif
#endif
#endif
#if defined (__386BSD__)
printf ("i386-pc-bsd\n"); exit (0);
#endif
#if defined (sequent)
#if defined (i386)
printf ("i386-sequent-dynix\n"); exit (0);
#endif
#if defined (ns32000)
printf ("ns32k-sequent-dynix\n"); exit (0);
#endif
#endif
#if defined (_SEQUENT_)
struct utsname un;
uname(&un);
if (strncmp(un.version, "V2", 2) == 0) {
printf ("i386-sequent-ptx2\n"); exit (0);
}
if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */
printf ("i386-sequent-ptx1\n"); exit (0);
}
printf ("i386-sequent-ptx\n"); exit (0);
#endif
#if defined (vax)
#if !defined (ultrix)
#include
#if defined (BSD)
#if BSD == 43
printf ("vax-dec-bsd4.3\n"); exit (0);
#else
#if BSD == 199006
printf ("vax-dec-bsd4.3reno\n"); exit (0);
#else
printf ("vax-dec-bsd\n"); exit (0);
#endif
#endif
#else
printf ("vax-dec-bsd\n"); exit (0);
#endif
#else
#if defined(_SIZE_T_) || defined(SIGLOST)
struct utsname un;
uname (&un);
printf ("vax-dec-ultrix%s\n", un.release); exit (0);
#else
printf ("vax-dec-ultrix\n"); exit (0);
#endif
#endif
#endif
#if defined(ultrix) || defined(_ultrix) || defined(__ultrix) || defined(__ultrix__)
#if defined(mips) || defined(__mips) || defined(__mips__) || defined(MIPS) || defined(__MIPS__)
#if defined(_SIZE_T_) || defined(SIGLOST)
struct utsname *un;
uname (&un);
printf ("mips-dec-ultrix%s\n", un.release); exit (0);
#else
printf ("mips-dec-ultrix\n"); exit (0);
#endif
#endif
#endif
#if defined (alliant) && defined (i860)
printf ("i860-alliant-bsd\n"); exit (0);
#endif
exit (1);
}
EOF
$CC_FOR_BUILD -o "$dummy" "$dummy.c" 2>/dev/null && SYSTEM_NAME=`"$dummy"` &&
{ echo "$SYSTEM_NAME"; exit; }
# Apollos put the system type in the environment.
test -d /usr/apollo && { echo "$ISP-apollo-$SYSTYPE"; exit; }
echo "$0: unable to guess system type" >&2
case $UNAME_MACHINE:$UNAME_SYSTEM in
mips:Linux | mips64:Linux)
# If we got here on MIPS GNU/Linux, output extra information.
cat >&2 <&2 <&2 </dev/null || echo unknown`
uname -r = `(uname -r) 2>/dev/null || echo unknown`
uname -s = `(uname -s) 2>/dev/null || echo unknown`
uname -v = `(uname -v) 2>/dev/null || echo unknown`
/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null`
/bin/uname -X = `(/bin/uname -X) 2>/dev/null`
hostinfo = `(hostinfo) 2>/dev/null`
/bin/universe = `(/bin/universe) 2>/dev/null`
/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null`
/bin/arch = `(/bin/arch) 2>/dev/null`
/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null`
/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null`
UNAME_MACHINE = "$UNAME_MACHINE"
UNAME_RELEASE = "$UNAME_RELEASE"
UNAME_SYSTEM = "$UNAME_SYSTEM"
UNAME_VERSION = "$UNAME_VERSION"
EOF
fi
exit 1
# Local variables:
# eval: (add-hook 'before-save-hook 'time-stamp)
# time-stamp-start: "timestamp='"
# time-stamp-format: "%:y-%02m-%02d"
# time-stamp-end: "'"
# End:
proftpd-mod_proxy-0.9.5/config.sub 0000775 0000000 0000000 00000105752 14757370167 0017237 0 ustar 00root root 0000000 0000000 #! /bin/sh
# Configuration validation subroutine script.
# Copyright 1992-2023 Free Software Foundation, Inc.
# shellcheck disable=SC2006,SC2268 # see below for rationale
timestamp='2023-01-21'
# This file is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see .
#
# As a special exception to the GNU General Public License, if you
# distribute this file as part of a program that contains a
# configuration script generated by Autoconf, you may include it under
# the same distribution terms that you use for the rest of that
# program. This Exception is an additional permission under section 7
# of the GNU General Public License, version 3 ("GPLv3").
# Please send patches to .
#
# Configuration subroutine to validate and canonicalize a configuration type.
# Supply the specified configuration type as an argument.
# If it is invalid, we print an error message on stderr and exit with code 1.
# Otherwise, we print the canonical config type on stdout and succeed.
# You can get the latest version of this script from:
# https://git.savannah.gnu.org/cgit/config.git/plain/config.sub
# This file is supposed to be the same for all GNU packages
# and recognize all the CPU types, system types and aliases
# that are meaningful with *any* GNU software.
# Each package is responsible for reporting which valid configurations
# it does not support. The user should be able to distinguish
# a failure to support a valid configuration from a meaningless
# configuration.
# The goal of this file is to map all the various variations of a given
# machine specification into a single specification in the form:
# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM
# or in some cases, the newer four-part form:
# CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM
# It is wrong to echo any other type of specification.
# The "shellcheck disable" line above the timestamp inhibits complaints
# about features and limitations of the classic Bourne shell that were
# superseded or lifted in POSIX. However, this script identifies a wide
# variety of pre-POSIX systems that do not have POSIX shells at all, and
# even some reasonably current systems (Solaris 10 as case-in-point) still
# have a pre-POSIX /bin/sh.
me=`echo "$0" | sed -e 's,.*/,,'`
usage="\
Usage: $0 [OPTION] CPU-MFR-OPSYS or ALIAS
Canonicalize a configuration name.
Options:
-h, --help print this help, then exit
-t, --time-stamp print date of last modification, then exit
-v, --version print version number, then exit
Report bugs and patches to ."
version="\
GNU config.sub ($timestamp)
Copyright 1992-2023 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
help="
Try \`$me --help' for more information."
# Parse command line
while test $# -gt 0 ; do
case $1 in
--time-stamp | --time* | -t )
echo "$timestamp" ; exit ;;
--version | -v )
echo "$version" ; exit ;;
--help | --h* | -h )
echo "$usage"; exit ;;
-- ) # Stop option processing
shift; break ;;
- ) # Use stdin as input.
break ;;
-* )
echo "$me: invalid option $1$help" >&2
exit 1 ;;
*local*)
# First pass through any local machine types.
echo "$1"
exit ;;
* )
break ;;
esac
done
case $# in
0) echo "$me: missing argument$help" >&2
exit 1;;
1) ;;
*) echo "$me: too many arguments$help" >&2
exit 1;;
esac
# Split fields of configuration type
# shellcheck disable=SC2162
saved_IFS=$IFS
IFS="-" read field1 field2 field3 field4 <&2
exit 1
;;
*-*-*-*)
basic_machine=$field1-$field2
basic_os=$field3-$field4
;;
*-*-*)
# Ambiguous whether COMPANY is present, or skipped and KERNEL-OS is two
# parts
maybe_os=$field2-$field3
case $maybe_os in
nto-qnx* | linux-* | uclinux-uclibc* \
| uclinux-gnu* | kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* \
| netbsd*-eabi* | kopensolaris*-gnu* | cloudabi*-eabi* \
| storm-chaos* | os2-emx* | rtmk-nova* | managarm-*)
basic_machine=$field1
basic_os=$maybe_os
;;
android-linux)
basic_machine=$field1-unknown
basic_os=linux-android
;;
*)
basic_machine=$field1-$field2
basic_os=$field3
;;
esac
;;
*-*)
# A lone config we happen to match not fitting any pattern
case $field1-$field2 in
decstation-3100)
basic_machine=mips-dec
basic_os=
;;
*-*)
# Second component is usually, but not always the OS
case $field2 in
# Prevent following clause from handling this valid os
sun*os*)
basic_machine=$field1
basic_os=$field2
;;
zephyr*)
basic_machine=$field1-unknown
basic_os=$field2
;;
# Manufacturers
dec* | mips* | sequent* | encore* | pc533* | sgi* | sony* \
| att* | 7300* | 3300* | delta* | motorola* | sun[234]* \
| unicom* | ibm* | next | hp | isi* | apollo | altos* \
| convergent* | ncr* | news | 32* | 3600* | 3100* \
| hitachi* | c[123]* | convex* | sun | crds | omron* | dg \
| ultra | tti* | harris | dolphin | highlevel | gould \
| cbm | ns | masscomp | apple | axis | knuth | cray \
| microblaze* | sim | cisco \
| oki | wec | wrs | winbond)
basic_machine=$field1-$field2
basic_os=
;;
*)
basic_machine=$field1
basic_os=$field2
;;
esac
;;
esac
;;
*)
# Convert single-component short-hands not valid as part of
# multi-component configurations.
case $field1 in
386bsd)
basic_machine=i386-pc
basic_os=bsd
;;
a29khif)
basic_machine=a29k-amd
basic_os=udi
;;
adobe68k)
basic_machine=m68010-adobe
basic_os=scout
;;
alliant)
basic_machine=fx80-alliant
basic_os=
;;
altos | altos3068)
basic_machine=m68k-altos
basic_os=
;;
am29k)
basic_machine=a29k-none
basic_os=bsd
;;
amdahl)
basic_machine=580-amdahl
basic_os=sysv
;;
amiga)
basic_machine=m68k-unknown
basic_os=
;;
amigaos | amigados)
basic_machine=m68k-unknown
basic_os=amigaos
;;
amigaunix | amix)
basic_machine=m68k-unknown
basic_os=sysv4
;;
apollo68)
basic_machine=m68k-apollo
basic_os=sysv
;;
apollo68bsd)
basic_machine=m68k-apollo
basic_os=bsd
;;
aros)
basic_machine=i386-pc
basic_os=aros
;;
aux)
basic_machine=m68k-apple
basic_os=aux
;;
balance)
basic_machine=ns32k-sequent
basic_os=dynix
;;
blackfin)
basic_machine=bfin-unknown
basic_os=linux
;;
cegcc)
basic_machine=arm-unknown
basic_os=cegcc
;;
convex-c1)
basic_machine=c1-convex
basic_os=bsd
;;
convex-c2)
basic_machine=c2-convex
basic_os=bsd
;;
convex-c32)
basic_machine=c32-convex
basic_os=bsd
;;
convex-c34)
basic_machine=c34-convex
basic_os=bsd
;;
convex-c38)
basic_machine=c38-convex
basic_os=bsd
;;
cray)
basic_machine=j90-cray
basic_os=unicos
;;
crds | unos)
basic_machine=m68k-crds
basic_os=
;;
da30)
basic_machine=m68k-da30
basic_os=
;;
decstation | pmax | pmin | dec3100 | decstatn)
basic_machine=mips-dec
basic_os=
;;
delta88)
basic_machine=m88k-motorola
basic_os=sysv3
;;
dicos)
basic_machine=i686-pc
basic_os=dicos
;;
djgpp)
basic_machine=i586-pc
basic_os=msdosdjgpp
;;
ebmon29k)
basic_machine=a29k-amd
basic_os=ebmon
;;
es1800 | OSE68k | ose68k | ose | OSE)
basic_machine=m68k-ericsson
basic_os=ose
;;
gmicro)
basic_machine=tron-gmicro
basic_os=sysv
;;
go32)
basic_machine=i386-pc
basic_os=go32
;;
h8300hms)
basic_machine=h8300-hitachi
basic_os=hms
;;
h8300xray)
basic_machine=h8300-hitachi
basic_os=xray
;;
h8500hms)
basic_machine=h8500-hitachi
basic_os=hms
;;
harris)
basic_machine=m88k-harris
basic_os=sysv3
;;
hp300 | hp300hpux)
basic_machine=m68k-hp
basic_os=hpux
;;
hp300bsd)
basic_machine=m68k-hp
basic_os=bsd
;;
hppaosf)
basic_machine=hppa1.1-hp
basic_os=osf
;;
hppro)
basic_machine=hppa1.1-hp
basic_os=proelf
;;
i386mach)
basic_machine=i386-mach
basic_os=mach
;;
isi68 | isi)
basic_machine=m68k-isi
basic_os=sysv
;;
m68knommu)
basic_machine=m68k-unknown
basic_os=linux
;;
magnum | m3230)
basic_machine=mips-mips
basic_os=sysv
;;
merlin)
basic_machine=ns32k-utek
basic_os=sysv
;;
mingw64)
basic_machine=x86_64-pc
basic_os=mingw64
;;
mingw32)
basic_machine=i686-pc
basic_os=mingw32
;;
mingw32ce)
basic_machine=arm-unknown
basic_os=mingw32ce
;;
monitor)
basic_machine=m68k-rom68k
basic_os=coff
;;
morphos)
basic_machine=powerpc-unknown
basic_os=morphos
;;
moxiebox)
basic_machine=moxie-unknown
basic_os=moxiebox
;;
msdos)
basic_machine=i386-pc
basic_os=msdos
;;
msys)
basic_machine=i686-pc
basic_os=msys
;;
mvs)
basic_machine=i370-ibm
basic_os=mvs
;;
nacl)
basic_machine=le32-unknown
basic_os=nacl
;;
ncr3000)
basic_machine=i486-ncr
basic_os=sysv4
;;
netbsd386)
basic_machine=i386-pc
basic_os=netbsd
;;
netwinder)
basic_machine=armv4l-rebel
basic_os=linux
;;
news | news700 | news800 | news900)
basic_machine=m68k-sony
basic_os=newsos
;;
news1000)
basic_machine=m68030-sony
basic_os=newsos
;;
necv70)
basic_machine=v70-nec
basic_os=sysv
;;
nh3000)
basic_machine=m68k-harris
basic_os=cxux
;;
nh[45]000)
basic_machine=m88k-harris
basic_os=cxux
;;
nindy960)
basic_machine=i960-intel
basic_os=nindy
;;
mon960)
basic_machine=i960-intel
basic_os=mon960
;;
nonstopux)
basic_machine=mips-compaq
basic_os=nonstopux
;;
os400)
basic_machine=powerpc-ibm
basic_os=os400
;;
OSE68000 | ose68000)
basic_machine=m68000-ericsson
basic_os=ose
;;
os68k)
basic_machine=m68k-none
basic_os=os68k
;;
paragon)
basic_machine=i860-intel
basic_os=osf
;;
parisc)
basic_machine=hppa-unknown
basic_os=linux
;;
psp)
basic_machine=mipsallegrexel-sony
basic_os=psp
;;
pw32)
basic_machine=i586-unknown
basic_os=pw32
;;
rdos | rdos64)
basic_machine=x86_64-pc
basic_os=rdos
;;
rdos32)
basic_machine=i386-pc
basic_os=rdos
;;
rom68k)
basic_machine=m68k-rom68k
basic_os=coff
;;
sa29200)
basic_machine=a29k-amd
basic_os=udi
;;
sei)
basic_machine=mips-sei
basic_os=seiux
;;
sequent)
basic_machine=i386-sequent
basic_os=
;;
sps7)
basic_machine=m68k-bull
basic_os=sysv2
;;
st2000)
basic_machine=m68k-tandem
basic_os=
;;
stratus)
basic_machine=i860-stratus
basic_os=sysv4
;;
sun2)
basic_machine=m68000-sun
basic_os=
;;
sun2os3)
basic_machine=m68000-sun
basic_os=sunos3
;;
sun2os4)
basic_machine=m68000-sun
basic_os=sunos4
;;
sun3)
basic_machine=m68k-sun
basic_os=
;;
sun3os3)
basic_machine=m68k-sun
basic_os=sunos3
;;
sun3os4)
basic_machine=m68k-sun
basic_os=sunos4
;;
sun4)
basic_machine=sparc-sun
basic_os=
;;
sun4os3)
basic_machine=sparc-sun
basic_os=sunos3
;;
sun4os4)
basic_machine=sparc-sun
basic_os=sunos4
;;
sun4sol2)
basic_machine=sparc-sun
basic_os=solaris2
;;
sun386 | sun386i | roadrunner)
basic_machine=i386-sun
basic_os=
;;
sv1)
basic_machine=sv1-cray
basic_os=unicos
;;
symmetry)
basic_machine=i386-sequent
basic_os=dynix
;;
t3e)
basic_machine=alphaev5-cray
basic_os=unicos
;;
t90)
basic_machine=t90-cray
basic_os=unicos
;;
toad1)
basic_machine=pdp10-xkl
basic_os=tops20
;;
tpf)
basic_machine=s390x-ibm
basic_os=tpf
;;
udi29k)
basic_machine=a29k-amd
basic_os=udi
;;
ultra3)
basic_machine=a29k-nyu
basic_os=sym1
;;
v810 | necv810)
basic_machine=v810-nec
basic_os=none
;;
vaxv)
basic_machine=vax-dec
basic_os=sysv
;;
vms)
basic_machine=vax-dec
basic_os=vms
;;
vsta)
basic_machine=i386-pc
basic_os=vsta
;;
vxworks960)
basic_machine=i960-wrs
basic_os=vxworks
;;
vxworks68)
basic_machine=m68k-wrs
basic_os=vxworks
;;
vxworks29k)
basic_machine=a29k-wrs
basic_os=vxworks
;;
xbox)
basic_machine=i686-pc
basic_os=mingw32
;;
ymp)
basic_machine=ymp-cray
basic_os=unicos
;;
*)
basic_machine=$1
basic_os=
;;
esac
;;
esac
# Decode 1-component or ad-hoc basic machines
case $basic_machine in
# Here we handle the default manufacturer of certain CPU types. It is in
# some cases the only manufacturer, in others, it is the most popular.
w89k)
cpu=hppa1.1
vendor=winbond
;;
op50n)
cpu=hppa1.1
vendor=oki
;;
op60c)
cpu=hppa1.1
vendor=oki
;;
ibm*)
cpu=i370
vendor=ibm
;;
orion105)
cpu=clipper
vendor=highlevel
;;
mac | mpw | mac-mpw)
cpu=m68k
vendor=apple
;;
pmac | pmac-mpw)
cpu=powerpc
vendor=apple
;;
# Recognize the various machine names and aliases which stand
# for a CPU type and a company and sometimes even an OS.
3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc)
cpu=m68000
vendor=att
;;
3b*)
cpu=we32k
vendor=att
;;
bluegene*)
cpu=powerpc
vendor=ibm
basic_os=cnk
;;
decsystem10* | dec10*)
cpu=pdp10
vendor=dec
basic_os=tops10
;;
decsystem20* | dec20*)
cpu=pdp10
vendor=dec
basic_os=tops20
;;
delta | 3300 | motorola-3300 | motorola-delta \
| 3300-motorola | delta-motorola)
cpu=m68k
vendor=motorola
;;
dpx2*)
cpu=m68k
vendor=bull
basic_os=sysv3
;;
encore | umax | mmax)
cpu=ns32k
vendor=encore
;;
elxsi)
cpu=elxsi
vendor=elxsi
basic_os=${basic_os:-bsd}
;;
fx2800)
cpu=i860
vendor=alliant
;;
genix)
cpu=ns32k
vendor=ns
;;
h3050r* | hiux*)
cpu=hppa1.1
vendor=hitachi
basic_os=hiuxwe2
;;
hp3k9[0-9][0-9] | hp9[0-9][0-9])
cpu=hppa1.0
vendor=hp
;;
hp9k2[0-9][0-9] | hp9k31[0-9])
cpu=m68000
vendor=hp
;;
hp9k3[2-9][0-9])
cpu=m68k
vendor=hp
;;
hp9k6[0-9][0-9] | hp6[0-9][0-9])
cpu=hppa1.0
vendor=hp
;;
hp9k7[0-79][0-9] | hp7[0-79][0-9])
cpu=hppa1.1
vendor=hp
;;
hp9k78[0-9] | hp78[0-9])
# FIXME: really hppa2.0-hp
cpu=hppa1.1
vendor=hp
;;
hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893)
# FIXME: really hppa2.0-hp
cpu=hppa1.1
vendor=hp
;;
hp9k8[0-9][13679] | hp8[0-9][13679])
cpu=hppa1.1
vendor=hp
;;
hp9k8[0-9][0-9] | hp8[0-9][0-9])
cpu=hppa1.0
vendor=hp
;;
i*86v32)
cpu=`echo "$1" | sed -e 's/86.*/86/'`
vendor=pc
basic_os=sysv32
;;
i*86v4*)
cpu=`echo "$1" | sed -e 's/86.*/86/'`
vendor=pc
basic_os=sysv4
;;
i*86v)
cpu=`echo "$1" | sed -e 's/86.*/86/'`
vendor=pc
basic_os=sysv
;;
i*86sol2)
cpu=`echo "$1" | sed -e 's/86.*/86/'`
vendor=pc
basic_os=solaris2
;;
j90 | j90-cray)
cpu=j90
vendor=cray
basic_os=${basic_os:-unicos}
;;
iris | iris4d)
cpu=mips
vendor=sgi
case $basic_os in
irix*)
;;
*)
basic_os=irix4
;;
esac
;;
miniframe)
cpu=m68000
vendor=convergent
;;
*mint | mint[0-9]* | *MiNT | *MiNT[0-9]*)
cpu=m68k
vendor=atari
basic_os=mint
;;
news-3600 | risc-news)
cpu=mips
vendor=sony
basic_os=newsos
;;
next | m*-next)
cpu=m68k
vendor=next
case $basic_os in
openstep*)
;;
nextstep*)
;;
ns2*)
basic_os=nextstep2
;;
*)
basic_os=nextstep3
;;
esac
;;
np1)
cpu=np1
vendor=gould
;;
op50n-* | op60c-*)
cpu=hppa1.1
vendor=oki
basic_os=proelf
;;
pa-hitachi)
cpu=hppa1.1
vendor=hitachi
basic_os=hiuxwe2
;;
pbd)
cpu=sparc
vendor=tti
;;
pbb)
cpu=m68k
vendor=tti
;;
pc532)
cpu=ns32k
vendor=pc532
;;
pn)
cpu=pn
vendor=gould
;;
power)
cpu=power
vendor=ibm
;;
ps2)
cpu=i386
vendor=ibm
;;
rm[46]00)
cpu=mips
vendor=siemens
;;
rtpc | rtpc-*)
cpu=romp
vendor=ibm
;;
sde)
cpu=mipsisa32
vendor=sde
basic_os=${basic_os:-elf}
;;
simso-wrs)
cpu=sparclite
vendor=wrs
basic_os=vxworks
;;
tower | tower-32)
cpu=m68k
vendor=ncr
;;
vpp*|vx|vx-*)
cpu=f301
vendor=fujitsu
;;
w65)
cpu=w65
vendor=wdc
;;
w89k-*)
cpu=hppa1.1
vendor=winbond
basic_os=proelf
;;
none)
cpu=none
vendor=none
;;
leon|leon[3-9])
cpu=sparc
vendor=$basic_machine
;;
leon-*|leon[3-9]-*)
cpu=sparc
vendor=`echo "$basic_machine" | sed 's/-.*//'`
;;
*-*)
# shellcheck disable=SC2162
saved_IFS=$IFS
IFS="-" read cpu vendor <&2
exit 1
;;
esac
;;
esac
# Here we canonicalize certain aliases for manufacturers.
case $vendor in
digital*)
vendor=dec
;;
commodore*)
vendor=cbm
;;
*)
;;
esac
# Decode manufacturer-specific aliases for certain operating systems.
if test x$basic_os != x
then
# First recognize some ad-hoc cases, or perhaps split kernel-os, or else just
# set os.
case $basic_os in
gnu/linux*)
kernel=linux
os=`echo "$basic_os" | sed -e 's|gnu/linux|gnu|'`
;;
os2-emx)
kernel=os2
os=`echo "$basic_os" | sed -e 's|os2-emx|emx|'`
;;
nto-qnx*)
kernel=nto
os=`echo "$basic_os" | sed -e 's|nto-qnx|qnx|'`
;;
*-*)
# shellcheck disable=SC2162
saved_IFS=$IFS
IFS="-" read kernel os <&2
exit 1
;;
esac
# As a final step for OS-related things, validate the OS-kernel combination
# (given a valid OS), if there is a kernel.
case $kernel-$os in
linux-gnu* | linux-dietlibc* | linux-android* | linux-newlib* \
| linux-musl* | linux-relibc* | linux-uclibc* | linux-mlibc* )
;;
uclinux-uclibc* )
;;
managarm-mlibc* | managarm-kernel* )
;;
-dietlibc* | -newlib* | -musl* | -relibc* | -uclibc* | -mlibc* )
# These are just libc implementations, not actual OSes, and thus
# require a kernel.
echo "Invalid configuration \`$1': libc \`$os' needs explicit kernel." 1>&2
exit 1
;;
-kernel* )
echo "Invalid configuration \`$1': \`$os' needs explicit kernel." 1>&2
exit 1
;;
*-kernel* )
echo "Invalid configuration \`$1': \`$kernel' does not support \`$os'." 1>&2
exit 1
;;
kfreebsd*-gnu* | kopensolaris*-gnu*)
;;
vxworks-simlinux | vxworks-simwindows | vxworks-spe)
;;
nto-qnx*)
;;
os2-emx)
;;
*-eabi* | *-gnueabi*)
;;
-*)
# Blank kernel with real OS is always fine.
;;
*-*)
echo "Invalid configuration \`$1': Kernel \`$kernel' not known to work with OS \`$os'." 1>&2
exit 1
;;
esac
# Here we handle the case where we know the os, and the CPU type, but not the
# manufacturer. We pick the logical manufacturer.
case $vendor in
unknown)
case $cpu-$os in
*-riscix*)
vendor=acorn
;;
*-sunos*)
vendor=sun
;;
*-cnk* | *-aix*)
vendor=ibm
;;
*-beos*)
vendor=be
;;
*-hpux*)
vendor=hp
;;
*-mpeix*)
vendor=hp
;;
*-hiux*)
vendor=hitachi
;;
*-unos*)
vendor=crds
;;
*-dgux*)
vendor=dg
;;
*-luna*)
vendor=omron
;;
*-genix*)
vendor=ns
;;
*-clix*)
vendor=intergraph
;;
*-mvs* | *-opened*)
vendor=ibm
;;
*-os400*)
vendor=ibm
;;
s390-* | s390x-*)
vendor=ibm
;;
*-ptx*)
vendor=sequent
;;
*-tpf*)
vendor=ibm
;;
*-vxsim* | *-vxworks* | *-windiss*)
vendor=wrs
;;
*-aux*)
vendor=apple
;;
*-hms*)
vendor=hitachi
;;
*-mpw* | *-macos*)
vendor=apple
;;
*-*mint | *-mint[0-9]* | *-*MiNT | *-MiNT[0-9]*)
vendor=atari
;;
*-vos*)
vendor=stratus
;;
esac
;;
esac
echo "$cpu-$vendor-${kernel:+$kernel-}$os"
exit
# Local variables:
# eval: (add-hook 'before-save-hook 'time-stamp)
# time-stamp-start: "timestamp='"
# time-stamp-format: "%:y-%02m-%02d"
# time-stamp-end: "'"
# End:
proftpd-mod_proxy-0.9.5/configure 0000775 0000000 0000000 00000476207 14757370167 0017171 0 ustar 00root root 0000000 0000000 #! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.69.
#
#
# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc.
#
#
# This configure script is free software; the Free Software Foundation
# gives unlimited permission to copy, distribute and modify it.
## -------------------- ##
## M4sh Initialization. ##
## -------------------- ##
# Be more Bourne compatible
DUALCASE=1; export DUALCASE # for MKS sh
if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then :
emulate sh
NULLCMD=:
# Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which
# is contrary to our usage. Disable this feature.
alias -g '${1+"$@"}'='"$@"'
setopt NO_GLOB_SUBST
else
case `(set -o) 2>/dev/null` in #(
*posix*) :
set -o posix ;; #(
*) :
;;
esac
fi
as_nl='
'
export as_nl
# Printing a long string crashes Solaris 7 /usr/bin/printf.
as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo
as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo
# Prefer a ksh shell builtin over an external printf program on Solaris,
# but without wasting forks for bash or zsh.
if test -z "$BASH_VERSION$ZSH_VERSION" \
&& (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then
as_echo='print -r --'
as_echo_n='print -rn --'
elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then
as_echo='printf %s\n'
as_echo_n='printf %s'
else
if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then
as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"'
as_echo_n='/usr/ucb/echo -n'
else
as_echo_body='eval expr "X$1" : "X\\(.*\\)"'
as_echo_n_body='eval
arg=$1;
case $arg in #(
*"$as_nl"*)
expr "X$arg" : "X\\(.*\\)$as_nl";
arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;;
esac;
expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl"
'
export as_echo_n_body
as_echo_n='sh -c $as_echo_n_body as_echo'
fi
export as_echo_body
as_echo='sh -c $as_echo_body as_echo'
fi
# The user is always right.
if test "${PATH_SEPARATOR+set}" != set; then
PATH_SEPARATOR=:
(PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && {
(PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 ||
PATH_SEPARATOR=';'
}
fi
# IFS
# We need space, tab and new line, in precisely that order. Quoting is
# there to prevent editors from complaining about space-tab.
# (If _AS_PATH_WALK were called with IFS unset, it would disable word
# splitting by setting IFS to empty value.)
IFS=" "" $as_nl"
# Find who we are. Look in the path if we contain no directory separator.
as_myself=
case $0 in #((
*[\\/]* ) as_myself=$0 ;;
*) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
for as_dir in $PATH
do
IFS=$as_save_IFS
test -z "$as_dir" && as_dir=.
test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
done
IFS=$as_save_IFS
;;
esac
# We did not find ourselves, most probably we were run as `sh COMMAND'
# in which case we are not to be found in the path.
if test "x$as_myself" = x; then
as_myself=$0
fi
if test ! -f "$as_myself"; then
$as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2
exit 1
fi
# Unset variables that we do not need and which cause bugs (e.g. in
# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1"
# suppresses any "Segmentation fault" message there. '((' could
# trigger a bug in pdksh 5.2.14.
for as_var in BASH_ENV ENV MAIL MAILPATH
do eval test x\${$as_var+set} = xset \
&& ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || :
done
PS1='$ '
PS2='> '
PS4='+ '
# NLS nuisances.
LC_ALL=C
export LC_ALL
LANGUAGE=C
export LANGUAGE
# CDPATH.
(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
# Use a proper internal environment variable to ensure we don't fall
# into an infinite loop, continuously re-executing ourselves.
if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then
_as_can_reexec=no; export _as_can_reexec;
# We cannot yet assume a decent shell, so we have to provide a
# neutralization value for shells without unset; and this also
# works around shells that cannot unset nonexistent variables.
# Preserve -v and -x to the replacement shell.
BASH_ENV=/dev/null
ENV=/dev/null
(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV
case $- in # ((((
*v*x* | *x*v* ) as_opts=-vx ;;
*v* ) as_opts=-v ;;
*x* ) as_opts=-x ;;
* ) as_opts= ;;
esac
exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"}
# Admittedly, this is quite paranoid, since all the known shells bail
# out after a failed `exec'.
$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2
as_fn_exit 255
fi
# We don't want this to propagate to other subprocesses.
{ _as_can_reexec=; unset _as_can_reexec;}
if test "x$CONFIG_SHELL" = x; then
as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then :
emulate sh
NULLCMD=:
# Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which
# is contrary to our usage. Disable this feature.
alias -g '\${1+\"\$@\"}'='\"\$@\"'
setopt NO_GLOB_SUBST
else
case \`(set -o) 2>/dev/null\` in #(
*posix*) :
set -o posix ;; #(
*) :
;;
esac
fi
"
as_required="as_fn_return () { (exit \$1); }
as_fn_success () { as_fn_return 0; }
as_fn_failure () { as_fn_return 1; }
as_fn_ret_success () { return 0; }
as_fn_ret_failure () { return 1; }
exitcode=0
as_fn_success || { exitcode=1; echo as_fn_success failed.; }
as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; }
as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; }
as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; }
if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then :
else
exitcode=1; echo positional parameters were not saved.
fi
test x\$exitcode = x0 || exit 1
test -x / || exit 1"
as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO
as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO
eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" &&
test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1
test \$(( 1 + 1 )) = 2 || exit 1"
if (eval "$as_required") 2>/dev/null; then :
as_have_required=yes
else
as_have_required=no
fi
if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then :
else
as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
as_found=false
for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH
do
IFS=$as_save_IFS
test -z "$as_dir" && as_dir=.
as_found=:
case $as_dir in #(
/*)
for as_base in sh bash ksh sh5; do
# Try only shells that exist, to save several forks.
as_shell=$as_dir/$as_base
if { test -f "$as_shell" || test -f "$as_shell.exe"; } &&
{ $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then :
CONFIG_SHELL=$as_shell as_have_required=yes
if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then :
break 2
fi
fi
done;;
esac
as_found=false
done
$as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } &&
{ $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then :
CONFIG_SHELL=$SHELL as_have_required=yes
fi; }
IFS=$as_save_IFS
if test "x$CONFIG_SHELL" != x; then :
export CONFIG_SHELL
# We cannot yet assume a decent shell, so we have to provide a
# neutralization value for shells without unset; and this also
# works around shells that cannot unset nonexistent variables.
# Preserve -v and -x to the replacement shell.
BASH_ENV=/dev/null
ENV=/dev/null
(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV
case $- in # ((((
*v*x* | *x*v* ) as_opts=-vx ;;
*v* ) as_opts=-v ;;
*x* ) as_opts=-x ;;
* ) as_opts= ;;
esac
exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"}
# Admittedly, this is quite paranoid, since all the known shells bail
# out after a failed `exec'.
$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2
exit 255
fi
if test x$as_have_required = xno; then :
$as_echo "$0: This script requires a shell more modern than all"
$as_echo "$0: the shells that I found on your system."
if test x${ZSH_VERSION+set} = xset ; then
$as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should"
$as_echo "$0: be upgraded to zsh 4.3.4 or later."
else
$as_echo "$0: Please tell bug-autoconf@gnu.org about your system,
$0: including any error possibly output before this
$0: message. Then install a modern shell, or manually run
$0: the script under such a shell if you do have one."
fi
exit 1
fi
fi
fi
SHELL=${CONFIG_SHELL-/bin/sh}
export SHELL
# Unset more variables known to interfere with behavior of common tools.
CLICOLOR_FORCE= GREP_OPTIONS=
unset CLICOLOR_FORCE GREP_OPTIONS
## --------------------- ##
## M4sh Shell Functions. ##
## --------------------- ##
# as_fn_unset VAR
# ---------------
# Portably unset VAR.
as_fn_unset ()
{
{ eval $1=; unset $1;}
}
as_unset=as_fn_unset
# as_fn_set_status STATUS
# -----------------------
# Set $? to STATUS, without forking.
as_fn_set_status ()
{
return $1
} # as_fn_set_status
# as_fn_exit STATUS
# -----------------
# Exit the shell with STATUS, even in a "trap 0" or "set -e" context.
as_fn_exit ()
{
set +e
as_fn_set_status $1
exit $1
} # as_fn_exit
# as_fn_mkdir_p
# -------------
# Create "$as_dir" as a directory, including parents if necessary.
as_fn_mkdir_p ()
{
case $as_dir in #(
-*) as_dir=./$as_dir;;
esac
test -d "$as_dir" || eval $as_mkdir_p || {
as_dirs=
while :; do
case $as_dir in #(
*\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'(
*) as_qdir=$as_dir;;
esac
as_dirs="'$as_qdir' $as_dirs"
as_dir=`$as_dirname -- "$as_dir" ||
$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
X"$as_dir" : 'X\(//\)[^/]' \| \
X"$as_dir" : 'X\(//\)$' \| \
X"$as_dir" : 'X\(/\)' \| . 2>/dev/null ||
$as_echo X"$as_dir" |
sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
s//\1/
q
}
/^X\(\/\/\)[^/].*/{
s//\1/
q
}
/^X\(\/\/\)$/{
s//\1/
q
}
/^X\(\/\).*/{
s//\1/
q
}
s/.*/./; q'`
test -d "$as_dir" && break
done
test -z "$as_dirs" || eval "mkdir $as_dirs"
} || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir"
} # as_fn_mkdir_p
# as_fn_executable_p FILE
# -----------------------
# Test if FILE is an executable regular file.
as_fn_executable_p ()
{
test -f "$1" && test -x "$1"
} # as_fn_executable_p
# as_fn_append VAR VALUE
# ----------------------
# Append the text in VALUE to the end of the definition contained in VAR. Take
# advantage of any shell optimizations that allow amortized linear growth over
# repeated appends, instead of the typical quadratic growth present in naive
# implementations.
if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then :
eval 'as_fn_append ()
{
eval $1+=\$2
}'
else
as_fn_append ()
{
eval $1=\$$1\$2
}
fi # as_fn_append
# as_fn_arith ARG...
# ------------------
# Perform arithmetic evaluation on the ARGs, and store the result in the
# global $as_val. Take advantage of shells that can avoid forks. The arguments
# must be portable across $(()) and expr.
if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then :
eval 'as_fn_arith ()
{
as_val=$(( $* ))
}'
else
as_fn_arith ()
{
as_val=`expr "$@" || test $? -eq 1`
}
fi # as_fn_arith
# as_fn_error STATUS ERROR [LINENO LOG_FD]
# ----------------------------------------
# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are
# provided, also output the error to LOG_FD, referencing LINENO. Then exit the
# script with STATUS, using 1 if that was 0.
as_fn_error ()
{
as_status=$1; test $as_status -eq 0 && as_status=1
if test "$4"; then
as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
$as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4
fi
$as_echo "$as_me: error: $2" >&2
as_fn_exit $as_status
} # as_fn_error
if expr a : '\(a\)' >/dev/null 2>&1 &&
test "X`expr 00001 : '.*\(...\)'`" = X001; then
as_expr=expr
else
as_expr=false
fi
if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then
as_basename=basename
else
as_basename=false
fi
if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then
as_dirname=dirname
else
as_dirname=false
fi
as_me=`$as_basename -- "$0" ||
$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
X"$0" : 'X\(//\)$' \| \
X"$0" : 'X\(/\)' \| . 2>/dev/null ||
$as_echo X/"$0" |
sed '/^.*\/\([^/][^/]*\)\/*$/{
s//\1/
q
}
/^X\/\(\/\/\)$/{
s//\1/
q
}
/^X\/\(\/\).*/{
s//\1/
q
}
s/.*/./; q'`
# Avoid depending upon Character Ranges.
as_cr_letters='abcdefghijklmnopqrstuvwxyz'
as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
as_cr_Letters=$as_cr_letters$as_cr_LETTERS
as_cr_digits='0123456789'
as_cr_alnum=$as_cr_Letters$as_cr_digits
as_lineno_1=$LINENO as_lineno_1a=$LINENO
as_lineno_2=$LINENO as_lineno_2a=$LINENO
eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" &&
test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || {
# Blame Lee E. McMahon (1931-1989) for sed's syntax. :-)
sed -n '
p
/[$]LINENO/=
' <$as_myself |
sed '
s/[$]LINENO.*/&-/
t lineno
b
:lineno
N
:loop
s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/
t loop
s/-\n.*//
' >$as_me.lineno &&
chmod +x "$as_me.lineno" ||
{ $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; }
# If we had to re-execute with $CONFIG_SHELL, we're ensured to have
# already done that, so ensure we don't try to do so again and fall
# in an infinite loop. This has already happened in practice.
_as_can_reexec=no; export _as_can_reexec
# Don't try to exec as it changes $[0], causing all sort of problems
# (the dirname of $[0] is not the place where we might find the
# original and so on. Autoconf is especially sensitive to this).
. "./$as_me.lineno"
# Exit status is that of the last command.
exit
}
ECHO_C= ECHO_N= ECHO_T=
case `echo -n x` in #(((((
-n*)
case `echo 'xy\c'` in
*c*) ECHO_T=' ';; # ECHO_T is single tab character.
xy) ECHO_C='\c';;
*) echo `echo ksh88 bug on AIX 6.1` > /dev/null
ECHO_T=' ';;
esac;;
*)
ECHO_N='-n';;
esac
rm -f conf$$ conf$$.exe conf$$.file
if test -d conf$$.dir; then
rm -f conf$$.dir/conf$$.file
else
rm -f conf$$.dir
mkdir conf$$.dir 2>/dev/null
fi
if (echo >conf$$.file) 2>/dev/null; then
if ln -s conf$$.file conf$$ 2>/dev/null; then
as_ln_s='ln -s'
# ... but there are two gotchas:
# 1) On MSYS, both `ln -s file dir' and `ln file dir' fail.
# 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable.
# In both cases, we have to default to `cp -pR'.
ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe ||
as_ln_s='cp -pR'
elif ln conf$$.file conf$$ 2>/dev/null; then
as_ln_s=ln
else
as_ln_s='cp -pR'
fi
else
as_ln_s='cp -pR'
fi
rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file
rmdir conf$$.dir 2>/dev/null
if mkdir -p . 2>/dev/null; then
as_mkdir_p='mkdir -p "$as_dir"'
else
test -d ./-p && rmdir ./-p
as_mkdir_p=false
fi
as_test_x='test -x'
as_executable_p=as_fn_executable_p
# Sed expression to map a string onto a valid CPP name.
as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
# Sed expression to map a string onto a valid variable name.
as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"
test -n "$DJDIR" || exec 7<&0 &1
# Name of the host.
# hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status,
# so uname gets run too.
ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q`
#
# Initializations.
#
ac_default_prefix=/usr/local
ac_clean_files=
ac_config_libobj_dir=.
LIBOBJS=
cross_compiling=no
subdirs=
MFLAGS=
MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME=
PACKAGE_TARNAME=
PACKAGE_VERSION=
PACKAGE_STRING=
PACKAGE_BUGREPORT=
PACKAGE_URL=
ac_unique_file="./mod_proxy.c"
# Factoring default headers for most tests.
ac_includes_default="\
#include
#ifdef HAVE_SYS_TYPES_H
# include
#endif
#ifdef HAVE_SYS_STAT_H
# include
#endif
#ifdef STDC_HEADERS
# include
# include
#else
# ifdef HAVE_STDLIB_H
# include
# endif
#endif
#ifdef HAVE_STRING_H
# if !defined STDC_HEADERS && defined HAVE_MEMORY_H
# include
# endif
# include
#endif
#ifdef HAVE_STRINGS_H
# include
#endif
#ifdef HAVE_INTTYPES_H
# include
#endif
#ifdef HAVE_STDINT_H
# include
#endif
#ifdef HAVE_UNISTD_H
# include
#endif"
ac_subst_vars='LTLIBOBJS
LIBOBJS
MODULE_LIBS
LIBDIRS
INCLUDES
ENABLE_TESTS
SET_MAKE
EGREP
GREP
CPP
OBJEXT
EXEEXT
ac_ct_CC
CPPFLAGS
LDFLAGS
CFLAGS
CC
target_os
target_vendor
target_cpu
target
host_os
host_vendor
host_cpu
host
build_os
build_vendor
build_cpu
build
target_alias
host_alias
build_alias
LIBS
ECHO_T
ECHO_N
ECHO_C
DEFS
mandir
localedir
libdir
psdir
pdfdir
dvidir
htmldir
infodir
docdir
oldincludedir
includedir
localstatedir
sharedstatedir
sysconfdir
datadir
datarootdir
libexecdir
sbindir
bindir
program_transform_name
prefix
exec_prefix
PACKAGE_URL
PACKAGE_BUGREPORT
PACKAGE_STRING
PACKAGE_VERSION
PACKAGE_TARNAME
PACKAGE_NAME
PATH_SEPARATOR
SHELL'
ac_subst_files=''
ac_user_opts='
enable_option_checking
with_includes
with_libraries
enable_tests
'
ac_precious_vars='build_alias
host_alias
target_alias
CC
CFLAGS
LDFLAGS
LIBS
CPPFLAGS
CPP'
# Initialize some variables set by options.
ac_init_help=
ac_init_version=false
ac_unrecognized_opts=
ac_unrecognized_sep=
# The variables have the same names as the options, with
# dashes changed to underlines.
cache_file=/dev/null
exec_prefix=NONE
no_create=
no_recursion=
prefix=NONE
program_prefix=NONE
program_suffix=NONE
program_transform_name=s,x,x,
silent=
site=
srcdir=
verbose=
x_includes=NONE
x_libraries=NONE
# Installation directory options.
# These are left unexpanded so users can "make install exec_prefix=/foo"
# and all the variables that are supposed to be based on exec_prefix
# by default will actually change.
# Use braces instead of parens because sh, perl, etc. also accept them.
# (The list follows the same order as the GNU Coding Standards.)
bindir='${exec_prefix}/bin'
sbindir='${exec_prefix}/sbin'
libexecdir='${exec_prefix}/libexec'
datarootdir='${prefix}/share'
datadir='${datarootdir}'
sysconfdir='${prefix}/etc'
sharedstatedir='${prefix}/com'
localstatedir='${prefix}/var'
includedir='${prefix}/include'
oldincludedir='/usr/include'
docdir='${datarootdir}/doc/${PACKAGE}'
infodir='${datarootdir}/info'
htmldir='${docdir}'
dvidir='${docdir}'
pdfdir='${docdir}'
psdir='${docdir}'
libdir='${exec_prefix}/lib'
localedir='${datarootdir}/locale'
mandir='${datarootdir}/man'
ac_prev=
ac_dashdash=
for ac_option
do
# If the previous option needs an argument, assign it.
if test -n "$ac_prev"; then
eval $ac_prev=\$ac_option
ac_prev=
continue
fi
case $ac_option in
*=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;;
*=) ac_optarg= ;;
*) ac_optarg=yes ;;
esac
# Accept the important Cygnus configure options, so we can diagnose typos.
case $ac_dashdash$ac_option in
--)
ac_dashdash=yes ;;
-bindir | --bindir | --bindi | --bind | --bin | --bi)
ac_prev=bindir ;;
-bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*)
bindir=$ac_optarg ;;
-build | --build | --buil | --bui | --bu)
ac_prev=build_alias ;;
-build=* | --build=* | --buil=* | --bui=* | --bu=*)
build_alias=$ac_optarg ;;
-cache-file | --cache-file | --cache-fil | --cache-fi \
| --cache-f | --cache- | --cache | --cach | --cac | --ca | --c)
ac_prev=cache_file ;;
-cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \
| --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*)
cache_file=$ac_optarg ;;
--config-cache | -C)
cache_file=config.cache ;;
-datadir | --datadir | --datadi | --datad)
ac_prev=datadir ;;
-datadir=* | --datadir=* | --datadi=* | --datad=*)
datadir=$ac_optarg ;;
-datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \
| --dataroo | --dataro | --datar)
ac_prev=datarootdir ;;
-datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \
| --dataroot=* | --dataroo=* | --dataro=* | --datar=*)
datarootdir=$ac_optarg ;;
-disable-* | --disable-*)
ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'`
# Reject names that are not valid shell variable names.
expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
as_fn_error $? "invalid feature name: $ac_useropt"
ac_useropt_orig=$ac_useropt
ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
case $ac_user_opts in
*"
"enable_$ac_useropt"
"*) ;;
*) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig"
ac_unrecognized_sep=', ';;
esac
eval enable_$ac_useropt=no ;;
-docdir | --docdir | --docdi | --doc | --do)
ac_prev=docdir ;;
-docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*)
docdir=$ac_optarg ;;
-dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv)
ac_prev=dvidir ;;
-dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*)
dvidir=$ac_optarg ;;
-enable-* | --enable-*)
ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'`
# Reject names that are not valid shell variable names.
expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
as_fn_error $? "invalid feature name: $ac_useropt"
ac_useropt_orig=$ac_useropt
ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
case $ac_user_opts in
*"
"enable_$ac_useropt"
"*) ;;
*) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig"
ac_unrecognized_sep=', ';;
esac
eval enable_$ac_useropt=\$ac_optarg ;;
-exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \
| --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \
| --exec | --exe | --ex)
ac_prev=exec_prefix ;;
-exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \
| --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \
| --exec=* | --exe=* | --ex=*)
exec_prefix=$ac_optarg ;;
-gas | --gas | --ga | --g)
# Obsolete; use --with-gas.
with_gas=yes ;;
-help | --help | --hel | --he | -h)
ac_init_help=long ;;
-help=r* | --help=r* | --hel=r* | --he=r* | -hr*)
ac_init_help=recursive ;;
-help=s* | --help=s* | --hel=s* | --he=s* | -hs*)
ac_init_help=short ;;
-host | --host | --hos | --ho)
ac_prev=host_alias ;;
-host=* | --host=* | --hos=* | --ho=*)
host_alias=$ac_optarg ;;
-htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht)
ac_prev=htmldir ;;
-htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \
| --ht=*)
htmldir=$ac_optarg ;;
-includedir | --includedir | --includedi | --included | --include \
| --includ | --inclu | --incl | --inc)
ac_prev=includedir ;;
-includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \
| --includ=* | --inclu=* | --incl=* | --inc=*)
includedir=$ac_optarg ;;
-infodir | --infodir | --infodi | --infod | --info | --inf)
ac_prev=infodir ;;
-infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*)
infodir=$ac_optarg ;;
-libdir | --libdir | --libdi | --libd)
ac_prev=libdir ;;
-libdir=* | --libdir=* | --libdi=* | --libd=*)
libdir=$ac_optarg ;;
-libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \
| --libexe | --libex | --libe)
ac_prev=libexecdir ;;
-libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \
| --libexe=* | --libex=* | --libe=*)
libexecdir=$ac_optarg ;;
-localedir | --localedir | --localedi | --localed | --locale)
ac_prev=localedir ;;
-localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*)
localedir=$ac_optarg ;;
-localstatedir | --localstatedir | --localstatedi | --localstated \
| --localstate | --localstat | --localsta | --localst | --locals)
ac_prev=localstatedir ;;
-localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \
| --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*)
localstatedir=$ac_optarg ;;
-mandir | --mandir | --mandi | --mand | --man | --ma | --m)
ac_prev=mandir ;;
-mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*)
mandir=$ac_optarg ;;
-nfp | --nfp | --nf)
# Obsolete; use --without-fp.
with_fp=no ;;
-no-create | --no-create | --no-creat | --no-crea | --no-cre \
| --no-cr | --no-c | -n)
no_create=yes ;;
-no-recursion | --no-recursion | --no-recursio | --no-recursi \
| --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r)
no_recursion=yes ;;
-oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \
| --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \
| --oldin | --oldi | --old | --ol | --o)
ac_prev=oldincludedir ;;
-oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \
| --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \
| --oldin=* | --oldi=* | --old=* | --ol=* | --o=*)
oldincludedir=$ac_optarg ;;
-prefix | --prefix | --prefi | --pref | --pre | --pr | --p)
ac_prev=prefix ;;
-prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*)
prefix=$ac_optarg ;;
-program-prefix | --program-prefix | --program-prefi | --program-pref \
| --program-pre | --program-pr | --program-p)
ac_prev=program_prefix ;;
-program-prefix=* | --program-prefix=* | --program-prefi=* \
| --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*)
program_prefix=$ac_optarg ;;
-program-suffix | --program-suffix | --program-suffi | --program-suff \
| --program-suf | --program-su | --program-s)
ac_prev=program_suffix ;;
-program-suffix=* | --program-suffix=* | --program-suffi=* \
| --program-suff=* | --program-suf=* | --program-su=* | --program-s=*)
program_suffix=$ac_optarg ;;
-program-transform-name | --program-transform-name \
| --program-transform-nam | --program-transform-na \
| --program-transform-n | --program-transform- \
| --program-transform | --program-transfor \
| --program-transfo | --program-transf \
| --program-trans | --program-tran \
| --progr-tra | --program-tr | --program-t)
ac_prev=program_transform_name ;;
-program-transform-name=* | --program-transform-name=* \
| --program-transform-nam=* | --program-transform-na=* \
| --program-transform-n=* | --program-transform-=* \
| --program-transform=* | --program-transfor=* \
| --program-transfo=* | --program-transf=* \
| --program-trans=* | --program-tran=* \
| --progr-tra=* | --program-tr=* | --program-t=*)
program_transform_name=$ac_optarg ;;
-pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd)
ac_prev=pdfdir ;;
-pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*)
pdfdir=$ac_optarg ;;
-psdir | --psdir | --psdi | --psd | --ps)
ac_prev=psdir ;;
-psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*)
psdir=$ac_optarg ;;
-q | -quiet | --quiet | --quie | --qui | --qu | --q \
| -silent | --silent | --silen | --sile | --sil)
silent=yes ;;
-sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
ac_prev=sbindir ;;
-sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
| --sbi=* | --sb=*)
sbindir=$ac_optarg ;;
-sharedstatedir | --sharedstatedir | --sharedstatedi \
| --sharedstated | --sharedstate | --sharedstat | --sharedsta \
| --sharedst | --shareds | --shared | --share | --shar \
| --sha | --sh)
ac_prev=sharedstatedir ;;
-sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \
| --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \
| --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \
| --sha=* | --sh=*)
sharedstatedir=$ac_optarg ;;
-site | --site | --sit)
ac_prev=site ;;
-site=* | --site=* | --sit=*)
site=$ac_optarg ;;
-srcdir | --srcdir | --srcdi | --srcd | --src | --sr)
ac_prev=srcdir ;;
-srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*)
srcdir=$ac_optarg ;;
-sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \
| --syscon | --sysco | --sysc | --sys | --sy)
ac_prev=sysconfdir ;;
-sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \
| --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*)
sysconfdir=$ac_optarg ;;
-target | --target | --targe | --targ | --tar | --ta | --t)
ac_prev=target_alias ;;
-target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*)
target_alias=$ac_optarg ;;
-v | -verbose | --verbose | --verbos | --verbo | --verb)
verbose=yes ;;
-version | --version | --versio | --versi | --vers | -V)
ac_init_version=: ;;
-with-* | --with-*)
ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'`
# Reject names that are not valid shell variable names.
expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
as_fn_error $? "invalid package name: $ac_useropt"
ac_useropt_orig=$ac_useropt
ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
case $ac_user_opts in
*"
"with_$ac_useropt"
"*) ;;
*) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig"
ac_unrecognized_sep=', ';;
esac
eval with_$ac_useropt=\$ac_optarg ;;
-without-* | --without-*)
ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'`
# Reject names that are not valid shell variable names.
expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
as_fn_error $? "invalid package name: $ac_useropt"
ac_useropt_orig=$ac_useropt
ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
case $ac_user_opts in
*"
"with_$ac_useropt"
"*) ;;
*) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig"
ac_unrecognized_sep=', ';;
esac
eval with_$ac_useropt=no ;;
--x)
# Obsolete; use --with-x.
with_x=yes ;;
-x-includes | --x-includes | --x-include | --x-includ | --x-inclu \
| --x-incl | --x-inc | --x-in | --x-i)
ac_prev=x_includes ;;
-x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \
| --x-incl=* | --x-inc=* | --x-in=* | --x-i=*)
x_includes=$ac_optarg ;;
-x-libraries | --x-libraries | --x-librarie | --x-librari \
| --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l)
ac_prev=x_libraries ;;
-x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \
| --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*)
x_libraries=$ac_optarg ;;
-*) as_fn_error $? "unrecognized option: \`$ac_option'
Try \`$0 --help' for more information"
;;
*=*)
ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='`
# Reject names that are not valid shell variable names.
case $ac_envvar in #(
'' | [0-9]* | *[!_$as_cr_alnum]* )
as_fn_error $? "invalid variable name: \`$ac_envvar'" ;;
esac
eval $ac_envvar=\$ac_optarg
export $ac_envvar ;;
*)
# FIXME: should be removed in autoconf 3.0.
$as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2
expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null &&
$as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2
: "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}"
;;
esac
done
if test -n "$ac_prev"; then
ac_option=--`echo $ac_prev | sed 's/_/-/g'`
as_fn_error $? "missing argument to $ac_option"
fi
if test -n "$ac_unrecognized_opts"; then
case $enable_option_checking in
no) ;;
fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;;
*) $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;;
esac
fi
# Check all directory arguments for consistency.
for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \
datadir sysconfdir sharedstatedir localstatedir includedir \
oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
libdir localedir mandir
do
eval ac_val=\$$ac_var
# Remove trailing slashes.
case $ac_val in
*/ )
ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'`
eval $ac_var=\$ac_val;;
esac
# Be sure to have absolute directory names.
case $ac_val in
[\\/$]* | ?:[\\/]* ) continue;;
NONE | '' ) case $ac_var in *prefix ) continue;; esac;;
esac
as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val"
done
# There might be people who depend on the old broken behavior: `$host'
# used to hold the argument of --host etc.
# FIXME: To remove some day.
build=$build_alias
host=$host_alias
target=$target_alias
# FIXME: To remove some day.
if test "x$host_alias" != x; then
if test "x$build_alias" = x; then
cross_compiling=maybe
elif test "x$build_alias" != "x$host_alias"; then
cross_compiling=yes
fi
fi
ac_tool_prefix=
test -n "$host_alias" && ac_tool_prefix=$host_alias-
test "$silent" = yes && exec 6>/dev/null
ac_pwd=`pwd` && test -n "$ac_pwd" &&
ac_ls_di=`ls -di .` &&
ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` ||
as_fn_error $? "working directory cannot be determined"
test "X$ac_ls_di" = "X$ac_pwd_ls_di" ||
as_fn_error $? "pwd does not report name of working directory"
# Find the source files, if location was not specified.
if test -z "$srcdir"; then
ac_srcdir_defaulted=yes
# Try the directory containing this script, then the parent directory.
ac_confdir=`$as_dirname -- "$as_myself" ||
$as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
X"$as_myself" : 'X\(//\)[^/]' \| \
X"$as_myself" : 'X\(//\)$' \| \
X"$as_myself" : 'X\(/\)' \| . 2>/dev/null ||
$as_echo X"$as_myself" |
sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
s//\1/
q
}
/^X\(\/\/\)[^/].*/{
s//\1/
q
}
/^X\(\/\/\)$/{
s//\1/
q
}
/^X\(\/\).*/{
s//\1/
q
}
s/.*/./; q'`
srcdir=$ac_confdir
if test ! -r "$srcdir/$ac_unique_file"; then
srcdir=..
fi
else
ac_srcdir_defaulted=no
fi
if test ! -r "$srcdir/$ac_unique_file"; then
test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .."
as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir"
fi
ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work"
ac_abs_confdir=`(
cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg"
pwd)`
# When building in place, set srcdir=.
if test "$ac_abs_confdir" = "$ac_pwd"; then
srcdir=.
fi
# Remove unnecessary trailing slashes from srcdir.
# Double slashes in file names in object file debugging info
# mess up M-x gdb in Emacs.
case $srcdir in
*/) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;;
esac
for ac_var in $ac_precious_vars; do
eval ac_env_${ac_var}_set=\${${ac_var}+set}
eval ac_env_${ac_var}_value=\$${ac_var}
eval ac_cv_env_${ac_var}_set=\${${ac_var}+set}
eval ac_cv_env_${ac_var}_value=\$${ac_var}
done
#
# Report the --help message.
#
if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
\`configure' configures this package to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
To assign environment variables (e.g., CC, CFLAGS...), specify them as
VAR=VALUE. See below for descriptions of some of the useful variables.
Defaults for the options are specified in brackets.
Configuration:
-h, --help display this help and exit
--help=short display options specific to this package
--help=recursive display the short help of all the included packages
-V, --version display version information and exit
-q, --quiet, --silent do not print \`checking ...' messages
--cache-file=FILE cache test results in FILE [disabled]
-C, --config-cache alias for \`--cache-file=config.cache'
-n, --no-create do not create output files
--srcdir=DIR find the sources in DIR [configure dir or \`..']
Installation directories:
--prefix=PREFIX install architecture-independent files in PREFIX
[$ac_default_prefix]
--exec-prefix=EPREFIX install architecture-dependent files in EPREFIX
[PREFIX]
By default, \`make install' will install all the files in
\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify
an installation prefix other than \`$ac_default_prefix' using \`--prefix',
for instance \`--prefix=\$HOME'.
For better control, use the options below.
Fine tuning of the installation directories:
--bindir=DIR user executables [EPREFIX/bin]
--sbindir=DIR system admin executables [EPREFIX/sbin]
--libexecdir=DIR program executables [EPREFIX/libexec]
--sysconfdir=DIR read-only single-machine data [PREFIX/etc]
--sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com]
--localstatedir=DIR modifiable single-machine data [PREFIX/var]
--libdir=DIR object code libraries [EPREFIX/lib]
--includedir=DIR C header files [PREFIX/include]
--oldincludedir=DIR C header files for non-gcc [/usr/include]
--datarootdir=DIR read-only arch.-independent data root [PREFIX/share]
--datadir=DIR read-only architecture-independent data [DATAROOTDIR]
--infodir=DIR info documentation [DATAROOTDIR/info]
--localedir=DIR locale-dependent data [DATAROOTDIR/locale]
--mandir=DIR man documentation [DATAROOTDIR/man]
--docdir=DIR documentation root [DATAROOTDIR/doc/PACKAGE]
--htmldir=DIR html documentation [DOCDIR]
--dvidir=DIR dvi documentation [DOCDIR]
--pdfdir=DIR pdf documentation [DOCDIR]
--psdir=DIR ps documentation [DOCDIR]
_ACEOF
cat <<\_ACEOF
System types:
--build=BUILD configure for building on BUILD [guessed]
--host=HOST cross-compile to build programs to run on HOST [BUILD]
--target=TARGET configure for building compilers for TARGET [HOST]
_ACEOF
fi
if test -n "$ac_init_help"; then
cat <<\_ACEOF
Optional Features:
--disable-option-checking ignore unrecognized --enable/--with options
--disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no)
--enable-FEATURE[=ARG] include FEATURE [ARG=yes]
--enable-tests enable unit tests (default=no)
Optional Packages:
--with-PACKAGE[=ARG] use PACKAGE [ARG=yes]
--without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no)
--with-includes=LIST add additional include paths to proftpd. LIST is a
colon-separated list of include paths to add e.g.
--with-includes=/some/mysql/include:/my/include
--with-libraries=LIST add additional library paths to proftpd. LIST is a
colon-separated list of include paths to add e.g.
--with-libraries=/some/mysql/libdir:/my/libs
Some influential environment variables:
CC C compiler command
CFLAGS C compiler flags
LDFLAGS linker flags, e.g. -L if you have libraries in a
nonstandard directory
LIBS libraries to pass to the linker, e.g. -l
CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I if
you have headers in a nonstandard directory
CPP C preprocessor
Use these variables to override the choices made by `configure' or to help
it to find libraries and programs with nonstandard names/locations.
Report bugs to the package provider.
_ACEOF
ac_status=$?
fi
if test "$ac_init_help" = "recursive"; then
# If there are subdirs, report their specific --help.
for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue
test -d "$ac_dir" ||
{ cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } ||
continue
ac_builddir=.
case "$ac_dir" in
.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;;
*)
ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'`
# A ".." for each directory in $ac_dir_suffix.
ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'`
case $ac_top_builddir_sub in
"") ac_top_builddir_sub=. ac_top_build_prefix= ;;
*) ac_top_build_prefix=$ac_top_builddir_sub/ ;;
esac ;;
esac
ac_abs_top_builddir=$ac_pwd
ac_abs_builddir=$ac_pwd$ac_dir_suffix
# for backward compatibility:
ac_top_builddir=$ac_top_build_prefix
case $srcdir in
.) # We are building in place.
ac_srcdir=.
ac_top_srcdir=$ac_top_builddir_sub
ac_abs_top_srcdir=$ac_pwd ;;
[\\/]* | ?:[\\/]* ) # Absolute name.
ac_srcdir=$srcdir$ac_dir_suffix;
ac_top_srcdir=$srcdir
ac_abs_top_srcdir=$srcdir ;;
*) # Relative name.
ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix
ac_top_srcdir=$ac_top_build_prefix$srcdir
ac_abs_top_srcdir=$ac_pwd/$srcdir ;;
esac
ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix
cd "$ac_dir" || { ac_status=$?; continue; }
# Check for guested configure.
if test -f "$ac_srcdir/configure.gnu"; then
echo &&
$SHELL "$ac_srcdir/configure.gnu" --help=recursive
elif test -f "$ac_srcdir/configure"; then
echo &&
$SHELL "$ac_srcdir/configure" --help=recursive
else
$as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2
fi || ac_status=$?
cd "$ac_pwd" || { ac_status=$?; break; }
done
fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
configure
generated by GNU Autoconf 2.69
Copyright (C) 2012 Free Software Foundation, Inc.
This configure script is free software; the Free Software Foundation
gives unlimited permission to copy, distribute and modify it.
_ACEOF
exit
fi
## ------------------------ ##
## Autoconf initialization. ##
## ------------------------ ##
# ac_fn_c_try_compile LINENO
# --------------------------
# Try to compile conftest.$ac_ext, and return whether this succeeded.
ac_fn_c_try_compile ()
{
as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
rm -f conftest.$ac_objext
if { { ac_try="$ac_compile"
case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
$as_echo "$ac_try_echo"; } >&5
(eval "$ac_compile") 2>conftest.err
ac_status=$?
if test -s conftest.err; then
grep -v '^ *+' conftest.err >conftest.er1
cat conftest.er1 >&5
mv -f conftest.er1 conftest.err
fi
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; } && {
test -z "$ac_c_werror_flag" ||
test ! -s conftest.err
} && test -s conftest.$ac_objext; then :
ac_retval=0
else
$as_echo "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5
ac_retval=1
fi
eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
as_fn_set_status $ac_retval
} # ac_fn_c_try_compile
# ac_fn_c_try_cpp LINENO
# ----------------------
# Try to preprocess conftest.$ac_ext, and return whether this succeeded.
ac_fn_c_try_cpp ()
{
as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
if { { ac_try="$ac_cpp conftest.$ac_ext"
case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
$as_echo "$ac_try_echo"; } >&5
(eval "$ac_cpp conftest.$ac_ext") 2>conftest.err
ac_status=$?
if test -s conftest.err; then
grep -v '^ *+' conftest.err >conftest.er1
cat conftest.er1 >&5
mv -f conftest.er1 conftest.err
fi
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; } > conftest.i && {
test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" ||
test ! -s conftest.err
}; then :
ac_retval=0
else
$as_echo "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5
ac_retval=1
fi
eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
as_fn_set_status $ac_retval
} # ac_fn_c_try_cpp
# ac_fn_c_check_header_mongrel LINENO HEADER VAR INCLUDES
# -------------------------------------------------------
# Tests whether HEADER exists, giving a warning if it cannot be compiled using
# the include files in INCLUDES and setting the cache variable VAR
# accordingly.
ac_fn_c_check_header_mongrel ()
{
as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
if eval \${$3+:} false; then :
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
$as_echo_n "checking for $2... " >&6; }
if eval \${$3+:} false; then :
$as_echo_n "(cached) " >&6
fi
eval ac_res=\$$3
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
$as_echo "$ac_res" >&6; }
else
# Is the header compilable?
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 usability" >&5
$as_echo_n "checking $2 usability... " >&6; }
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
$4
#include <$2>
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
ac_header_compiler=yes
else
ac_header_compiler=no
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_compiler" >&5
$as_echo "$ac_header_compiler" >&6; }
# Is the header present?
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 presence" >&5
$as_echo_n "checking $2 presence... " >&6; }
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include <$2>
_ACEOF
if ac_fn_c_try_cpp "$LINENO"; then :
ac_header_preproc=yes
else
ac_header_preproc=no
fi
rm -f conftest.err conftest.i conftest.$ac_ext
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_preproc" >&5
$as_echo "$ac_header_preproc" >&6; }
# So? What about this header?
case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in #((
yes:no: )
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&5
$as_echo "$as_me: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&2;}
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5
$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;}
;;
no:yes:* )
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: present but cannot be compiled" >&5
$as_echo "$as_me: WARNING: $2: present but cannot be compiled" >&2;}
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: check for missing prerequisite headers?" >&5
$as_echo "$as_me: WARNING: $2: check for missing prerequisite headers?" >&2;}
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: see the Autoconf documentation" >&5
$as_echo "$as_me: WARNING: $2: see the Autoconf documentation" >&2;}
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&5
$as_echo "$as_me: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&2;}
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5
$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;}
;;
esac
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
$as_echo_n "checking for $2... " >&6; }
if eval \${$3+:} false; then :
$as_echo_n "(cached) " >&6
else
eval "$3=\$ac_header_compiler"
fi
eval ac_res=\$$3
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
$as_echo "$ac_res" >&6; }
fi
eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
} # ac_fn_c_check_header_mongrel
# ac_fn_c_try_run LINENO
# ----------------------
# Try to link conftest.$ac_ext, and return whether this succeeded. Assumes
# that executables *can* be run.
ac_fn_c_try_run ()
{
as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
if { { ac_try="$ac_link"
case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
$as_echo "$ac_try_echo"; } >&5
(eval "$ac_link") 2>&5
ac_status=$?
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; } && { ac_try='./conftest$ac_exeext'
{ { case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
$as_echo "$ac_try_echo"; } >&5
(eval "$ac_try") 2>&5
ac_status=$?
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; }; then :
ac_retval=0
else
$as_echo "$as_me: program exited with status $ac_status" >&5
$as_echo "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5
ac_retval=$ac_status
fi
rm -rf conftest.dSYM conftest_ipa8_conftest.oo
eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
as_fn_set_status $ac_retval
} # ac_fn_c_try_run
# ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES
# -------------------------------------------------------
# Tests whether HEADER exists and can be compiled using the include files in
# INCLUDES, setting the cache variable VAR accordingly.
ac_fn_c_check_header_compile ()
{
as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
$as_echo_n "checking for $2... " >&6; }
if eval \${$3+:} false; then :
$as_echo_n "(cached) " >&6
else
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
$4
#include <$2>
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
eval "$3=yes"
else
eval "$3=no"
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
fi
eval ac_res=\$$3
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
$as_echo "$ac_res" >&6; }
eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
} # ac_fn_c_check_header_compile
# ac_fn_c_try_link LINENO
# -----------------------
# Try to link conftest.$ac_ext, and return whether this succeeded.
ac_fn_c_try_link ()
{
as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
rm -f conftest.$ac_objext conftest$ac_exeext
if { { ac_try="$ac_link"
case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
$as_echo "$ac_try_echo"; } >&5
(eval "$ac_link") 2>conftest.err
ac_status=$?
if test -s conftest.err; then
grep -v '^ *+' conftest.err >conftest.er1
cat conftest.er1 >&5
mv -f conftest.er1 conftest.err
fi
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; } && {
test -z "$ac_c_werror_flag" ||
test ! -s conftest.err
} && test -s conftest$ac_exeext && {
test "$cross_compiling" = yes ||
test -x conftest$ac_exeext
}; then :
ac_retval=0
else
$as_echo "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5
ac_retval=1
fi
# Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information
# created by the PGI compiler (conftest_ipa8_conftest.oo), as it would
# interfere with the next link command; also delete a directory that is
# left behind by Apple's compiler. We do this before executing the actions.
rm -rf conftest.dSYM conftest_ipa8_conftest.oo
eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
as_fn_set_status $ac_retval
} # ac_fn_c_try_link
# ac_fn_c_check_func LINENO FUNC VAR
# ----------------------------------
# Tests whether FUNC exists, setting the cache variable VAR accordingly
ac_fn_c_check_func ()
{
as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
$as_echo_n "checking for $2... " >&6; }
if eval \${$3+:} false; then :
$as_echo_n "(cached) " >&6
else
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
/* Define $2 to an innocuous variant, in case declares $2.
For example, HP-UX 11i declares gettimeofday. */
#define $2 innocuous_$2
/* System header to define __stub macros and hopefully few prototypes,
which can conflict with char $2 (); below.
Prefer to if __STDC__ is defined, since
exists even on freestanding compilers. */
#ifdef __STDC__
# include
#else
# include
#endif
#undef $2
/* Override any GCC internal prototype to avoid an error.
Use char because int might match the return type of a GCC
builtin and then its argument prototype would still apply. */
#ifdef __cplusplus
extern "C"
#endif
char $2 ();
/* The GNU C library defines this for functions which it implements
to always fail with ENOSYS. Some functions are actually named
something starting with __ and the normal name is an alias. */
#if defined __stub_$2 || defined __stub___$2
choke me
#endif
int
main ()
{
return $2 ();
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"; then :
eval "$3=yes"
else
eval "$3=no"
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
fi
eval ac_res=\$$3
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
$as_echo "$ac_res" >&6; }
eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
} # ac_fn_c_check_func
cat >config.log <<_ACEOF
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
It was created by $as_me, which was
generated by GNU Autoconf 2.69. Invocation command line was
$ $0 $@
_ACEOF
exec 5>>config.log
{
cat <<_ASUNAME
## --------- ##
## Platform. ##
## --------- ##
hostname = `(hostname || uname -n) 2>/dev/null | sed 1q`
uname -m = `(uname -m) 2>/dev/null || echo unknown`
uname -r = `(uname -r) 2>/dev/null || echo unknown`
uname -s = `(uname -s) 2>/dev/null || echo unknown`
uname -v = `(uname -v) 2>/dev/null || echo unknown`
/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown`
/bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown`
/bin/arch = `(/bin/arch) 2>/dev/null || echo unknown`
/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown`
/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown`
/usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown`
/bin/machine = `(/bin/machine) 2>/dev/null || echo unknown`
/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown`
/bin/universe = `(/bin/universe) 2>/dev/null || echo unknown`
_ASUNAME
as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
for as_dir in $PATH
do
IFS=$as_save_IFS
test -z "$as_dir" && as_dir=.
$as_echo "PATH: $as_dir"
done
IFS=$as_save_IFS
} >&5
cat >&5 <<_ACEOF
## ----------- ##
## Core tests. ##
## ----------- ##
_ACEOF
# Keep a trace of the command line.
# Strip out --no-create and --no-recursion so they do not pile up.
# Strip out --silent because we don't want to record it for future runs.
# Also quote any args containing shell meta-characters.
# Make two passes to allow for proper duplicate-argument suppression.
ac_configure_args=
ac_configure_args0=
ac_configure_args1=
ac_must_keep_next=false
for ac_pass in 1 2
do
for ac_arg
do
case $ac_arg in
-no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;;
-q | -quiet | --quiet | --quie | --qui | --qu | --q \
| -silent | --silent | --silen | --sile | --sil)
continue ;;
*\'*)
ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;;
esac
case $ac_pass in
1) as_fn_append ac_configure_args0 " '$ac_arg'" ;;
2)
as_fn_append ac_configure_args1 " '$ac_arg'"
if test $ac_must_keep_next = true; then
ac_must_keep_next=false # Got value, back to normal.
else
case $ac_arg in
*=* | --config-cache | -C | -disable-* | --disable-* \
| -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \
| -q | -quiet | --q* | -silent | --sil* | -v | -verb* \
| -with-* | --with-* | -without-* | --without-* | --x)
case "$ac_configure_args0 " in
"$ac_configure_args1"*" '$ac_arg' "* ) continue ;;
esac
;;
-* ) ac_must_keep_next=true ;;
esac
fi
as_fn_append ac_configure_args " '$ac_arg'"
;;
esac
done
done
{ ac_configure_args0=; unset ac_configure_args0;}
{ ac_configure_args1=; unset ac_configure_args1;}
# When interrupted or exit'd, cleanup temporary files, and complete
# config.log. We remove comments because anyway the quotes in there
# would cause problems or look ugly.
# WARNING: Use '\'' to represent an apostrophe within the trap.
# WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug.
trap 'exit_status=$?
# Save into config.log some information that might help in debugging.
{
echo
$as_echo "## ---------------- ##
## Cache variables. ##
## ---------------- ##"
echo
# The following way of writing the cache mishandles newlines in values,
(
for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do
eval ac_val=\$$ac_var
case $ac_val in #(
*${as_nl}*)
case $ac_var in #(
*_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5
$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
esac
case $ac_var in #(
_ | IFS | as_nl) ;; #(
BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #(
*) { eval $ac_var=; unset $ac_var;} ;;
esac ;;
esac
done
(set) 2>&1 |
case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #(
*${as_nl}ac_space=\ *)
sed -n \
"s/'\''/'\''\\\\'\'''\''/g;
s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p"
;; #(
*)
sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p"
;;
esac |
sort
)
echo
$as_echo "## ----------------- ##
## Output variables. ##
## ----------------- ##"
echo
for ac_var in $ac_subst_vars
do
eval ac_val=\$$ac_var
case $ac_val in
*\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;;
esac
$as_echo "$ac_var='\''$ac_val'\''"
done | sort
echo
if test -n "$ac_subst_files"; then
$as_echo "## ------------------- ##
## File substitutions. ##
## ------------------- ##"
echo
for ac_var in $ac_subst_files
do
eval ac_val=\$$ac_var
case $ac_val in
*\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;;
esac
$as_echo "$ac_var='\''$ac_val'\''"
done | sort
echo
fi
if test -s confdefs.h; then
$as_echo "## ----------- ##
## confdefs.h. ##
## ----------- ##"
echo
cat confdefs.h
echo
fi
test "$ac_signal" != 0 &&
$as_echo "$as_me: caught signal $ac_signal"
$as_echo "$as_me: exit $exit_status"
} >&5
rm -f core *.core core.conftest.* &&
rm -f -r conftest* confdefs* conf$$* $ac_clean_files &&
exit $exit_status
' 0
for ac_signal in 1 2 13 15; do
trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal
done
ac_signal=0
# confdefs.h avoids OS command line length limits that DEFS can exceed.
rm -f -r conftest* confdefs.h
$as_echo "/* confdefs.h */" > confdefs.h
# Predefined preprocessor variables.
cat >>confdefs.h <<_ACEOF
#define PACKAGE_NAME "$PACKAGE_NAME"
_ACEOF
cat >>confdefs.h <<_ACEOF
#define PACKAGE_TARNAME "$PACKAGE_TARNAME"
_ACEOF
cat >>confdefs.h <<_ACEOF
#define PACKAGE_VERSION "$PACKAGE_VERSION"
_ACEOF
cat >>confdefs.h <<_ACEOF
#define PACKAGE_STRING "$PACKAGE_STRING"
_ACEOF
cat >>confdefs.h <<_ACEOF
#define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT"
_ACEOF
cat >>confdefs.h <<_ACEOF
#define PACKAGE_URL "$PACKAGE_URL"
_ACEOF
# Let the site file select an alternate cache file if it wants to.
# Prefer an explicitly selected file to automatically selected ones.
ac_site_file1=NONE
ac_site_file2=NONE
if test -n "$CONFIG_SITE"; then
# We do not want a PATH search for config.site.
case $CONFIG_SITE in #((
-*) ac_site_file1=./$CONFIG_SITE;;
*/*) ac_site_file1=$CONFIG_SITE;;
*) ac_site_file1=./$CONFIG_SITE;;
esac
elif test "x$prefix" != xNONE; then
ac_site_file1=$prefix/share/config.site
ac_site_file2=$prefix/etc/config.site
else
ac_site_file1=$ac_default_prefix/share/config.site
ac_site_file2=$ac_default_prefix/etc/config.site
fi
for ac_site_file in "$ac_site_file1" "$ac_site_file2"
do
test "x$ac_site_file" = xNONE && continue
if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5
$as_echo "$as_me: loading site script $ac_site_file" >&6;}
sed 's/^/| /' "$ac_site_file" >&5
. "$ac_site_file" \
|| { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
as_fn_error $? "failed to load site script $ac_site_file
See \`config.log' for more details" "$LINENO" 5; }
fi
done
if test -r "$cache_file"; then
# Some versions of bash will fail to source /dev/null (special files
# actually), so we avoid doing that. DJGPP emulates it as a regular file.
if test /dev/null != "$cache_file" && test -f "$cache_file"; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5
$as_echo "$as_me: loading cache $cache_file" >&6;}
case $cache_file in
[\\/]* | ?:[\\/]* ) . "$cache_file";;
*) . "./$cache_file";;
esac
fi
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5
$as_echo "$as_me: creating cache $cache_file" >&6;}
>$cache_file
fi
# Check that the precious variables saved in the cache have kept the same
# value.
ac_cache_corrupted=false
for ac_var in $ac_precious_vars; do
eval ac_old_set=\$ac_cv_env_${ac_var}_set
eval ac_new_set=\$ac_env_${ac_var}_set
eval ac_old_val=\$ac_cv_env_${ac_var}_value
eval ac_new_val=\$ac_env_${ac_var}_value
case $ac_old_set,$ac_new_set in
set,)
{ $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5
$as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;}
ac_cache_corrupted=: ;;
,set)
{ $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5
$as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;}
ac_cache_corrupted=: ;;
,);;
*)
if test "x$ac_old_val" != "x$ac_new_val"; then
# differences in whitespace do not lead to failure.
ac_old_val_w=`echo x $ac_old_val`
ac_new_val_w=`echo x $ac_new_val`
if test "$ac_old_val_w" != "$ac_new_val_w"; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5
$as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;}
ac_cache_corrupted=:
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5
$as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;}
eval $ac_var=\$ac_old_val
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5
$as_echo "$as_me: former value: \`$ac_old_val'" >&2;}
{ $as_echo "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5
$as_echo "$as_me: current value: \`$ac_new_val'" >&2;}
fi;;
esac
# Pass precious variables to config.status.
if test "$ac_new_set" = set; then
case $ac_new_val in
*\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;;
*) ac_arg=$ac_var=$ac_new_val ;;
esac
case " $ac_configure_args " in
*" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy.
*) as_fn_append ac_configure_args " '$ac_arg'" ;;
esac
fi
done
if $ac_cache_corrupted; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
{ $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5
$as_echo "$as_me: error: changes in the environment can compromise the build" >&2;}
as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5
fi
## -------------------- ##
## Main body of script. ##
## -------------------- ##
ac_ext=c
ac_cpp='$CPP $CPPFLAGS'
ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
ac_compiler_gnu=$ac_cv_c_compiler_gnu
ac_aux_dir=
for ac_dir in "$srcdir" "$srcdir/.." "$srcdir/../.."; do
if test -f "$ac_dir/install-sh"; then
ac_aux_dir=$ac_dir
ac_install_sh="$ac_aux_dir/install-sh -c"
break
elif test -f "$ac_dir/install.sh"; then
ac_aux_dir=$ac_dir
ac_install_sh="$ac_aux_dir/install.sh -c"
break
elif test -f "$ac_dir/shtool"; then
ac_aux_dir=$ac_dir
ac_install_sh="$ac_aux_dir/shtool install -c"
break
fi
done
if test -z "$ac_aux_dir"; then
as_fn_error $? "cannot find install-sh, install.sh, or shtool in \"$srcdir\" \"$srcdir/..\" \"$srcdir/../..\"" "$LINENO" 5
fi
# These three variables are undocumented and unsupported,
# and are intended to be withdrawn in a future Autoconf release.
# They can cause serious problems if a builder's source tree is in a directory
# whose full name contains unusual characters.
ac_config_guess="$SHELL $ac_aux_dir/config.guess" # Please don't use this var.
ac_config_sub="$SHELL $ac_aux_dir/config.sub" # Please don't use this var.
ac_configure="$SHELL $ac_aux_dir/configure" # Please don't use this var.
# Make sure we can run config.sub.
$SHELL "$ac_aux_dir/config.sub" sun4 >/dev/null 2>&1 ||
as_fn_error $? "cannot run $SHELL $ac_aux_dir/config.sub" "$LINENO" 5
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking build system type" >&5
$as_echo_n "checking build system type... " >&6; }
if ${ac_cv_build+:} false; then :
$as_echo_n "(cached) " >&6
else
ac_build_alias=$build_alias
test "x$ac_build_alias" = x &&
ac_build_alias=`$SHELL "$ac_aux_dir/config.guess"`
test "x$ac_build_alias" = x &&
as_fn_error $? "cannot guess build type; you must specify one" "$LINENO" 5
ac_cv_build=`$SHELL "$ac_aux_dir/config.sub" $ac_build_alias` ||
as_fn_error $? "$SHELL $ac_aux_dir/config.sub $ac_build_alias failed" "$LINENO" 5
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_build" >&5
$as_echo "$ac_cv_build" >&6; }
case $ac_cv_build in
*-*-*) ;;
*) as_fn_error $? "invalid value of canonical build" "$LINENO" 5;;
esac
build=$ac_cv_build
ac_save_IFS=$IFS; IFS='-'
set x $ac_cv_build
shift
build_cpu=$1
build_vendor=$2
shift; shift
# Remember, the first character of IFS is used to create $*,
# except with old shells:
build_os=$*
IFS=$ac_save_IFS
case $build_os in *\ *) build_os=`echo "$build_os" | sed 's/ /-/g'`;; esac
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking host system type" >&5
$as_echo_n "checking host system type... " >&6; }
if ${ac_cv_host+:} false; then :
$as_echo_n "(cached) " >&6
else
if test "x$host_alias" = x; then
ac_cv_host=$ac_cv_build
else
ac_cv_host=`$SHELL "$ac_aux_dir/config.sub" $host_alias` ||
as_fn_error $? "$SHELL $ac_aux_dir/config.sub $host_alias failed" "$LINENO" 5
fi
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_host" >&5
$as_echo "$ac_cv_host" >&6; }
case $ac_cv_host in
*-*-*) ;;
*) as_fn_error $? "invalid value of canonical host" "$LINENO" 5;;
esac
host=$ac_cv_host
ac_save_IFS=$IFS; IFS='-'
set x $ac_cv_host
shift
host_cpu=$1
host_vendor=$2
shift; shift
# Remember, the first character of IFS is used to create $*,
# except with old shells:
host_os=$*
IFS=$ac_save_IFS
case $host_os in *\ *) host_os=`echo "$host_os" | sed 's/ /-/g'`;; esac
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking target system type" >&5
$as_echo_n "checking target system type... " >&6; }
if ${ac_cv_target+:} false; then :
$as_echo_n "(cached) " >&6
else
if test "x$target_alias" = x; then
ac_cv_target=$ac_cv_host
else
ac_cv_target=`$SHELL "$ac_aux_dir/config.sub" $target_alias` ||
as_fn_error $? "$SHELL $ac_aux_dir/config.sub $target_alias failed" "$LINENO" 5
fi
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_target" >&5
$as_echo "$ac_cv_target" >&6; }
case $ac_cv_target in
*-*-*) ;;
*) as_fn_error $? "invalid value of canonical target" "$LINENO" 5;;
esac
target=$ac_cv_target
ac_save_IFS=$IFS; IFS='-'
set x $ac_cv_target
shift
target_cpu=$1
target_vendor=$2
shift; shift
# Remember, the first character of IFS is used to create $*,
# except with old shells:
target_os=$*
IFS=$ac_save_IFS
case $target_os in *\ *) target_os=`echo "$target_os" | sed 's/ /-/g'`;; esac
# The aliases save the names the user supplied, while $host etc.
# will get canonicalized.
test -n "$target_alias" &&
test "$program_prefix$program_suffix$program_transform_name" = \
NONENONEs,x,x, &&
program_prefix=${target_alias}-
ostype=`echo $build_os | sed 's/\..*$//g' | sed 's/-.*//g' | tr abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ`
ac_ext=c
ac_cpp='$CPP $CPPFLAGS'
ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
ac_compiler_gnu=$ac_cv_c_compiler_gnu
if test -n "$ac_tool_prefix"; then
# Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args.
set dummy ${ac_tool_prefix}gcc; ac_word=$2
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
$as_echo_n "checking for $ac_word... " >&6; }
if ${ac_cv_prog_CC+:} false; then :
$as_echo_n "(cached) " >&6
else
if test -n "$CC"; then
ac_cv_prog_CC="$CC" # Let the user override the test.
else
as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
for as_dir in $PATH
do
IFS=$as_save_IFS
test -z "$as_dir" && as_dir=.
for ac_exec_ext in '' $ac_executable_extensions; do
if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
ac_cv_prog_CC="${ac_tool_prefix}gcc"
$as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
break 2
fi
done
done
IFS=$as_save_IFS
fi
fi
CC=$ac_cv_prog_CC
if test -n "$CC"; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
$as_echo "$CC" >&6; }
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
fi
fi
if test -z "$ac_cv_prog_CC"; then
ac_ct_CC=$CC
# Extract the first word of "gcc", so it can be a program name with args.
set dummy gcc; ac_word=$2
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
$as_echo_n "checking for $ac_word... " >&6; }
if ${ac_cv_prog_ac_ct_CC+:} false; then :
$as_echo_n "(cached) " >&6
else
if test -n "$ac_ct_CC"; then
ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
else
as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
for as_dir in $PATH
do
IFS=$as_save_IFS
test -z "$as_dir" && as_dir=.
for ac_exec_ext in '' $ac_executable_extensions; do
if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
ac_cv_prog_ac_ct_CC="gcc"
$as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
break 2
fi
done
done
IFS=$as_save_IFS
fi
fi
ac_ct_CC=$ac_cv_prog_ac_ct_CC
if test -n "$ac_ct_CC"; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
$as_echo "$ac_ct_CC" >&6; }
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
fi
if test "x$ac_ct_CC" = x; then
CC=""
else
case $cross_compiling:$ac_tool_warned in
yes:)
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
ac_tool_warned=yes ;;
esac
CC=$ac_ct_CC
fi
else
CC="$ac_cv_prog_CC"
fi
if test -z "$CC"; then
if test -n "$ac_tool_prefix"; then
# Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args.
set dummy ${ac_tool_prefix}cc; ac_word=$2
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
$as_echo_n "checking for $ac_word... " >&6; }
if ${ac_cv_prog_CC+:} false; then :
$as_echo_n "(cached) " >&6
else
if test -n "$CC"; then
ac_cv_prog_CC="$CC" # Let the user override the test.
else
as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
for as_dir in $PATH
do
IFS=$as_save_IFS
test -z "$as_dir" && as_dir=.
for ac_exec_ext in '' $ac_executable_extensions; do
if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
ac_cv_prog_CC="${ac_tool_prefix}cc"
$as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
break 2
fi
done
done
IFS=$as_save_IFS
fi
fi
CC=$ac_cv_prog_CC
if test -n "$CC"; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
$as_echo "$CC" >&6; }
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
fi
fi
fi
if test -z "$CC"; then
# Extract the first word of "cc", so it can be a program name with args.
set dummy cc; ac_word=$2
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
$as_echo_n "checking for $ac_word... " >&6; }
if ${ac_cv_prog_CC+:} false; then :
$as_echo_n "(cached) " >&6
else
if test -n "$CC"; then
ac_cv_prog_CC="$CC" # Let the user override the test.
else
ac_prog_rejected=no
as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
for as_dir in $PATH
do
IFS=$as_save_IFS
test -z "$as_dir" && as_dir=.
for ac_exec_ext in '' $ac_executable_extensions; do
if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then
ac_prog_rejected=yes
continue
fi
ac_cv_prog_CC="cc"
$as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
break 2
fi
done
done
IFS=$as_save_IFS
if test $ac_prog_rejected = yes; then
# We found a bogon in the path, so make sure we never use it.
set dummy $ac_cv_prog_CC
shift
if test $# != 0; then
# We chose a different compiler from the bogus one.
# However, it has the same basename, so the bogon will be chosen
# first if we set CC to just the basename; use the full file name.
shift
ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@"
fi
fi
fi
fi
CC=$ac_cv_prog_CC
if test -n "$CC"; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
$as_echo "$CC" >&6; }
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
fi
fi
if test -z "$CC"; then
if test -n "$ac_tool_prefix"; then
for ac_prog in cl.exe
do
# Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args.
set dummy $ac_tool_prefix$ac_prog; ac_word=$2
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
$as_echo_n "checking for $ac_word... " >&6; }
if ${ac_cv_prog_CC+:} false; then :
$as_echo_n "(cached) " >&6
else
if test -n "$CC"; then
ac_cv_prog_CC="$CC" # Let the user override the test.
else
as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
for as_dir in $PATH
do
IFS=$as_save_IFS
test -z "$as_dir" && as_dir=.
for ac_exec_ext in '' $ac_executable_extensions; do
if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
ac_cv_prog_CC="$ac_tool_prefix$ac_prog"
$as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
break 2
fi
done
done
IFS=$as_save_IFS
fi
fi
CC=$ac_cv_prog_CC
if test -n "$CC"; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
$as_echo "$CC" >&6; }
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
fi
test -n "$CC" && break
done
fi
if test -z "$CC"; then
ac_ct_CC=$CC
for ac_prog in cl.exe
do
# Extract the first word of "$ac_prog", so it can be a program name with args.
set dummy $ac_prog; ac_word=$2
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
$as_echo_n "checking for $ac_word... " >&6; }
if ${ac_cv_prog_ac_ct_CC+:} false; then :
$as_echo_n "(cached) " >&6
else
if test -n "$ac_ct_CC"; then
ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
else
as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
for as_dir in $PATH
do
IFS=$as_save_IFS
test -z "$as_dir" && as_dir=.
for ac_exec_ext in '' $ac_executable_extensions; do
if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
ac_cv_prog_ac_ct_CC="$ac_prog"
$as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
break 2
fi
done
done
IFS=$as_save_IFS
fi
fi
ac_ct_CC=$ac_cv_prog_ac_ct_CC
if test -n "$ac_ct_CC"; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
$as_echo "$ac_ct_CC" >&6; }
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
fi
test -n "$ac_ct_CC" && break
done
if test "x$ac_ct_CC" = x; then
CC=""
else
case $cross_compiling:$ac_tool_warned in
yes:)
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
ac_tool_warned=yes ;;
esac
CC=$ac_ct_CC
fi
fi
fi
test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
as_fn_error $? "no acceptable C compiler found in \$PATH
See \`config.log' for more details" "$LINENO" 5; }
# Provide some information about the compiler.
$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5
set X $ac_compile
ac_compiler=$2
for ac_option in --version -v -V -qversion; do
{ { ac_try="$ac_compiler $ac_option >&5"
case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
$as_echo "$ac_try_echo"; } >&5
(eval "$ac_compiler $ac_option >&5") 2>conftest.err
ac_status=$?
if test -s conftest.err; then
sed '10a\
... rest of stderr output deleted ...
10q' conftest.err >conftest.er1
cat conftest.er1 >&5
fi
rm -f conftest.er1 conftest.err
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }
done
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
int
main ()
{
;
return 0;
}
_ACEOF
ac_clean_files_save=$ac_clean_files
ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out"
# Try to create an executable without -o first, disregard a.out.
# It will help us diagnose broken compilers, and finding out an intuition
# of exeext.
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5
$as_echo_n "checking whether the C compiler works... " >&6; }
ac_link_default=`$as_echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'`
# The possible output files:
ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*"
ac_rmfiles=
for ac_file in $ac_files
do
case $ac_file in
*.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;;
* ) ac_rmfiles="$ac_rmfiles $ac_file";;
esac
done
rm -f $ac_rmfiles
if { { ac_try="$ac_link_default"
case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
$as_echo "$ac_try_echo"; } >&5
(eval "$ac_link_default") 2>&5
ac_status=$?
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then :
# Autoconf-2.13 could set the ac_cv_exeext variable to `no'.
# So ignore a value of `no', otherwise this would lead to `EXEEXT = no'
# in a Makefile. We should not override ac_cv_exeext if it was cached,
# so that the user can short-circuit this test for compilers unknown to
# Autoconf.
for ac_file in $ac_files ''
do
test -f "$ac_file" || continue
case $ac_file in
*.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj )
;;
[ab].out )
# We found the default executable, but exeext='' is most
# certainly right.
break;;
*.* )
if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no;
then :; else
ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'`
fi
# We set ac_cv_exeext here because the later test for it is not
# safe: cross compilers may not add the suffix if given an `-o'
# argument, so we may need to know it at that point already.
# Even if this section looks crufty: it has the advantage of
# actually working.
break;;
* )
break;;
esac
done
test "$ac_cv_exeext" = no && ac_cv_exeext=
else
ac_file=''
fi
if test -z "$ac_file"; then :
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
$as_echo "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
as_fn_error 77 "C compiler cannot create executables
See \`config.log' for more details" "$LINENO" 5; }
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5
$as_echo_n "checking for C compiler default output file name... " >&6; }
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5
$as_echo "$ac_file" >&6; }
ac_exeext=$ac_cv_exeext
rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out
ac_clean_files=$ac_clean_files_save
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5
$as_echo_n "checking for suffix of executables... " >&6; }
if { { ac_try="$ac_link"
case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
$as_echo "$ac_try_echo"; } >&5
(eval "$ac_link") 2>&5
ac_status=$?
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then :
# If both `conftest.exe' and `conftest' are `present' (well, observable)
# catch `conftest.exe'. For instance with Cygwin, `ls conftest' will
# work properly (i.e., refer to `conftest.exe'), while it won't with
# `rm'.
for ac_file in conftest.exe conftest conftest.*; do
test -f "$ac_file" || continue
case $ac_file in
*.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;;
*.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'`
break;;
* ) break;;
esac
done
else
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
as_fn_error $? "cannot compute suffix of executables: cannot compile and link
See \`config.log' for more details" "$LINENO" 5; }
fi
rm -f conftest conftest$ac_cv_exeext
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5
$as_echo "$ac_cv_exeext" >&6; }
rm -f conftest.$ac_ext
EXEEXT=$ac_cv_exeext
ac_exeext=$EXEEXT
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include
int
main ()
{
FILE *f = fopen ("conftest.out", "w");
return ferror (f) || fclose (f) != 0;
;
return 0;
}
_ACEOF
ac_clean_files="$ac_clean_files conftest.out"
# Check that the compiler produces executables we can run. If not, either
# the compiler is broken, or we cross compile.
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5
$as_echo_n "checking whether we are cross compiling... " >&6; }
if test "$cross_compiling" != yes; then
{ { ac_try="$ac_link"
case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
$as_echo "$ac_try_echo"; } >&5
(eval "$ac_link") 2>&5
ac_status=$?
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }
if { ac_try='./conftest$ac_cv_exeext'
{ { case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
$as_echo "$ac_try_echo"; } >&5
(eval "$ac_try") 2>&5
ac_status=$?
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; }; then
cross_compiling=no
else
if test "$cross_compiling" = maybe; then
cross_compiling=yes
else
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
as_fn_error $? "cannot run C compiled programs.
If you meant to cross compile, use \`--host'.
See \`config.log' for more details" "$LINENO" 5; }
fi
fi
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5
$as_echo "$cross_compiling" >&6; }
rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out
ac_clean_files=$ac_clean_files_save
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5
$as_echo_n "checking for suffix of object files... " >&6; }
if ${ac_cv_objext+:} false; then :
$as_echo_n "(cached) " >&6
else
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
int
main ()
{
;
return 0;
}
_ACEOF
rm -f conftest.o conftest.obj
if { { ac_try="$ac_compile"
case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
$as_echo "$ac_try_echo"; } >&5
(eval "$ac_compile") 2>&5
ac_status=$?
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then :
for ac_file in conftest.o conftest.obj conftest.*; do
test -f "$ac_file" || continue;
case $ac_file in
*.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;;
*) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'`
break;;
esac
done
else
$as_echo "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
as_fn_error $? "cannot compute suffix of object files: cannot compile
See \`config.log' for more details" "$LINENO" 5; }
fi
rm -f conftest.$ac_cv_objext conftest.$ac_ext
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5
$as_echo "$ac_cv_objext" >&6; }
OBJEXT=$ac_cv_objext
ac_objext=$OBJEXT
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5
$as_echo_n "checking whether we are using the GNU C compiler... " >&6; }
if ${ac_cv_c_compiler_gnu+:} false; then :
$as_echo_n "(cached) " >&6
else
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
int
main ()
{
#ifndef __GNUC__
choke me
#endif
;
return 0;
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
ac_compiler_gnu=yes
else
ac_compiler_gnu=no
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
ac_cv_c_compiler_gnu=$ac_compiler_gnu
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5
$as_echo "$ac_cv_c_compiler_gnu" >&6; }
if test $ac_compiler_gnu = yes; then
GCC=yes
else
GCC=
fi
ac_test_CFLAGS=${CFLAGS+set}
ac_save_CFLAGS=$CFLAGS
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5
$as_echo_n "checking whether $CC accepts -g... " >&6; }
if ${ac_cv_prog_cc_g+:} false; then :
$as_echo_n "(cached) " >&6
else
ac_save_c_werror_flag=$ac_c_werror_flag
ac_c_werror_flag=yes
ac_cv_prog_cc_g=no
CFLAGS="-g"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
int
main ()
{
;
return 0;
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
ac_cv_prog_cc_g=yes
else
CFLAGS=""
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
int
main ()
{
;
return 0;
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
else
ac_c_werror_flag=$ac_save_c_werror_flag
CFLAGS="-g"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
int
main ()
{
;
return 0;
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
ac_cv_prog_cc_g=yes
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
ac_c_werror_flag=$ac_save_c_werror_flag
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5
$as_echo "$ac_cv_prog_cc_g" >&6; }
if test "$ac_test_CFLAGS" = set; then
CFLAGS=$ac_save_CFLAGS
elif test $ac_cv_prog_cc_g = yes; then
if test "$GCC" = yes; then
CFLAGS="-g -O2"
else
CFLAGS="-g"
fi
else
if test "$GCC" = yes; then
CFLAGS="-O2"
else
CFLAGS=
fi
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5
$as_echo_n "checking for $CC option to accept ISO C89... " >&6; }
if ${ac_cv_prog_cc_c89+:} false; then :
$as_echo_n "(cached) " >&6
else
ac_cv_prog_cc_c89=no
ac_save_CC=$CC
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include
#include
struct stat;
/* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */
struct buf { int x; };
FILE * (*rcsopen) (struct buf *, struct stat *, int);
static char *e (p, i)
char **p;
int i;
{
return p[i];
}
static char *f (char * (*g) (char **, int), char **p, ...)
{
char *s;
va_list v;
va_start (v,p);
s = g (p, va_arg (v,int));
va_end (v);
return s;
}
/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has
function prototypes and stuff, but not '\xHH' hex character constants.
These don't provoke an error unfortunately, instead are silently treated
as 'x'. The following induces an error, until -std is added to get
proper ANSI mode. Curiously '\x00'!='x' always comes out true, for an
array size at least. It's necessary to write '\x00'==0 to get something
that's true only with -std. */
int osf4_cc_array ['\x00' == 0 ? 1 : -1];
/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters
inside strings and character constants. */
#define FOO(x) 'x'
int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1];
int test (int i, double x);
struct s1 {int (*f) (int a);};
struct s2 {int (*f) (double a);};
int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int);
int argc;
char **argv;
int
main ()
{
return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1];
;
return 0;
}
_ACEOF
for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \
-Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__"
do
CC="$ac_save_CC $ac_arg"
if ac_fn_c_try_compile "$LINENO"; then :
ac_cv_prog_cc_c89=$ac_arg
fi
rm -f core conftest.err conftest.$ac_objext
test "x$ac_cv_prog_cc_c89" != "xno" && break
done
rm -f conftest.$ac_ext
CC=$ac_save_CC
fi
# AC_CACHE_VAL
case "x$ac_cv_prog_cc_c89" in
x)
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
$as_echo "none needed" >&6; } ;;
xno)
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
$as_echo "unsupported" >&6; } ;;
*)
CC="$CC $ac_cv_prog_cc_c89"
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5
$as_echo "$ac_cv_prog_cc_c89" >&6; } ;;
esac
if test "x$ac_cv_prog_cc_c89" != xno; then :
fi
ac_ext=c
ac_cpp='$CPP $CPPFLAGS'
ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
ac_compiler_gnu=$ac_cv_c_compiler_gnu
ac_ext=c
ac_cpp='$CPP $CPPFLAGS'
ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
ac_compiler_gnu=$ac_cv_c_compiler_gnu
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5
$as_echo_n "checking how to run the C preprocessor... " >&6; }
# On Suns, sometimes $CPP names a directory.
if test -n "$CPP" && test -d "$CPP"; then
CPP=
fi
if test -z "$CPP"; then
if ${ac_cv_prog_CPP+:} false; then :
$as_echo_n "(cached) " >&6
else
# Double quotes because CPP needs to be expanded
for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp"
do
ac_preproc_ok=false
for ac_c_preproc_warn_flag in '' yes
do
# Use a header file that comes with gcc, so configuring glibc
# with a fresh cross-compiler works.
# Prefer to if __STDC__ is defined, since
# exists even on freestanding compilers.
# On the NeXT, cc -E runs the code through the compiler's parser,
# not just through cpp. "Syntax error" is here to catch this case.
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#ifdef __STDC__
# include
#else
# include
#endif
Syntax error
_ACEOF
if ac_fn_c_try_cpp "$LINENO"; then :
else
# Broken: fails on valid input.
continue
fi
rm -f conftest.err conftest.i conftest.$ac_ext
# OK, works on sane cases. Now check whether nonexistent headers
# can be detected and how.
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include
_ACEOF
if ac_fn_c_try_cpp "$LINENO"; then :
# Broken: success on invalid input.
continue
else
# Passes both tests.
ac_preproc_ok=:
break
fi
rm -f conftest.err conftest.i conftest.$ac_ext
done
# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
rm -f conftest.i conftest.err conftest.$ac_ext
if $ac_preproc_ok; then :
break
fi
done
ac_cv_prog_CPP=$CPP
fi
CPP=$ac_cv_prog_CPP
else
ac_cv_prog_CPP=$CPP
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5
$as_echo "$CPP" >&6; }
ac_preproc_ok=false
for ac_c_preproc_warn_flag in '' yes
do
# Use a header file that comes with gcc, so configuring glibc
# with a fresh cross-compiler works.
# Prefer to if __STDC__ is defined, since
# exists even on freestanding compilers.
# On the NeXT, cc -E runs the code through the compiler's parser,
# not just through cpp. "Syntax error" is here to catch this case.
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#ifdef __STDC__
# include
#else
# include
#endif
Syntax error
_ACEOF
if ac_fn_c_try_cpp "$LINENO"; then :
else
# Broken: fails on valid input.
continue
fi
rm -f conftest.err conftest.i conftest.$ac_ext
# OK, works on sane cases. Now check whether nonexistent headers
# can be detected and how.
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include
_ACEOF
if ac_fn_c_try_cpp "$LINENO"; then :
# Broken: success on invalid input.
continue
else
# Passes both tests.
ac_preproc_ok=:
break
fi
rm -f conftest.err conftest.i conftest.$ac_ext
done
# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
rm -f conftest.i conftest.err conftest.$ac_ext
if $ac_preproc_ok; then :
else
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
as_fn_error $? "C preprocessor \"$CPP\" fails sanity check
See \`config.log' for more details" "$LINENO" 5; }
fi
ac_ext=c
ac_cpp='$CPP $CPPFLAGS'
ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
ac_compiler_gnu=$ac_cv_c_compiler_gnu
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5
$as_echo_n "checking for grep that handles long lines and -e... " >&6; }
if ${ac_cv_path_GREP+:} false; then :
$as_echo_n "(cached) " >&6
else
if test -z "$GREP"; then
ac_path_GREP_found=false
# Loop through the user's path and test for each of PROGNAME-LIST
as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
do
IFS=$as_save_IFS
test -z "$as_dir" && as_dir=.
for ac_prog in grep ggrep; do
for ac_exec_ext in '' $ac_executable_extensions; do
ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext"
as_fn_executable_p "$ac_path_GREP" || continue
# Check for GNU ac_path_GREP and select it if it is found.
# Check for GNU $ac_path_GREP
case `"$ac_path_GREP" --version 2>&1` in
*GNU*)
ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;;
*)
ac_count=0
$as_echo_n 0123456789 >"conftest.in"
while :
do
cat "conftest.in" "conftest.in" >"conftest.tmp"
mv "conftest.tmp" "conftest.in"
cp "conftest.in" "conftest.nl"
$as_echo 'GREP' >> "conftest.nl"
"$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break
diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
as_fn_arith $ac_count + 1 && ac_count=$as_val
if test $ac_count -gt ${ac_path_GREP_max-0}; then
# Best one so far, save it but keep looking for a better one
ac_cv_path_GREP="$ac_path_GREP"
ac_path_GREP_max=$ac_count
fi
# 10*(2^10) chars as input seems more than enough
test $ac_count -gt 10 && break
done
rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
esac
$ac_path_GREP_found && break 3
done
done
done
IFS=$as_save_IFS
if test -z "$ac_cv_path_GREP"; then
as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
fi
else
ac_cv_path_GREP=$GREP
fi
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5
$as_echo "$ac_cv_path_GREP" >&6; }
GREP="$ac_cv_path_GREP"
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5
$as_echo_n "checking for egrep... " >&6; }
if ${ac_cv_path_EGREP+:} false; then :
$as_echo_n "(cached) " >&6
else
if echo a | $GREP -E '(a|b)' >/dev/null 2>&1
then ac_cv_path_EGREP="$GREP -E"
else
if test -z "$EGREP"; then
ac_path_EGREP_found=false
# Loop through the user's path and test for each of PROGNAME-LIST
as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
do
IFS=$as_save_IFS
test -z "$as_dir" && as_dir=.
for ac_prog in egrep; do
for ac_exec_ext in '' $ac_executable_extensions; do
ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext"
as_fn_executable_p "$ac_path_EGREP" || continue
# Check for GNU ac_path_EGREP and select it if it is found.
# Check for GNU $ac_path_EGREP
case `"$ac_path_EGREP" --version 2>&1` in
*GNU*)
ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;;
*)
ac_count=0
$as_echo_n 0123456789 >"conftest.in"
while :
do
cat "conftest.in" "conftest.in" >"conftest.tmp"
mv "conftest.tmp" "conftest.in"
cp "conftest.in" "conftest.nl"
$as_echo 'EGREP' >> "conftest.nl"
"$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break
diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
as_fn_arith $ac_count + 1 && ac_count=$as_val
if test $ac_count -gt ${ac_path_EGREP_max-0}; then
# Best one so far, save it but keep looking for a better one
ac_cv_path_EGREP="$ac_path_EGREP"
ac_path_EGREP_max=$ac_count
fi
# 10*(2^10) chars as input seems more than enough
test $ac_count -gt 10 && break
done
rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
esac
$ac_path_EGREP_found && break 3
done
done
done
IFS=$as_save_IFS
if test -z "$ac_cv_path_EGREP"; then
as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
fi
else
ac_cv_path_EGREP=$EGREP
fi
fi
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5
$as_echo "$ac_cv_path_EGREP" >&6; }
EGREP="$ac_cv_path_EGREP"
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5
$as_echo_n "checking for ANSI C header files... " >&6; }
if ${ac_cv_header_stdc+:} false; then :
$as_echo_n "(cached) " >&6
else
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include
#include
#include
#include
int
main ()
{
;
return 0;
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
ac_cv_header_stdc=yes
else
ac_cv_header_stdc=no
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
if test $ac_cv_header_stdc = yes; then
# SunOS 4.x string.h does not declare mem*, contrary to ANSI.
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include
_ACEOF
if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
$EGREP "memchr" >/dev/null 2>&1; then :
else
ac_cv_header_stdc=no
fi
rm -f conftest*
fi
if test $ac_cv_header_stdc = yes; then
# ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI.
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include
_ACEOF
if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
$EGREP "free" >/dev/null 2>&1; then :
else
ac_cv_header_stdc=no
fi
rm -f conftest*
fi
if test $ac_cv_header_stdc = yes; then
# /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi.
if test "$cross_compiling" = yes; then :
:
else
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include
#include
#if ((' ' & 0x0FF) == 0x020)
# define ISLOWER(c) ('a' <= (c) && (c) <= 'z')
# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c))
#else
# define ISLOWER(c) \
(('a' <= (c) && (c) <= 'i') \
|| ('j' <= (c) && (c) <= 'r') \
|| ('s' <= (c) && (c) <= 'z'))
# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c))
#endif
#define XOR(e, f) (((e) && !(f)) || (!(e) && (f)))
int
main ()
{
int i;
for (i = 0; i < 256; i++)
if (XOR (islower (i), ISLOWER (i))
|| toupper (i) != TOUPPER (i))
return 2;
return 0;
}
_ACEOF
if ac_fn_c_try_run "$LINENO"; then :
else
ac_cv_header_stdc=no
fi
rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
conftest.$ac_objext conftest.beam conftest.$ac_ext
fi
fi
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5
$as_echo "$ac_cv_header_stdc" >&6; }
if test $ac_cv_header_stdc = yes; then
$as_echo "#define STDC_HEADERS 1" >>confdefs.h
fi
# On IRIX 5.3, sys/types and inttypes.h are conflicting.
for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \
inttypes.h stdint.h unistd.h
do :
as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default
"
if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
cat >>confdefs.h <<_ACEOF
#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1
_ACEOF
fi
done
ac_fn_c_check_header_mongrel "$LINENO" "minix/config.h" "ac_cv_header_minix_config_h" "$ac_includes_default"
if test "x$ac_cv_header_minix_config_h" = xyes; then :
MINIX=yes
else
MINIX=
fi
if test "$MINIX" = yes; then
$as_echo "#define _POSIX_SOURCE 1" >>confdefs.h
$as_echo "#define _POSIX_1_SOURCE 2" >>confdefs.h
$as_echo "#define _MINIX 1" >>confdefs.h
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether it is safe to define __EXTENSIONS__" >&5
$as_echo_n "checking whether it is safe to define __EXTENSIONS__... " >&6; }
if ${ac_cv_safe_to_define___extensions__+:} false; then :
$as_echo_n "(cached) " >&6
else
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
# define __EXTENSIONS__ 1
$ac_includes_default
int
main ()
{
;
return 0;
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
ac_cv_safe_to_define___extensions__=yes
else
ac_cv_safe_to_define___extensions__=no
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_safe_to_define___extensions__" >&5
$as_echo "$ac_cv_safe_to_define___extensions__" >&6; }
test $ac_cv_safe_to_define___extensions__ = yes &&
$as_echo "#define __EXTENSIONS__ 1" >>confdefs.h
$as_echo "#define _ALL_SOURCE 1" >>confdefs.h
$as_echo "#define _GNU_SOURCE 1" >>confdefs.h
$as_echo "#define _POSIX_PTHREAD_SEMANTICS 1" >>confdefs.h
$as_echo "#define _TANDEM_SOURCE 1" >>confdefs.h
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing strerror" >&5
$as_echo_n "checking for library containing strerror... " >&6; }
if ${ac_cv_search_strerror+:} false; then :
$as_echo_n "(cached) " >&6
else
ac_func_search_save_LIBS=$LIBS
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
/* Override any GCC internal prototype to avoid an error.
Use char because int might match the return type of a GCC
builtin and then its argument prototype would still apply. */
#ifdef __cplusplus
extern "C"
#endif
char strerror ();
int
main ()
{
return strerror ();
;
return 0;
}
_ACEOF
for ac_lib in '' cposix; do
if test -z "$ac_lib"; then
ac_res="none required"
else
ac_res=-l$ac_lib
LIBS="-l$ac_lib $ac_func_search_save_LIBS"
fi
if ac_fn_c_try_link "$LINENO"; then :
ac_cv_search_strerror=$ac_res
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext
if ${ac_cv_search_strerror+:} false; then :
break
fi
done
if ${ac_cv_search_strerror+:} false; then :
else
ac_cv_search_strerror=no
fi
rm conftest.$ac_ext
LIBS=$ac_func_search_save_LIBS
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_strerror" >&5
$as_echo "$ac_cv_search_strerror" >&6; }
ac_res=$ac_cv_search_strerror
if test "$ac_res" != no; then :
test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} sets \$(MAKE)" >&5
$as_echo_n "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; }
set x ${MAKE-make}
ac_make=`$as_echo "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'`
if eval \${ac_cv_prog_make_${ac_make}_set+:} false; then :
$as_echo_n "(cached) " >&6
else
cat >conftest.make <<\_ACEOF
SHELL = /bin/sh
all:
@echo '@@@%%%=$(MAKE)=@@@%%%'
_ACEOF
# GNU make sometimes prints "make[1]: Entering ...", which would confuse us.
case `${MAKE-make} -f conftest.make 2>/dev/null` in
*@@@%%%=?*=@@@%%%*)
eval ac_cv_prog_make_${ac_make}_set=yes;;
*)
eval ac_cv_prog_make_${ac_make}_set=no;;
esac
rm -f conftest.make
fi
if eval test \$ac_cv_prog_make_${ac_make}_set = yes; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
SET_MAKE=
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
SET_MAKE="MAKE=${MAKE-make}"
fi
# Check whether --with-includes was given.
if test "${with_includes+set}" = set; then :
withval=$with_includes; ac_addl_includes=`echo "$withval" | sed -e 's/:/ /g'` ;
for ainclude in $ac_addl_includes; do
if test x"$ac_build_addl_includes" = x ; then
ac_build_addl_includes="-I$ainclude"
else
ac_build_addl_includes="-I$ainclude $ac_build_addl_includes"
fi
done
CPPFLAGS="$CPPFLAGS $ac_build_addl_includes"
fi
# Check whether --with-libraries was given.
if test "${with_libraries+set}" = set; then :
withval=$with_libraries; ac_addl_libdirs=`echo "$withval" | sed -e 's/:/ /g'` ;
for alibdir in $ac_addl_libdirs; do
if test x"$ac_build_addl_libdirs" = x ; then
ac_build_addl_libdirs="-L$alibdir"
else
ac_build_addl_libdirs="-L$alibdir $ac_build_addl_libdirs"
fi
done
LDFLAGS="$LDFLAGS $ac_build_addl_libdirs"
fi
ENABLE_TESTS="\"\""
# Check whether --enable-tests was given.
if test "${enable_tests+set}" = set; then :
enableval=$enable_tests;
if test x"$enableval" != xno ; then
for ac_header in check.h
do :
ac_fn_c_check_header_mongrel "$LINENO" "check.h" "ac_cv_header_check_h" "$ac_includes_default"
if test "x$ac_cv_header_check_h" = xyes; then :
cat >>confdefs.h <<_ACEOF
#define HAVE_CHECK_H 1
_ACEOF
fi
done
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for tcase_create in -lcheck" >&5
$as_echo_n "checking for tcase_create in -lcheck... " >&6; }
if ${ac_cv_lib_check_tcase_create+:} false; then :
$as_echo_n "(cached) " >&6
else
ac_check_lib_save_LIBS=$LIBS
LIBS="-lcheck $LIBS"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
/* Override any GCC internal prototype to avoid an error.
Use char because int might match the return type of a GCC
builtin and then its argument prototype would still apply. */
#ifdef __cplusplus
extern "C"
#endif
char tcase_create ();
int
main ()
{
return tcase_create ();
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"; then :
ac_cv_lib_check_tcase_create=yes
else
ac_cv_lib_check_tcase_create=no
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
LIBS=$ac_check_lib_save_LIBS
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_check_tcase_create" >&5
$as_echo "$ac_cv_lib_check_tcase_create" >&6; }
if test "x$ac_cv_lib_check_tcase_create" = xyes; then :
$as_echo "#define HAVE_LIBCHECK 1" >>confdefs.h
ENABLE_TESTS="1"
else
as_fn_error $? "libcheck support, required for tests, not present -- aborting" "$LINENO" 5
fi
fi
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5
$as_echo_n "checking for ANSI C header files... " >&6; }
if ${ac_cv_header_stdc+:} false; then :
$as_echo_n "(cached) " >&6
else
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include
#include
#include
#include
int
main ()
{
;
return 0;
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
ac_cv_header_stdc=yes
else
ac_cv_header_stdc=no
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
if test $ac_cv_header_stdc = yes; then
# SunOS 4.x string.h does not declare mem*, contrary to ANSI.
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include
_ACEOF
if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
$EGREP "memchr" >/dev/null 2>&1; then :
else
ac_cv_header_stdc=no
fi
rm -f conftest*
fi
if test $ac_cv_header_stdc = yes; then
# ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI.
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include
_ACEOF
if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
$EGREP "free" >/dev/null 2>&1; then :
else
ac_cv_header_stdc=no
fi
rm -f conftest*
fi
if test $ac_cv_header_stdc = yes; then
# /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi.
if test "$cross_compiling" = yes; then :
:
else
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include
#include
#if ((' ' & 0x0FF) == 0x020)
# define ISLOWER(c) ('a' <= (c) && (c) <= 'z')
# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c))
#else
# define ISLOWER(c) \
(('a' <= (c) && (c) <= 'i') \
|| ('j' <= (c) && (c) <= 'r') \
|| ('s' <= (c) && (c) <= 'z'))
# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c))
#endif
#define XOR(e, f) (((e) && !(f)) || (!(e) && (f)))
int
main ()
{
int i;
for (i = 0; i < 256; i++)
if (XOR (islower (i), ISLOWER (i))
|| toupper (i) != TOUPPER (i))
return 2;
return 0;
}
_ACEOF
if ac_fn_c_try_run "$LINENO"; then :
else
ac_cv_header_stdc=no
fi
rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
conftest.$ac_objext conftest.beam conftest.$ac_ext
fi
fi
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5
$as_echo "$ac_cv_header_stdc" >&6; }
if test $ac_cv_header_stdc = yes; then
$as_echo "#define STDC_HEADERS 1" >>confdefs.h
fi
for ac_header in sqlite3.h stdlib.h unistd.h limits.h fcntl.h sys/sysctl.h sys/sysinfo.h
do :
as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default"
if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
cat >>confdefs.h <<_ACEOF
#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1
_ACEOF
fi
done
ac_fn_c_check_header_mongrel "$LINENO" "zlib.h" "ac_cv_header_zlib_h" "$ac_includes_default"
if test "x$ac_cv_header_zlib_h" = xyes; then :
$as_echo "#define HAVE_ZLIB_H 1" >>confdefs.h
MODULE_LIBS="$MODULE_LIBS -lz"
fi
for ac_func in random srandom strnstr sysctl sysinfo
do :
as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var"
if eval test \"x\$"$as_ac_var"\" = x"yes"; then :
cat >>confdefs.h <<_ACEOF
#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1
_ACEOF
fi
done
ac_dns_libs=""
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for resolver symbols in libc" >&5
$as_echo_n "checking for resolver symbols in libc... " >&6; }
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include
#include
#include
#include
int
main ()
{
int res;
res = res_query(NULL, ns_c_in, ns_t_txt, NULL, 0);
res = ns_initparse(NULL, 0, NULL);
res = ns_parserr(NULL, ns_s_an, 0, NULL);
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"; then :
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for resolver symbols in libresolv" >&5
$as_echo_n "checking for resolver symbols in libresolv... " >&6; }
saved_libs="$LIBS"
LIBS="$LIBS -lresolv"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include
#include
#include
#include
int
main ()
{
int res;
res = res_query(NULL, ns_c_in, ns_t_txt, NULL, 0);
res = ns_initparse(NULL, 0, NULL);
res = ns_parserr(NULL, ns_s_an, 0, NULL);
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"; then :
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
LIBS="$saved_libs"
ac_dns_libs="-lresolv"
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
LIBS="$saved_libs"
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for resolver symbols in /usr/lib64/libresolv.a" >&5
$as_echo_n "checking for resolver symbols in /usr/lib64/libresolv.a... " >&6; }
saved_libs="$LIBS"
LIBS="$LIBS /usr/lib64/libresolv.a"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include
#include
#include
#include
int
main ()
{
int res;
res = res_query(NULL, ns_c_in, ns_t_txt, NULL, 0);
res = ns_initparse(NULL, 0, NULL);
res = ns_parserr(NULL, ns_s_an, 0, NULL);
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"; then :
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
LIBS="$saved_libs"
ac_dns_libs="/usr/lib64/libresolv.a"
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
LIBS="$saved_libs"
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for resolver symbols in /usr/lib/libresolv.a" >&5
$as_echo_n "checking for resolver symbols in /usr/lib/libresolv.a... " >&6; }
saved_libs="$LIBS"
LIBS="$LIBS /usr/lib/libresolv.a"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include
#include
#include
#include
int
main ()
{
int res;
res = res_query(NULL, ns_c_in, ns_t_txt, NULL, 0);
res = ns_initparse(NULL, 0, NULL);
res = ns_parserr(NULL, ns_s_an, 0, NULL);
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"; then :
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
LIBS="$saved_libs"
ac_dns_libs="/usr/lib/libresolv.a"
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
LIBS="$saved_libs"
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
# Check for OpenSSL-isms
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether OpenSSL has crippled AES support" >&5
$as_echo_n "checking whether OpenSSL has crippled AES support... " >&6; }
LIBS="-lcrypto $LIBS"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#ifdef HAVE_STRING_H
# include
#endif
#include
int
main ()
{
EVP_CIPHER *c;
c = EVP_aes_192_cbc();
c = EVP_aes_256_cbc();
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"; then :
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
LIBS="$saved_libs"
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
$as_echo "#define HAVE_AES_CRIPPLED_OPENSSL 1" >>confdefs.h
LIBS="$saved_libs"
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether OpenSSL supports SHA256" >&5
$as_echo_n "checking whether OpenSSL supports SHA256... " >&6; }
LIBS="-lcrypto $LIBS"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include
int
main ()
{
const EVP_MD *md;
md = EVP_sha256();
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"; then :
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
$as_echo "#define HAVE_SHA256_OPENSSL 1" >>confdefs.h
LIBS="$saved_libs"
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
LIBS="$saved_libs"
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether OpenSSL supports SHA512" >&5
$as_echo_n "checking whether OpenSSL supports SHA512... " >&6; }
LIBS="-lcrypto $LIBS"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include
int
main ()
{
const EVP_MD *md;
md = EVP_sha512();
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"; then :
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
$as_echo "#define HAVE_SHA512_OPENSSL 1" >>confdefs.h
LIBS="$saved_libs"
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
LIBS="$saved_libs"
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether OpenSSL supports EVP_aes_128_ctr" >&5
$as_echo_n "checking whether OpenSSL supports EVP_aes_128_ctr... " >&6; }
LIBS="-lcrypto $LIBS"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include
int
main ()
{
EVP_CIPHER *cipher;
cipher = EVP_aes_128_ctr();
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"; then :
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
$as_echo "#define HAVE_EVP_AES_128_CTR_OPENSSL 1" >>confdefs.h
LIBS="$saved_libs"
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
LIBS="$saved_libs"
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether OpenSSL supports EVP_aes_192_ctr" >&5
$as_echo_n "checking whether OpenSSL supports EVP_aes_192_ctr... " >&6; }
LIBS="-lcrypto $LIBS"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include
int
main ()
{
EVP_CIPHER *cipher;
cipher = EVP_aes_192_ctr();
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"; then :
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
$as_echo "#define HAVE_EVP_AES_192_CTR_OPENSSL 1" >>confdefs.h
LIBS="$saved_libs"
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
LIBS="$saved_libs"
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether OpenSSL supports EVP_aes_256_ctr" >&5
$as_echo_n "checking whether OpenSSL supports EVP_aes_256_ctr... " >&6; }
LIBS="-lcrypto $LIBS"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include
int
main ()
{
EVP_CIPHER *cipher;
cipher = EVP_aes_256_ctr();
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"; then :
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
$as_echo "#define HAVE_EVP_AES_256_CTR_OPENSSL 1" >>confdefs.h
LIBS="$saved_libs"
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
LIBS="$saved_libs"
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether OpenSSL supports EVP_aes_256_gcm" >&5
$as_echo_n "checking whether OpenSSL supports EVP_aes_256_gcm... " >&6; }
LIBS="-lcrypto $LIBS"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include
int
main ()
{
EVP_CIPHER *cipher;
cipher = EVP_aes_256_gcm();
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"; then :
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
$as_echo "#define HAVE_EVP_AES_256_GCM_OPENSSL 1" >>confdefs.h
LIBS="$saved_libs"
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
LIBS="$saved_libs"
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether OpenSSL supports X448 algorithm" >&5
$as_echo_n "checking whether OpenSSL supports X448 algorithm... " >&6; }
LIBS="-lcrypto $LIBS"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include
int
main ()
{
EVP_PKEY *pkey;
EVP_PKEY_CTX *pctx;
pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_X448, NULL, NULL, 0);
pctx = EVP_PKEY_CTX_new_id(NID_X448, NULL);
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"; then :
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
$as_echo "#define HAVE_X448_OPENSSL 1" >>confdefs.h
LIBS="$saved_libs"
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
LIBS="$saved_libs"
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether OpenSSL supports OSSL_PROVIDER_load" >&5
$as_echo_n "checking whether OpenSSL supports OSSL_PROVIDER_load... " >&6; }
LIBS="-lcrypto $LIBS"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include
int
main ()
{
OSSL_PROVIDER *provider;
provider = OSSL_PROVIDER_load(NULL, "default");
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"; then :
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
$as_echo "#define HAVE_OSSL_PROVIDER_LOAD_OPENSSL 1" >>confdefs.h
LIBS="$saved_libs"
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
LIBS="$saved_libs"
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
# Check for SQLite-isms
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for sqlite3_stmt_readonly" >&5
$as_echo_n "checking for sqlite3_stmt_readonly... " >&6; }
saved_libs="$LIBS"
LIBS="-lsqlite3"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include
#include
#ifdef HAVE_SQLITE3_H
# include
#endif
int
main ()
{
(void) sqlite3_stmt_readonly(NULL);
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"; then :
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
$as_echo "#define HAVE_SQLITE3_STMT_READONLY 1" >>confdefs.h
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
LIBS="$saved_libs"
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for sqlite3_trace" >&5
$as_echo_n "checking for sqlite3_trace... " >&6; }
saved_libs="$LIBS"
LIBS="-lsqlite3"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include
#include
#ifdef HAVE_SQLITE3_H
# include
#endif
int
main ()
{
(void) sqlite3_trace(NULL, NULL, NULL);
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"; then :
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
$as_echo "#define HAVE_SQLITE3_TRACE 1" >>confdefs.h
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
LIBS="$saved_libs"
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for sqlite3_trace_v2" >&5
$as_echo_n "checking for sqlite3_trace_v2... " >&6; }
saved_libs="$LIBS"
LIBS="-lsqlite3"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include
#include
#ifdef HAVE_SQLITE3_H
# include
#endif
int
main ()
{
(void) sqlite3_trace_v2(NULL, 0, NULL, NULL);
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"; then :
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
$as_echo "#define HAVE_SQLITE3_TRACE_V2 1" >>confdefs.h
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
LIBS="$saved_libs"
INCLUDES="$ac_build_addl_includes"
LIBDIRS="$ac_build_addl_libdirs"
MODULE_LIBS="$MODULE_LIBS $ac_dns_libs"
ac_config_headers="$ac_config_headers mod_proxy.h"
ac_config_files="$ac_config_files t/Makefile Makefile"
cat >confcache <<\_ACEOF
# This file is a shell script that caches the results of configure
# tests run on this system so they can be shared between configure
# scripts and configure runs, see configure's option --config-cache.
# It is not useful on other systems. If it contains results you don't
# want to keep, you may remove or edit it.
#
# config.status only pays attention to the cache file if you give it
# the --recheck option to rerun configure.
#
# `ac_cv_env_foo' variables (set or unset) will be overridden when
# loading this file, other *unset* `ac_cv_foo' will be assigned the
# following values.
_ACEOF
# The following way of writing the cache mishandles newlines in values,
# but we know of no workaround that is simple, portable, and efficient.
# So, we kill variables containing newlines.
# Ultrix sh set writes to stderr and can't be redirected directly,
# and sets the high bit in the cache file unless we assign to the vars.
(
for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do
eval ac_val=\$$ac_var
case $ac_val in #(
*${as_nl}*)
case $ac_var in #(
*_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5
$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
esac
case $ac_var in #(
_ | IFS | as_nl) ;; #(
BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #(
*) { eval $ac_var=; unset $ac_var;} ;;
esac ;;
esac
done
(set) 2>&1 |
case $as_nl`(ac_space=' '; set) 2>&1` in #(
*${as_nl}ac_space=\ *)
# `set' does not quote correctly, so add quotes: double-quote
# substitution turns \\\\ into \\, and sed turns \\ into \.
sed -n \
"s/'/'\\\\''/g;
s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p"
;; #(
*)
# `set' quotes correctly as required by POSIX, so do not add quotes.
sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p"
;;
esac |
sort
) |
sed '
/^ac_cv_env_/b end
t clear
:clear
s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/
t end
s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/
:end' >>confcache
if diff "$cache_file" confcache >/dev/null 2>&1; then :; else
if test -w "$cache_file"; then
if test "x$cache_file" != "x/dev/null"; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5
$as_echo "$as_me: updating cache $cache_file" >&6;}
if test ! -f "$cache_file" || test -h "$cache_file"; then
cat confcache >"$cache_file"
else
case $cache_file in #(
*/* | ?:*)
mv -f confcache "$cache_file"$$ &&
mv -f "$cache_file"$$ "$cache_file" ;; #(
*)
mv -f confcache "$cache_file" ;;
esac
fi
fi
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5
$as_echo "$as_me: not updating unwritable cache $cache_file" >&6;}
fi
fi
rm -f confcache
test "x$prefix" = xNONE && prefix=$ac_default_prefix
# Let make expand exec_prefix.
test "x$exec_prefix" = xNONE && exec_prefix='${prefix}'
DEFS=-DHAVE_CONFIG_H
ac_libobjs=
ac_ltlibobjs=
U=
for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue
# 1. Remove the extension, and $U if already installed.
ac_script='s/\$U\././;s/\.o$//;s/\.obj$//'
ac_i=`$as_echo "$ac_i" | sed "$ac_script"`
# 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR
# will be set to the directory where LIBOBJS objects are built.
as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext"
as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo'
done
LIBOBJS=$ac_libobjs
LTLIBOBJS=$ac_ltlibobjs
: "${CONFIG_STATUS=./config.status}"
ac_write_fail=0
ac_clean_files_save=$ac_clean_files
ac_clean_files="$ac_clean_files $CONFIG_STATUS"
{ $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5
$as_echo "$as_me: creating $CONFIG_STATUS" >&6;}
as_write_fail=0
cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1
#! $SHELL
# Generated by $as_me.
# Run this file to recreate the current configuration.
# Compiler output produced by configure, useful for debugging
# configure, is in config.log if it exists.
debug=false
ac_cs_recheck=false
ac_cs_silent=false
SHELL=\${CONFIG_SHELL-$SHELL}
export SHELL
_ASEOF
cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1
## -------------------- ##
## M4sh Initialization. ##
## -------------------- ##
# Be more Bourne compatible
DUALCASE=1; export DUALCASE # for MKS sh
if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then :
emulate sh
NULLCMD=:
# Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which
# is contrary to our usage. Disable this feature.
alias -g '${1+"$@"}'='"$@"'
setopt NO_GLOB_SUBST
else
case `(set -o) 2>/dev/null` in #(
*posix*) :
set -o posix ;; #(
*) :
;;
esac
fi
as_nl='
'
export as_nl
# Printing a long string crashes Solaris 7 /usr/bin/printf.
as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo
as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo
# Prefer a ksh shell builtin over an external printf program on Solaris,
# but without wasting forks for bash or zsh.
if test -z "$BASH_VERSION$ZSH_VERSION" \
&& (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then
as_echo='print -r --'
as_echo_n='print -rn --'
elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then
as_echo='printf %s\n'
as_echo_n='printf %s'
else
if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then
as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"'
as_echo_n='/usr/ucb/echo -n'
else
as_echo_body='eval expr "X$1" : "X\\(.*\\)"'
as_echo_n_body='eval
arg=$1;
case $arg in #(
*"$as_nl"*)
expr "X$arg" : "X\\(.*\\)$as_nl";
arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;;
esac;
expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl"
'
export as_echo_n_body
as_echo_n='sh -c $as_echo_n_body as_echo'
fi
export as_echo_body
as_echo='sh -c $as_echo_body as_echo'
fi
# The user is always right.
if test "${PATH_SEPARATOR+set}" != set; then
PATH_SEPARATOR=:
(PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && {
(PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 ||
PATH_SEPARATOR=';'
}
fi
# IFS
# We need space, tab and new line, in precisely that order. Quoting is
# there to prevent editors from complaining about space-tab.
# (If _AS_PATH_WALK were called with IFS unset, it would disable word
# splitting by setting IFS to empty value.)
IFS=" "" $as_nl"
# Find who we are. Look in the path if we contain no directory separator.
as_myself=
case $0 in #((
*[\\/]* ) as_myself=$0 ;;
*) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
for as_dir in $PATH
do
IFS=$as_save_IFS
test -z "$as_dir" && as_dir=.
test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
done
IFS=$as_save_IFS
;;
esac
# We did not find ourselves, most probably we were run as `sh COMMAND'
# in which case we are not to be found in the path.
if test "x$as_myself" = x; then
as_myself=$0
fi
if test ! -f "$as_myself"; then
$as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2
exit 1
fi
# Unset variables that we do not need and which cause bugs (e.g. in
# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1"
# suppresses any "Segmentation fault" message there. '((' could
# trigger a bug in pdksh 5.2.14.
for as_var in BASH_ENV ENV MAIL MAILPATH
do eval test x\${$as_var+set} = xset \
&& ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || :
done
PS1='$ '
PS2='> '
PS4='+ '
# NLS nuisances.
LC_ALL=C
export LC_ALL
LANGUAGE=C
export LANGUAGE
# CDPATH.
(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
# as_fn_error STATUS ERROR [LINENO LOG_FD]
# ----------------------------------------
# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are
# provided, also output the error to LOG_FD, referencing LINENO. Then exit the
# script with STATUS, using 1 if that was 0.
as_fn_error ()
{
as_status=$1; test $as_status -eq 0 && as_status=1
if test "$4"; then
as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
$as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4
fi
$as_echo "$as_me: error: $2" >&2
as_fn_exit $as_status
} # as_fn_error
# as_fn_set_status STATUS
# -----------------------
# Set $? to STATUS, without forking.
as_fn_set_status ()
{
return $1
} # as_fn_set_status
# as_fn_exit STATUS
# -----------------
# Exit the shell with STATUS, even in a "trap 0" or "set -e" context.
as_fn_exit ()
{
set +e
as_fn_set_status $1
exit $1
} # as_fn_exit
# as_fn_unset VAR
# ---------------
# Portably unset VAR.
as_fn_unset ()
{
{ eval $1=; unset $1;}
}
as_unset=as_fn_unset
# as_fn_append VAR VALUE
# ----------------------
# Append the text in VALUE to the end of the definition contained in VAR. Take
# advantage of any shell optimizations that allow amortized linear growth over
# repeated appends, instead of the typical quadratic growth present in naive
# implementations.
if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then :
eval 'as_fn_append ()
{
eval $1+=\$2
}'
else
as_fn_append ()
{
eval $1=\$$1\$2
}
fi # as_fn_append
# as_fn_arith ARG...
# ------------------
# Perform arithmetic evaluation on the ARGs, and store the result in the
# global $as_val. Take advantage of shells that can avoid forks. The arguments
# must be portable across $(()) and expr.
if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then :
eval 'as_fn_arith ()
{
as_val=$(( $* ))
}'
else
as_fn_arith ()
{
as_val=`expr "$@" || test $? -eq 1`
}
fi # as_fn_arith
if expr a : '\(a\)' >/dev/null 2>&1 &&
test "X`expr 00001 : '.*\(...\)'`" = X001; then
as_expr=expr
else
as_expr=false
fi
if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then
as_basename=basename
else
as_basename=false
fi
if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then
as_dirname=dirname
else
as_dirname=false
fi
as_me=`$as_basename -- "$0" ||
$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
X"$0" : 'X\(//\)$' \| \
X"$0" : 'X\(/\)' \| . 2>/dev/null ||
$as_echo X/"$0" |
sed '/^.*\/\([^/][^/]*\)\/*$/{
s//\1/
q
}
/^X\/\(\/\/\)$/{
s//\1/
q
}
/^X\/\(\/\).*/{
s//\1/
q
}
s/.*/./; q'`
# Avoid depending upon Character Ranges.
as_cr_letters='abcdefghijklmnopqrstuvwxyz'
as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
as_cr_Letters=$as_cr_letters$as_cr_LETTERS
as_cr_digits='0123456789'
as_cr_alnum=$as_cr_Letters$as_cr_digits
ECHO_C= ECHO_N= ECHO_T=
case `echo -n x` in #(((((
-n*)
case `echo 'xy\c'` in
*c*) ECHO_T=' ';; # ECHO_T is single tab character.
xy) ECHO_C='\c';;
*) echo `echo ksh88 bug on AIX 6.1` > /dev/null
ECHO_T=' ';;
esac;;
*)
ECHO_N='-n';;
esac
rm -f conf$$ conf$$.exe conf$$.file
if test -d conf$$.dir; then
rm -f conf$$.dir/conf$$.file
else
rm -f conf$$.dir
mkdir conf$$.dir 2>/dev/null
fi
if (echo >conf$$.file) 2>/dev/null; then
if ln -s conf$$.file conf$$ 2>/dev/null; then
as_ln_s='ln -s'
# ... but there are two gotchas:
# 1) On MSYS, both `ln -s file dir' and `ln file dir' fail.
# 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable.
# In both cases, we have to default to `cp -pR'.
ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe ||
as_ln_s='cp -pR'
elif ln conf$$.file conf$$ 2>/dev/null; then
as_ln_s=ln
else
as_ln_s='cp -pR'
fi
else
as_ln_s='cp -pR'
fi
rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file
rmdir conf$$.dir 2>/dev/null
# as_fn_mkdir_p
# -------------
# Create "$as_dir" as a directory, including parents if necessary.
as_fn_mkdir_p ()
{
case $as_dir in #(
-*) as_dir=./$as_dir;;
esac
test -d "$as_dir" || eval $as_mkdir_p || {
as_dirs=
while :; do
case $as_dir in #(
*\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'(
*) as_qdir=$as_dir;;
esac
as_dirs="'$as_qdir' $as_dirs"
as_dir=`$as_dirname -- "$as_dir" ||
$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
X"$as_dir" : 'X\(//\)[^/]' \| \
X"$as_dir" : 'X\(//\)$' \| \
X"$as_dir" : 'X\(/\)' \| . 2>/dev/null ||
$as_echo X"$as_dir" |
sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
s//\1/
q
}
/^X\(\/\/\)[^/].*/{
s//\1/
q
}
/^X\(\/\/\)$/{
s//\1/
q
}
/^X\(\/\).*/{
s//\1/
q
}
s/.*/./; q'`
test -d "$as_dir" && break
done
test -z "$as_dirs" || eval "mkdir $as_dirs"
} || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir"
} # as_fn_mkdir_p
if mkdir -p . 2>/dev/null; then
as_mkdir_p='mkdir -p "$as_dir"'
else
test -d ./-p && rmdir ./-p
as_mkdir_p=false
fi
# as_fn_executable_p FILE
# -----------------------
# Test if FILE is an executable regular file.
as_fn_executable_p ()
{
test -f "$1" && test -x "$1"
} # as_fn_executable_p
as_test_x='test -x'
as_executable_p=as_fn_executable_p
# Sed expression to map a string onto a valid CPP name.
as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
# Sed expression to map a string onto a valid variable name.
as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"
exec 6>&1
## ----------------------------------- ##
## Main body of $CONFIG_STATUS script. ##
## ----------------------------------- ##
_ASEOF
test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1
cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# Save the log message, to keep $0 and so on meaningful, and to
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
This file was extended by $as_me, which was
generated by GNU Autoconf 2.69. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
CONFIG_HEADERS = $CONFIG_HEADERS
CONFIG_LINKS = $CONFIG_LINKS
CONFIG_COMMANDS = $CONFIG_COMMANDS
$ $0 $@
on `(hostname || uname -n) 2>/dev/null | sed 1q`
"
_ACEOF
case $ac_config_files in *"
"*) set x $ac_config_files; shift; ac_config_files=$*;;
esac
case $ac_config_headers in *"
"*) set x $ac_config_headers; shift; ac_config_headers=$*;;
esac
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
# Files that config.status was made for.
config_files="$ac_config_files"
config_headers="$ac_config_headers"
_ACEOF
cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
ac_cs_usage="\
\`$as_me' instantiates files and other configuration actions
from templates according to the current configuration. Unless the files
and actions are specified as TAGs, all are instantiated by default.
Usage: $0 [OPTION]... [TAG]...
-h, --help print this help, then exit
-V, --version print version number and configuration settings, then exit
--config print configuration, then exit
-q, --quiet, --silent
do not print progress messages
-d, --debug don't remove temporary files
--recheck update $as_me by reconfiguring in the same conditions
--file=FILE[:TEMPLATE]
instantiate the configuration file FILE
--header=FILE[:TEMPLATE]
instantiate the configuration header FILE
Configuration files:
$config_files
Configuration headers:
$config_headers
Report bugs to the package provider."
_ACEOF
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
ac_cs_version="\\
config.status
configured by $0, generated by GNU Autoconf 2.69,
with options \\"\$ac_cs_config\\"
Copyright (C) 2012 Free Software Foundation, Inc.
This config.status script is free software; the Free Software Foundation
gives unlimited permission to copy, distribute and modify it."
ac_pwd='$ac_pwd'
srcdir='$srcdir'
test -n "\$AWK" || AWK=awk
_ACEOF
cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# The default lists apply if the user does not specify any file.
ac_need_defaults=:
while test $# != 0
do
case $1 in
--*=?*)
ac_option=`expr "X$1" : 'X\([^=]*\)='`
ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'`
ac_shift=:
;;
--*=)
ac_option=`expr "X$1" : 'X\([^=]*\)='`
ac_optarg=
ac_shift=:
;;
*)
ac_option=$1
ac_optarg=$2
ac_shift=shift
;;
esac
case $ac_option in
# Handling of the options.
-recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r)
ac_cs_recheck=: ;;
--version | --versio | --versi | --vers | --ver | --ve | --v | -V )
$as_echo "$ac_cs_version"; exit ;;
--config | --confi | --conf | --con | --co | --c )
$as_echo "$ac_cs_config"; exit ;;
--debug | --debu | --deb | --de | --d | -d )
debug=: ;;
--file | --fil | --fi | --f )
$ac_shift
case $ac_optarg in
*\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;;
'') as_fn_error $? "missing file argument" ;;
esac
as_fn_append CONFIG_FILES " '$ac_optarg'"
ac_need_defaults=false;;
--header | --heade | --head | --hea )
$ac_shift
case $ac_optarg in
*\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;;
esac
as_fn_append CONFIG_HEADERS " '$ac_optarg'"
ac_need_defaults=false;;
--he | --h)
# Conflict between --help and --header
as_fn_error $? "ambiguous option: \`$1'
Try \`$0 --help' for more information.";;
--help | --hel | -h )
$as_echo "$ac_cs_usage"; exit ;;
-q | -quiet | --quiet | --quie | --qui | --qu | --q \
| -silent | --silent | --silen | --sile | --sil | --si | --s)
ac_cs_silent=: ;;
# This is an error.
-*) as_fn_error $? "unrecognized option: \`$1'
Try \`$0 --help' for more information." ;;
*) as_fn_append ac_config_targets " $1"
ac_need_defaults=false ;;
esac
shift
done
ac_configure_extra_args=
if $ac_cs_silent; then
exec 6>/dev/null
ac_configure_extra_args="$ac_configure_extra_args --silent"
fi
_ACEOF
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
if \$ac_cs_recheck; then
set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion
shift
\$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6
CONFIG_SHELL='$SHELL'
export CONFIG_SHELL
exec "\$@"
fi
_ACEOF
cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
exec 5>>config.log
{
echo
sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX
## Running $as_me. ##
_ASBOX
$as_echo "$ac_log"
} >&5
_ACEOF
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
_ACEOF
cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# Handling of arguments.
for ac_config_target in $ac_config_targets
do
case $ac_config_target in
"mod_proxy.h") CONFIG_HEADERS="$CONFIG_HEADERS mod_proxy.h" ;;
"t/Makefile") CONFIG_FILES="$CONFIG_FILES t/Makefile" ;;
"Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;;
*) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;;
esac
done
# If the user did not use the arguments to specify the items to instantiate,
# then the envvar interface is used. Set only those that are not.
# We use the long form for the default assignment because of an extremely
# bizarre bug on SunOS 4.1.3.
if $ac_need_defaults; then
test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files
test "${CONFIG_HEADERS+set}" = set || CONFIG_HEADERS=$config_headers
fi
# Have a temporary directory for convenience. Make it in the build tree
# simply because there is no reason against having it here, and in addition,
# creating and moving files from /tmp can sometimes cause problems.
# Hook for its removal unless debugging.
# Note that there is a small window in which the directory will not be cleaned:
# after its creation but before its name has been assigned to `$tmp'.
$debug ||
{
tmp= ac_tmp=
trap 'exit_status=$?
: "${ac_tmp:=$tmp}"
{ test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status
' 0
trap 'as_fn_exit 1' 1 2 13 15
}
# Create a (secure) tmp directory for tmp files.
{
tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` &&
test -d "$tmp"
} ||
{
tmp=./conf$$-$RANDOM
(umask 077 && mkdir "$tmp")
} || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5
ac_tmp=$tmp
# Set up the scripts for CONFIG_FILES section.
# No need to generate them if there are no CONFIG_FILES.
# This happens for instance with `./config.status config.h'.
if test -n "$CONFIG_FILES"; then
ac_cr=`echo X | tr X '\015'`
# On cygwin, bash can eat \r inside `` if the user requested igncr.
# But we know of no other shell where ac_cr would be empty at this
# point, so we can use a bashism as a fallback.
if test "x$ac_cr" = x; then
eval ac_cr=\$\'\\r\'
fi
ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' /dev/null`
if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then
ac_cs_awk_cr='\\r'
else
ac_cs_awk_cr=$ac_cr
fi
echo 'BEGIN {' >"$ac_tmp/subs1.awk" &&
_ACEOF
{
echo "cat >conf$$subs.awk <<_ACEOF" &&
echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' &&
echo "_ACEOF"
} >conf$$subs.sh ||
as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'`
ac_delim='%!_!# '
for ac_last_try in false false false false false :; do
. ./conf$$subs.sh ||
as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X`
if test $ac_delim_n = $ac_delim_num; then
break
elif $ac_last_try; then
as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
else
ac_delim="$ac_delim!$ac_delim _$ac_delim!! "
fi
done
rm -f conf$$subs.sh
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK &&
_ACEOF
sed -n '
h
s/^/S["/; s/!.*/"]=/
p
g
s/^[^!]*!//
:repl
t repl
s/'"$ac_delim"'$//
t delim
:nl
h
s/\(.\{148\}\)..*/\1/
t more1
s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/
p
n
b repl
:more1
s/["\\]/\\&/g; s/^/"/; s/$/"\\/
p
g
s/.\{148\}//
t nl
:delim
h
s/\(.\{148\}\)..*/\1/
t more2
s/["\\]/\\&/g; s/^/"/; s/$/"/
p
b
:more2
s/["\\]/\\&/g; s/^/"/; s/$/"\\/
p
g
s/.\{148\}//
t delim
' >$CONFIG_STATUS || ac_write_fail=1
rm -f conf$$subs.awk
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
_ACAWK
cat >>"\$ac_tmp/subs1.awk" <<_ACAWK &&
for (key in S) S_is_set[key] = 1
FS = ""
}
{
line = $ 0
nfields = split(line, field, "@")
substed = 0
len = length(field[1])
for (i = 2; i < nfields; i++) {
key = field[i]
keylen = length(key)
if (S_is_set[key]) {
value = S[key]
line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3)
len += length(value) + length(field[++i])
substed = 1
} else
len += 1 + keylen
}
print line
}
_ACAWK
_ACEOF
cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then
sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g"
else
cat
fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \
|| as_fn_error $? "could not setup config files machinery" "$LINENO" 5
_ACEOF
# VPATH may cause trouble with some makes, so we remove sole $(srcdir),
# ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and
# trailing colons and then remove the whole line if VPATH becomes empty
# (actually we leave an empty line to preserve line numbers).
if test "x$srcdir" = x.; then
ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{
h
s///
s/^/:/
s/[ ]*$/:/
s/:\$(srcdir):/:/g
s/:\${srcdir}:/:/g
s/:@srcdir@:/:/g
s/^:*//
s/:*$//
x
s/\(=[ ]*\).*/\1/
G
s/\n//
s/^[^=]*=[ ]*$//
}'
fi
cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
fi # test -n "$CONFIG_FILES"
# Set up the scripts for CONFIG_HEADERS section.
# No need to generate them if there are no CONFIG_HEADERS.
# This happens for instance with `./config.status Makefile'.
if test -n "$CONFIG_HEADERS"; then
cat >"$ac_tmp/defines.awk" <<\_ACAWK ||
BEGIN {
_ACEOF
# Transform confdefs.h into an awk script `defines.awk', embedded as
# here-document in config.status, that substitutes the proper values into
# config.h.in to produce config.h.
# Create a delimiter string that does not exist in confdefs.h, to ease
# handling of long lines.
ac_delim='%!_!# '
for ac_last_try in false false :; do
ac_tt=`sed -n "/$ac_delim/p" confdefs.h`
if test -z "$ac_tt"; then
break
elif $ac_last_try; then
as_fn_error $? "could not make $CONFIG_HEADERS" "$LINENO" 5
else
ac_delim="$ac_delim!$ac_delim _$ac_delim!! "
fi
done
# For the awk script, D is an array of macro values keyed by name,
# likewise P contains macro parameters if any. Preserve backslash
# newline sequences.
ac_word_re=[_$as_cr_Letters][_$as_cr_alnum]*
sed -n '
s/.\{148\}/&'"$ac_delim"'/g
t rset
:rset
s/^[ ]*#[ ]*define[ ][ ]*/ /
t def
d
:def
s/\\$//
t bsnl
s/["\\]/\\&/g
s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\
D["\1"]=" \3"/p
s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2"/p
d
:bsnl
s/["\\]/\\&/g
s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\
D["\1"]=" \3\\\\\\n"\\/p
t cont
s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2\\\\\\n"\\/p
t cont
d
:cont
n
s/.\{148\}/&'"$ac_delim"'/g
t clear
:clear
s/\\$//
t bsnlc
s/["\\]/\\&/g; s/^/"/; s/$/"/p
d
:bsnlc
s/["\\]/\\&/g; s/^/"/; s/$/\\\\\\n"\\/p
b cont
' >$CONFIG_STATUS || ac_write_fail=1
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
for (key in D) D_is_set[key] = 1
FS = ""
}
/^[\t ]*#[\t ]*(define|undef)[\t ]+$ac_word_re([\t (]|\$)/ {
line = \$ 0
split(line, arg, " ")
if (arg[1] == "#") {
defundef = arg[2]
mac1 = arg[3]
} else {
defundef = substr(arg[1], 2)
mac1 = arg[2]
}
split(mac1, mac2, "(") #)
macro = mac2[1]
prefix = substr(line, 1, index(line, defundef) - 1)
if (D_is_set[macro]) {
# Preserve the white space surrounding the "#".
print prefix "define", macro P[macro] D[macro]
next
} else {
# Replace #undef with comments. This is necessary, for example,
# in the case of _POSIX_SOURCE, which is predefined and required
# on some systems where configure will not decide to define it.
if (defundef == "undef") {
print "/*", prefix defundef, macro, "*/"
next
}
}
}
{ print }
_ACAWK
_ACEOF
cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
as_fn_error $? "could not setup config headers machinery" "$LINENO" 5
fi # test -n "$CONFIG_HEADERS"
eval set X " :F $CONFIG_FILES :H $CONFIG_HEADERS "
shift
for ac_tag
do
case $ac_tag in
:[FHLC]) ac_mode=$ac_tag; continue;;
esac
case $ac_mode$ac_tag in
:[FHL]*:*);;
:L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;;
:[FH]-) ac_tag=-:-;;
:[FH]*) ac_tag=$ac_tag:$ac_tag.in;;
esac
ac_save_IFS=$IFS
IFS=:
set x $ac_tag
IFS=$ac_save_IFS
shift
ac_file=$1
shift
case $ac_mode in
:L) ac_source=$1;;
:[FH])
ac_file_inputs=
for ac_f
do
case $ac_f in
-) ac_f="$ac_tmp/stdin";;
*) # Look for the file first in the build tree, then in the source tree
# (if the path is not absolute). The absolute path cannot be DOS-style,
# because $ac_f cannot contain `:'.
test -f "$ac_f" ||
case $ac_f in
[\\/$]*) false;;
*) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";;
esac ||
as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;;
esac
case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac
as_fn_append ac_file_inputs " '$ac_f'"
done
# Let's still pretend it is `configure' which instantiates (i.e., don't
# use $as_me), people would be surprised to read:
# /* config.h. Generated by config.status. */
configure_input='Generated from '`
$as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g'
`' by configure.'
if test x"$ac_file" != x-; then
configure_input="$ac_file. $configure_input"
{ $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5
$as_echo "$as_me: creating $ac_file" >&6;}
fi
# Neutralize special characters interpreted by sed in replacement strings.
case $configure_input in #(
*\&* | *\|* | *\\* )
ac_sed_conf_input=`$as_echo "$configure_input" |
sed 's/[\\\\&|]/\\\\&/g'`;; #(
*) ac_sed_conf_input=$configure_input;;
esac
case $ac_tag in
*:-:* | *:-) cat >"$ac_tmp/stdin" \
|| as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;;
esac
;;
esac
ac_dir=`$as_dirname -- "$ac_file" ||
$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
X"$ac_file" : 'X\(//\)[^/]' \| \
X"$ac_file" : 'X\(//\)$' \| \
X"$ac_file" : 'X\(/\)' \| . 2>/dev/null ||
$as_echo X"$ac_file" |
sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
s//\1/
q
}
/^X\(\/\/\)[^/].*/{
s//\1/
q
}
/^X\(\/\/\)$/{
s//\1/
q
}
/^X\(\/\).*/{
s//\1/
q
}
s/.*/./; q'`
as_dir="$ac_dir"; as_fn_mkdir_p
ac_builddir=.
case "$ac_dir" in
.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;;
*)
ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'`
# A ".." for each directory in $ac_dir_suffix.
ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'`
case $ac_top_builddir_sub in
"") ac_top_builddir_sub=. ac_top_build_prefix= ;;
*) ac_top_build_prefix=$ac_top_builddir_sub/ ;;
esac ;;
esac
ac_abs_top_builddir=$ac_pwd
ac_abs_builddir=$ac_pwd$ac_dir_suffix
# for backward compatibility:
ac_top_builddir=$ac_top_build_prefix
case $srcdir in
.) # We are building in place.
ac_srcdir=.
ac_top_srcdir=$ac_top_builddir_sub
ac_abs_top_srcdir=$ac_pwd ;;
[\\/]* | ?:[\\/]* ) # Absolute name.
ac_srcdir=$srcdir$ac_dir_suffix;
ac_top_srcdir=$srcdir
ac_abs_top_srcdir=$srcdir ;;
*) # Relative name.
ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix
ac_top_srcdir=$ac_top_build_prefix$srcdir
ac_abs_top_srcdir=$ac_pwd/$srcdir ;;
esac
ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix
case $ac_mode in
:F)
#
# CONFIG_FILE
#
_ACEOF
cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# If the template does not know about datarootdir, expand it.
# FIXME: This hack should be removed a few years after 2.60.
ac_datarootdir_hack=; ac_datarootdir_seen=
ac_sed_dataroot='
/datarootdir/ {
p
q
}
/@datadir@/p
/@docdir@/p
/@infodir@/p
/@localedir@/p
/@mandir@/p'
case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in
*datarootdir*) ac_datarootdir_seen=yes;;
*@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*)
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5
$as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;}
_ACEOF
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_datarootdir_hack='
s&@datadir@&$datadir&g
s&@docdir@&$docdir&g
s&@infodir@&$infodir&g
s&@localedir@&$localedir&g
s&@mandir@&$mandir&g
s&\\\${datarootdir}&$datarootdir&g' ;;
esac
_ACEOF
# Neutralize VPATH when `$srcdir' = `.'.
# Shell code in configure.ac might set extrasub.
# FIXME: do we really want to maintain this feature?
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_sed_extra="$ac_vpsub
$extrasub
_ACEOF
cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
:t
/@[a-zA-Z_][a-zA-Z_0-9]*@/!b
s|@configure_input@|$ac_sed_conf_input|;t t
s&@top_builddir@&$ac_top_builddir_sub&;t t
s&@top_build_prefix@&$ac_top_build_prefix&;t t
s&@srcdir@&$ac_srcdir&;t t
s&@abs_srcdir@&$ac_abs_srcdir&;t t
s&@top_srcdir@&$ac_top_srcdir&;t t
s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t
s&@builddir@&$ac_builddir&;t t
s&@abs_builddir@&$ac_abs_builddir&;t t
s&@abs_top_builddir@&$ac_abs_top_builddir&;t t
$ac_datarootdir_hack
"
eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \
>$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5
test -z "$ac_datarootdir_hack$ac_datarootdir_seen" &&
{ ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } &&
{ ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \
"$ac_tmp/out"`; test -z "$ac_out"; } &&
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir'
which seems to be undefined. Please make sure it is defined" >&5
$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir'
which seems to be undefined. Please make sure it is defined" >&2;}
rm -f "$ac_tmp/stdin"
case $ac_file in
-) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";;
*) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";;
esac \
|| as_fn_error $? "could not create $ac_file" "$LINENO" 5
;;
:H)
#
# CONFIG_HEADER
#
if test x"$ac_file" != x-; then
{
$as_echo "/* $configure_input */" \
&& eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs"
} >"$ac_tmp/config.h" \
|| as_fn_error $? "could not create $ac_file" "$LINENO" 5
if diff "$ac_file" "$ac_tmp/config.h" >/dev/null 2>&1; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: $ac_file is unchanged" >&5
$as_echo "$as_me: $ac_file is unchanged" >&6;}
else
rm -f "$ac_file"
mv "$ac_tmp/config.h" "$ac_file" \
|| as_fn_error $? "could not create $ac_file" "$LINENO" 5
fi
else
$as_echo "/* $configure_input */" \
&& eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" \
|| as_fn_error $? "could not create -" "$LINENO" 5
fi
;;
esac
done # for ac_tag
as_fn_exit 0
_ACEOF
ac_clean_files=$ac_clean_files_save
test $ac_write_fail = 0 ||
as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5
# configure is writing to config.log, and then calls config.status.
# config.status does its own redirection, appending to config.log.
# Unfortunately, on DOS this fails, as config.log is still kept open
# by configure, so config.status won't be able to write to it; its
# output is simply discarded. So we exec the FD to /dev/null,
# effectively closing config.log, so it can be properly (re)opened and
# appended to by config.status. When coming back to configure, we
# need to make the FD available again.
if test "$no_create" != yes; then
ac_cs_success=:
ac_config_status_args=
test "$silent" = yes &&
ac_config_status_args="$ac_config_status_args --quiet"
exec 5>/dev/null
$SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false
exec 5>>config.log
# Use ||, not &&, to avoid exiting from the if with $? = 1, which
# would make configure fail if this is the last instruction.
$ac_cs_success || as_fn_exit 1
fi
if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5
$as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;}
fi
proftpd-mod_proxy-0.9.5/configure.in 0000664 0000000 0000000 00000026661 14757370167 0017566 0 ustar 00root root 0000000 0000000 dnl ProFTPD - mod_proxy
dnl Copyright (c) 2012-2022 TJ Saunders
dnl
dnl This program is free software; you can redistribute it and/or modify
dnl it under the terms of the GNU General Public License as published by
dnl the Free Software Foundation; either version 2 of the License, or
dnl (at your option) any later version.
dnl
dnl This program is distributed in the hope that it will be useful,
dnl but WITHOUT ANY WARRANTY; without even the implied warranty of
dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
dnl GNU General Public License for more details.
dnl
dnl You should have received a copy of the GNU General Public License
dnl along with this program; if not, write to the Free Software
dnl Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
dnl
dnl Process this file with autoconf to produce a configure script.
AC_INIT(./mod_proxy.c)
AC_CANONICAL_SYSTEM
ostype=`echo $build_os | sed 's/\..*$//g' | sed 's/-.*//g' | tr abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ`
AC_PROG_CC
AC_PROG_CPP
AC_AIX
AC_ISC_POSIX
AC_MINIX
AC_PROG_MAKE_SET
dnl Need to support/handle the --with-includes, --with-libraries, --enable-tests
dnl options
AC_ARG_WITH(includes,
[AC_HELP_STRING(
[--with-includes=LIST],
[add additional include paths to proftpd. LIST is a colon-separated list of include paths to add e.g. --with-includes=/some/mysql/include:/my/include])
],
[ ac_addl_includes=`echo "$withval" | sed -e 's/:/ /g'` ;
for ainclude in $ac_addl_includes; do
if test x"$ac_build_addl_includes" = x ; then
ac_build_addl_includes="-I$ainclude"
else
ac_build_addl_includes="-I$ainclude $ac_build_addl_includes"
fi
done
CPPFLAGS="$CPPFLAGS $ac_build_addl_includes"
])
AC_ARG_WITH(libraries,
[AC_HELP_STRING(
[--with-libraries=LIST],
[add additional library paths to proftpd. LIST is a colon-separated list of include paths to add e.g. --with-libraries=/some/mysql/libdir:/my/libs])
],
[ ac_addl_libdirs=`echo "$withval" | sed -e 's/:/ /g'` ;
for alibdir in $ac_addl_libdirs; do
if test x"$ac_build_addl_libdirs" = x ; then
ac_build_addl_libdirs="-L$alibdir"
else
ac_build_addl_libdirs="-L$alibdir $ac_build_addl_libdirs"
fi
done
LDFLAGS="$LDFLAGS $ac_build_addl_libdirs"
])
ENABLE_TESTS="\"\""
AC_ARG_ENABLE(tests,
[AC_HELP_STRING(
[--enable-tests],
[enable unit tests (default=no)])
],
[
if test x"$enableval" != xno ; then
AC_CHECK_HEADERS(check.h)
AC_CHECK_LIB(check, tcase_create,
[AC_DEFINE(HAVE_LIBCHECK, 1, [Define if libcheck is present.])
ENABLE_TESTS="1"
],
[
AC_MSG_ERROR([libcheck support, required for tests, not present -- aborting])
]
)
fi
])
AC_HEADER_STDC
AC_CHECK_HEADERS(sqlite3.h stdlib.h unistd.h limits.h fcntl.h sys/sysctl.h sys/sysinfo.h)
AC_CHECK_HEADER(zlib.h,
[AC_DEFINE(HAVE_ZLIB_H, 1, [Define if zlib.h is present.])
MODULE_LIBS="$MODULE_LIBS -lz"
])
AC_CHECK_FUNCS(random srandom strnstr sysctl sysinfo)
dnl Check whether libc provides the DNS resolver symbols (e.g. *BSD/Mac OSX)
dnl or not. And if not, check whether we need to link directly with
dnl /usr/lib/libresolv.a (32-bit) or /usr/lib64/libresolv.a (64-bit).
dnl
dnl Ideally we would link with libresolv using -lresolv. However, it seems
dnl that many Linux distributions shipped a broken version of libresolv.so
dnl which did not export the necessary ns_initparse/ns_parserr symbols. The
dnl static version of libresolv shipped DOES provide those symbols (probably
dnl for use by libc). For these cases, we link againt the static library.
ac_dns_libs=""
AC_MSG_CHECKING([for resolver symbols in libc])
AC_TRY_LINK(
[ #include
#include
#include
#include
],
[
int res;
res = res_query(NULL, ns_c_in, ns_t_txt, NULL, 0);
res = ns_initparse(NULL, 0, NULL);
res = ns_parserr(NULL, ns_s_an, 0, NULL);
],
[
AC_MSG_RESULT(yes)
],
[
AC_MSG_RESULT(no)
AC_MSG_CHECKING([for resolver symbols in libresolv])
saved_libs="$LIBS"
LIBS="$LIBS -lresolv"
AC_TRY_LINK(
[ #include
#include
#include
#include
],
[
int res;
res = res_query(NULL, ns_c_in, ns_t_txt, NULL, 0);
res = ns_initparse(NULL, 0, NULL);
res = ns_parserr(NULL, ns_s_an, 0, NULL);
],
[
AC_MSG_RESULT(yes)
LIBS="$saved_libs"
ac_dns_libs="-lresolv"
],
[
AC_MSG_RESULT(no)
LIBS="$saved_libs"
AC_MSG_CHECKING([for resolver symbols in /usr/lib64/libresolv.a])
saved_libs="$LIBS"
LIBS="$LIBS /usr/lib64/libresolv.a"
AC_TRY_LINK(
[ #include
#include
#include
#include
],
[
int res;
res = res_query(NULL, ns_c_in, ns_t_txt, NULL, 0);
res = ns_initparse(NULL, 0, NULL);
res = ns_parserr(NULL, ns_s_an, 0, NULL);
],
[
AC_MSG_RESULT(yes)
LIBS="$saved_libs"
ac_dns_libs="/usr/lib64/libresolv.a"
],
[
AC_MSG_RESULT(no)
LIBS="$saved_libs"
AC_MSG_CHECKING([for resolver symbols in /usr/lib/libresolv.a])
saved_libs="$LIBS"
LIBS="$LIBS /usr/lib/libresolv.a"
AC_TRY_LINK(
[ #include
#include
#include
#include
],
[
int res;
res = res_query(NULL, ns_c_in, ns_t_txt, NULL, 0);
res = ns_initparse(NULL, 0, NULL);
res = ns_parserr(NULL, ns_s_an, 0, NULL);
],
[
AC_MSG_RESULT(yes)
LIBS="$saved_libs"
ac_dns_libs="/usr/lib/libresolv.a"
],
[
AC_MSG_RESULT(no)
LIBS="$saved_libs"
]
)
]
)
]
)
]
)
# Check for OpenSSL-isms
AC_MSG_CHECKING([whether OpenSSL has crippled AES support])
LIBS="-lcrypto $LIBS"
AC_TRY_LINK(
[ #ifdef HAVE_STRING_H
# include
#endif
#include
],
[
EVP_CIPHER *c;
c = EVP_aes_192_cbc();
c = EVP_aes_256_cbc();
],
[
AC_MSG_RESULT(no)
LIBS="$saved_libs"
],
[
AC_MSG_RESULT(yes)
AC_DEFINE(HAVE_AES_CRIPPLED_OPENSSL, 1, [OpenSSL is missing AES192 and AES256 support])
LIBS="$saved_libs"
]
)
AC_MSG_CHECKING([whether OpenSSL supports SHA256])
LIBS="-lcrypto $LIBS"
AC_TRY_LINK(
[
#include
],
[
const EVP_MD *md;
md = EVP_sha256();
],
[
AC_MSG_RESULT(yes)
AC_DEFINE(HAVE_SHA256_OPENSSL, 1, [OpenSSL supports SHA224/SHA256])
LIBS="$saved_libs"
],
[
AC_MSG_RESULT(no)
LIBS="$saved_libs"
]
)
AC_MSG_CHECKING([whether OpenSSL supports SHA512])
LIBS="-lcrypto $LIBS"
AC_TRY_LINK(
[
#include
],
[
const EVP_MD *md;
md = EVP_sha512();
],
[
AC_MSG_RESULT(yes)
AC_DEFINE(HAVE_SHA512_OPENSSL, 1, [OpenSSL supports SHA384/SHA512])
LIBS="$saved_libs"
],
[
AC_MSG_RESULT(no)
LIBS="$saved_libs"
]
)
AC_MSG_CHECKING([whether OpenSSL supports EVP_aes_128_ctr])
LIBS="-lcrypto $LIBS"
AC_TRY_LINK(
[
#include
],
[
EVP_CIPHER *cipher;
cipher = EVP_aes_128_ctr();
],
[
AC_MSG_RESULT(yes)
AC_DEFINE(HAVE_EVP_AES_128_CTR_OPENSSL, 1, [OpenSSL supports EVP_aes_128_ctr])
LIBS="$saved_libs"
],
[
AC_MSG_RESULT(no)
LIBS="$saved_libs"
]
)
AC_MSG_CHECKING([whether OpenSSL supports EVP_aes_192_ctr])
LIBS="-lcrypto $LIBS"
AC_TRY_LINK(
[
#include
],
[
EVP_CIPHER *cipher;
cipher = EVP_aes_192_ctr();
],
[
AC_MSG_RESULT(yes)
AC_DEFINE(HAVE_EVP_AES_192_CTR_OPENSSL, 1, [OpenSSL supports EVP_aes_192_ctr])
LIBS="$saved_libs"
],
[
AC_MSG_RESULT(no)
LIBS="$saved_libs"
]
)
AC_MSG_CHECKING([whether OpenSSL supports EVP_aes_256_ctr])
LIBS="-lcrypto $LIBS"
AC_TRY_LINK(
[
#include
],
[
EVP_CIPHER *cipher;
cipher = EVP_aes_256_ctr();
],
[
AC_MSG_RESULT(yes)
AC_DEFINE(HAVE_EVP_AES_256_CTR_OPENSSL, 1, [OpenSSL supports EVP_aes_256_ctr])
LIBS="$saved_libs"
],
[
AC_MSG_RESULT(no)
LIBS="$saved_libs"
]
)
AC_MSG_CHECKING([whether OpenSSL supports EVP_aes_256_gcm])
LIBS="-lcrypto $LIBS"
AC_TRY_LINK(
[
#include
],
[
EVP_CIPHER *cipher;
cipher = EVP_aes_256_gcm();
],
[
AC_MSG_RESULT(yes)
AC_DEFINE(HAVE_EVP_AES_256_GCM_OPENSSL, 1, [OpenSSL supports EVP_aes_256_gcm])
LIBS="$saved_libs"
],
[
AC_MSG_RESULT(no)
LIBS="$saved_libs"
]
)
AC_MSG_CHECKING([whether OpenSSL supports X448 algorithm])
LIBS="-lcrypto $LIBS"
AC_TRY_LINK(
[
#include
],
[
EVP_PKEY *pkey;
EVP_PKEY_CTX *pctx;
pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_X448, NULL, NULL, 0);
pctx = EVP_PKEY_CTX_new_id(NID_X448, NULL);
],
[
AC_MSG_RESULT(yes)
AC_DEFINE(HAVE_X448_OPENSSL, 1, [OpenSSL supports X448 algorithm])
LIBS="$saved_libs"
],
[
AC_MSG_RESULT(no)
LIBS="$saved_libs"
]
)
AC_MSG_CHECKING([whether OpenSSL supports OSSL_PROVIDER_load])
LIBS="-lcrypto $LIBS"
AC_TRY_LINK(
[
#include
],
[
OSSL_PROVIDER *provider;
provider = OSSL_PROVIDER_load(NULL, "default");
],
[
AC_MSG_RESULT(yes)
AC_DEFINE(HAVE_OSSL_PROVIDER_LOAD_OPENSSL, 1, [OpenSSL supports OSSL_PROVIDER_load])
LIBS="$saved_libs"
],
[
AC_MSG_RESULT(no)
LIBS="$saved_libs"
]
)
# Check for SQLite-isms
AC_MSG_CHECKING([for sqlite3_stmt_readonly])
saved_libs="$LIBS"
LIBS="-lsqlite3"
AC_TRY_LINK([
#include
#include
#ifdef HAVE_SQLITE3_H
# include
#endif
], [
(void) sqlite3_stmt_readonly(NULL);
], [
AC_MSG_RESULT(yes)
AC_DEFINE(HAVE_SQLITE3_STMT_READONLY, 1, [Define if you have the sqlite3_stmt_readonly function])
], [
AC_MSG_RESULT(no)
]
)
LIBS="$saved_libs"
AC_MSG_CHECKING([for sqlite3_trace])
saved_libs="$LIBS"
LIBS="-lsqlite3"
AC_TRY_LINK([
#include
#include
#ifdef HAVE_SQLITE3_H
# include
#endif
], [
(void) sqlite3_trace(NULL, NULL, NULL);
], [
AC_MSG_RESULT(yes)
AC_DEFINE(HAVE_SQLITE3_TRACE, 1, [Define if you have the sqlite3_trace function])
], [
AC_MSG_RESULT(no)
]
)
LIBS="$saved_libs"
AC_MSG_CHECKING([for sqlite3_trace_v2])
saved_libs="$LIBS"
LIBS="-lsqlite3"
AC_TRY_LINK([
#include
#include
#ifdef HAVE_SQLITE3_H
# include
#endif
], [
(void) sqlite3_trace_v2(NULL, 0, NULL, NULL);
], [
AC_MSG_RESULT(yes)
AC_DEFINE(HAVE_SQLITE3_TRACE_V2, 1, [Define if you have the sqlite3_trace_v2 function])
], [
AC_MSG_RESULT(no)
]
)
LIBS="$saved_libs"
INCLUDES="$ac_build_addl_includes"
LIBDIRS="$ac_build_addl_libdirs"
MODULE_LIBS="$MODULE_LIBS $ac_dns_libs"
AC_SUBST(ENABLE_TESTS)
AC_SUBST(INCLUDES)
AC_SUBST(LDFLAGS)
AC_SUBST(LIBDIRS)
AC_SUBST(MODULE_LIBS)
AC_CONFIG_HEADER(mod_proxy.h)
AC_OUTPUT(
t/Makefile
Makefile
)
proftpd-mod_proxy-0.9.5/doc/ 0000775 0000000 0000000 00000000000 14757370167 0016007 5 ustar 00root root 0000000 0000000 proftpd-mod_proxy-0.9.5/doc/NOTES 0000664 0000000 0000000 00000015060 14757370167 0016624 0 ustar 00root root 0000000 0000000
Benefits:
Front servers which can't do: PASV, EPRT/EPSV, FTPS
Can use to do your logging: TransferLog, ExtendedLog, mod_log_zmq, etc.
Can use to do your monitoring: mod_snmp, etc
Single SSL cert (on the proxy) for several different backend servers
Have mod_netsieve here, for sieving the inbound commands and outbound files
(have netsieve rules for common cases like scanning for SSNs/credit card
numbers in outbound data)
What would a proxy module for proftpd look like?
client <-------> mod_proxy <--------> FTP server
So we'd be proxying FTP connections to other FTP servers, including data
transfers. Would we support both forward and reverse proxy configurations?
* Set DefaultServer on in the section using mod_proxy (or
don't set it all). That way, on a proxy server, only connections to
vhosts configured for proxying are handled; others are rejected.
Forward Proxy
Need some kind of mechanism that clients can use to specify their end
targets.
Reverse Proxy
Terminates any SSL session (backend connection to use SSL or not?)
What credentials to use for authenticating to backend server for a reverse
proxy connection? Modifying of directory listing results as necessary
How to map clients to origin servers? By user name, by client IP, or...?
Re-use proxy USER/PASS with origin server, or override with shared/common
origin server USER/PASS? Round-robin, consistent hashing, load balance?
*Do it based on USER: that's the one feature that mod_proxy can do (i.e.
it's protocol-aware); the other implementations are TCP-specific, and
can be done by nginx, haproxy, etc.
If map/lookup is done based on client IP, then it can be done at connect
time, and thus mod_proxy can relay the connection to the remote host, and
let all authentication be handled by the remote/target host.
Scenarios:
Complex:
backend server selection based on USER; this means mod_proxy needs to
handle login, _then_ select backend and create control connection to
selected backend.
*NB: Actually, the selection of the backend user *can* happen based
on just the USER command, no PASS; this assumes that the auth will
either succeed using the given USER, or it won't (and the connection
will be closed). This distinction means that the proxy does not
necessarily have to handle authentication itself.
Balancing (or: selection of backend server)
ProxyReverseConnect
roundrobin [options]
hashing (?) [options]
userbased [options]
ProxyReverseServers ftp://localhost:20741 ftp://localhost:20743
ProxyReverseServers file://path/to/servers.txt
sql://SQLNamedQuery/...
ldap://...?
HAproxy PROXY protocol
http://haproxy.1wt.eu/download/1.5/doc/proxy-protocol.txt
http://ben.timby.com/?page_id=210
Implemented in mod_proxy_protocol.
Any caching of proxied results, files, directory transfers, etc?
Use of DNAT/SNAT, so the remote/target server sees real IP of connecting
client, rather than that of the proxy? Or how else to inform the
backend/origin of the connecting client info? Keep in mind this possibility:
client -> proxy -> proxy -> proxy -> proxy -> server
FTP to SFTP proxying:
http://superuser.com/questions/422348/ftp-to-sftp-scp-proxy
for securing outbound connections (in a forward proxy mode), as well as SFTP
to FTP proxying (e.g. for SFTP for external clients, FTP for internal/legacy
servers) for reverse proxying.
* Use URIs for specifying protocol proxying; means needing a URI parser
good enough to extract scheme, host, port, and perhaps authority.
Health checks
TCP
Data Transfers:
MODE Z on frontend, backend?
Load balancing
Failover
Timeouts
ProxyConnectTimeout
Reverse connectivity feature? (Requires that the backend server know to
connect to proxy for "listening" connection, so custom work.)
HTTP CONNECT
Client sends CONNECT to proxy, then proceeds to do an _FTP_ control channel
through the established TCP connection.
* Can this be used to proxy/hide the origin of an SSH connection as well?
I think so...
client --> (sftp) --> proxy --> (ftp) --> origin
client --> (ftp) --> proxy --> (sftp) --> origin
Other Proxy Implementations
http://www.mcknight.de/jftpgw/features.html
http://aggemam.dk/ftpproxy
Forward FTP proxy, written in Java
http://www.glub.com/
Secure FTP Wrapper; see their "Login" article
http://frox.sourceforge.net/
http://www.ftpproxy.org/
http://freecode.com/projects/suseproxy-suite
http://www.linuxjournal.com/magazine/configuring-and-using-ftp-proxy
httpd.apache.org/docs/2.2/mod/mod_proxy.html
http://forums.devshed.com/ftp-help-113/apache-ftp-reverse-proxy-78960.html
http://www.apachelounge.com/viewtopic.php?t=3677
http://www.faqs.org/rfcs/rfc1919.html ("Classical Vs Transparent IP Proxies")
http://www.faqs.org/rfcs/rfc1579.html ("Firewall-Friendly FTP")
https://calomel.org/ftp_proxy.html
ftp-proxy started off as pftpx?
https://calomel.org/
http://www.codeproject.com/KB/IP/ProxyFtp.aspx
http://www.chilkatsoft.com/refdoc/xChilkatFtp2Ref.html
See ProxyMethod for various FTP proxy approaches
http://linux.die.net/man/1/lftp
ftp:proxy
ftp:proxy-auth-type
http://www.apsis.ch/pound/index_html
http://haproxy.1wt.eu/
http://www.loadbalancer.org/
http://www.tomkleinpeter.com/2008/03/17/programmers-toolbox-part-3-consistent-hashing/
http://cr.yp.to/ftpparse.html
Use for parsing FTP lists into SFTP dirlist
What about MLSD, though? Need to find parser for that, if avail
http://software.clapper.org/grizzled-python/epydoc/grizzled.net.ftp.parse.FTPMlstDataParser-class.html
Links/Articles:
http://ben.timby.com/?page_id=210
http://www.taiter.com/techlog/2012/09/ftp-load-balanced-through-haproxy.html
FTP load balancing through HAproxy
http://blog.cryptographyengineering.com/2012/03/how-do-interception-proxies-fail.html
ftpcluster:
http://www.awk-scripting.de/cluster/
Uses different cluster server addresses in PORT/PASV, and FXP among
cluster nodes. Interesting idea.
Goals
Contrast with Squid, Apache mod_proxy, others
Testing
Net::FTP in Perl; use Firewall, FirewallType constructor args. See
Net::Config perldocs (quite interesting, actually).
Net::SOCKS
IO::Socket::SOCKS
IO::Socket::SecureSocks
URI::Socks
Net::Proxy::Type
Need handle cases like:
client --> proxy <--> SOCKS/HTTP proxy <--> server
i.e. where the proxy needs to be able to tunnel its connections through
SOCKS/HTTP proxies.
proftpd-mod_proxy-0.9.5/doc/NOTES.dirlist-parsing 0000664 0000000 0000000 00000001376 14757370167 0021743 0 ustar 00root root 0000000 0000000
At some point, especially for protocol conversions, mod_proxy may need to
parse the directory listing from the backend server to give to the frontend
client. Thus we'll need some dirlist-parsing code.
Here's a fun one, from the lftp changes:
fixed MLSD parsing for semicolons in file names.
See:
http://lftp.yar.ru/news.html
Maybe lftp has MLSD parsing code to reuse?
It does indeed; see lftp-N.N.N/src/FtpListInfo.{h,cc}. Purportedly parses
Unix, MLSD, OS/2, NT, AS400, and EPLF. Could write proxy module that does
this. Note that FtpListInfo iterates through list of parsers to find the one
which handles the current format; do same, but remember the parser which
worked, per session/connection, so that such iteration isn't necessary
per-dirlist.
proftpd-mod_proxy-0.9.5/doc/NOTES.dns-srv 0000664 0000000 0000000 00000026723 14757370167 0020227 0 ustar 00root root 0000000 0000000
Use Docker Compose and:
proftpd+mod_proxy
proftpd/pure-ftpd
dnsmasq
Implementation:
ProxyDNSOptions
UseSRV (or +SRV)
UseTXT (or +TXT)
* allows room for later -A, -AAA
OR, even better:
ftp+srv://server.example.com
ftps+srv://server.example.com
DNS SRV query:
_ftp._tcp.server.example.com
Same for TXT records?
ftp+txt://server.example.com
ftps+txt://server.example.com
See:
https://docs.mongodb.com/manual/reference/connection-string/#connections-dns-seedlist
These would require changes to the URI parsing, Conn API, for "hints"?
NOTE: Port numbers are NOT allowed in the URL if the SRV scheme is used!
Why not? Because port numbers are returned in the SRV records themselves;
avoid any possible collisions/conflicts. Besides, mutiple SRV records
for the same service name might have different ports.
Similarly for TXT scheme; the port will be part of the URLs found in
the TXT records.
If ports ARE found in such URLs, they will be (logged and) ignored.
NOTE:
TXT records found to have URLs must NOT use the +txt, +srv schemes.
lib/proxy/dns.c
typedef enum {
PROXY_DNS_SRV,
PROXY_DNS_TXT
} proxy_dns_typ_e;
int proxy_dns_resolve(pool *p, const char *name, proxy_dns_type_e dns_type,
array_header **addrs);
NOTE:
Check that the A, AAAA records for SRV targets MATCH the initial name's
domain. If there is a domain mismatch, do we reject/skip that target?
Answer: For now, no. Accept the given target names.
NOTE: Watch/honor the TTLs on the retrieved records. A given SRV record
will have its own TTL; the target resource records (included, or
retrieved separately) will have their own TTLs (often shorter). Even
if we go with the shortest TTL, and set a timer, that timer will be
specific to that pconn object.
In order to honor the TTLs, we'd need to schedule timers -- but only in
the daemon/master process, NOT session/child processes. (Thus we'd need
to remove these re-resolve timers on sess_init.) We would need to
track the timer ID in the pconn struct; the timer callback would take
that pconn as an argument, to update the addr, port, other addrs. And
what about the memory pool to use for re-resolves, considering a long-lived
daemon process?
If resolution fails in timer, leave existing pconn (and timer) as is.
These TTL timers ONLY need to be there for URLs parsed at start-up time,
by the daemon/master process. This means we don't need it for URLs
obtained from SQL databases, Redis servers, per-user/group, etc.
Test:
SRV records:
one
none
per RFC 2782, if none, fallback to A record.
multiple
multiple weighted/prioritized
* when to query/look at RRs in "additional data" section? ns_s_ar (vs ns_s_an)?
Answer:
Target
The domain name of the target host. There MUST be one or more
address records for this name, the name MUST NOT be an alias (in
the sense of RFC 1034 or RFC 2181). Implementors are urged, but
not required, to return the address record(s) in the Additional
Data section. Unless and until permitted by future standards
action, name compression is not to be used for this field.
A Target of "." means that the service is decidedly not
available at this domain.
Need an example for testing?
$ dig _imaps._tcp.gmail.com SRV
; <<>> DiG 9.8.3-P1 <<>> _imaps._tcp.gmail.com SRV
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 17898
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 4
;; QUESTION SECTION:
;_imaps._tcp.gmail.com. IN SRV
;; ANSWER SECTION:
_imaps._tcp.gmail.com. 86400 IN SRV 5 0 993 imap.gmail.com.
;; ADDITIONAL SECTION:
imap.gmail.com. 96 IN A 74.125.20.108
imap.gmail.com. 96 IN A 74.125.20.109
imap.gmail.com. 96 IN AAAA 2607:f8b0:400e:c08::6d
imap.gmail.com. 96 IN AAAA 2607:f8b0:400e:c08::6c
Note that `dig gmail.com ANY` does NOT return the SRV records; you have to
query for them specifically. This also means that the name to be resolved
must be specifically constructed for SRV lookup!
ftp+srv://example.com -> _ftp._tcp.example.com
ftps+srv://example.com -> _ftp._tcp.example.com
Fun -- watch what happens for different nameservers:
# Here, we get the add'l records...
$ dig _imaps._tcp.gmail.com SRV @75.75.75.75
; <<>> DiG 9.8.3-P1 <<>> _imaps._tcp.gmail.com SRV @75.75.75.75
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 9937
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 4
;; QUESTION SECTION:
;_imaps._tcp.gmail.com. IN SRV
;; ANSWER SECTION:
_imaps._tcp.gmail.com. 86400 IN SRV 5 0 993 imap.gmail.com.
;; ADDITIONAL SECTION:
imap.gmail.com. 79 IN A 74.125.195.108
imap.gmail.com. 79 IN A 74.125.195.109
imap.gmail.com. 79 IN AAAA 2607:f8b0:400e:c04::6d
imap.gmail.com. 79 IN AAAA 2607:f8b0:400e:c04::6c
;; Query time: 32 msec
;; SERVER: 75.75.75.75#53(75.75.75.75)
;; WHEN: Sat Nov 14 20:13:05 2020
;; MSG SIZE rcvd: 161
# Here, we do NOT get the add'l records. Something to handle.
$ dig _imaps._tcp.gmail.com SRV @8.8.8.8
; <<>> DiG 9.8.3-P1 <<>> _imaps._tcp.gmail.com SRV @8.8.8.8
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 24242
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;_imaps._tcp.gmail.com. IN SRV
;; ANSWER SECTION:
_imaps._tcp.gmail.com. 21599 IN SRV 5 0 993 imap.gmail.com.
;; Query time: 30 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Sat Nov 14 20:13:59 2020
;; MSG SIZE rcvd: 73
Means that some resolvers might helpfully fill in the add'l records.
Need to find (or configure) multiple SRV records, pointing at different targets;
what would the add'l section look like for that?
Here's an example where the target does NOT match the domain:
$ dig _sipfederationtls._tcp.outlook.com SRV
; <<>> DiG 9.8.3-P1 <<>> _sipfederationtls._tcp.outlook.com SRV
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 7910
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;_sipfederationtls._tcp.outlook.com. IN SRV
;; ANSWER SECTION:
_sipfederationtls._tcp.outlook.com. 300 IN SRV 10 2 5061 federation.messenger.msn.com.
;; Query time: 38 msec
;; SERVER: 75.75.75.75#53(75.75.75.75)
;; WHEN: Sat Nov 14 20:20:46 2020
;; MSG SIZE rcvd: 100
Another fun one -- note the LACK of add'l data this time!
* found here: https://stackoverflow.com/questions/10138844/java-dns-lookup-for-srv-records
$ dig _nicname._tcp.uk SRV
; <<>> DiG 9.8.3-P1 <<>> _nicname._tcp.uk SRV
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 49671
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;_nicname._tcp.uk. IN SRV
;; ANSWER SECTION:
_nicname._tcp.uk. 172800 IN SRV 0 0 43 whois.nic.uk.
;; Query time: 42 msec
;; SERVER: 75.75.75.75#53(75.75.75.75)
;; WHEN: Sat Nov 14 20:41:50 2020
;; MSG SIZE rcvd: 66
And here:
$ dig _ldap._tcp.ru.ac.za SRV
; <<>> DiG 9.8.3-P1 <<>> _ldap._tcp.ru.ac.za SRV
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 56234
;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;_ldap._tcp.ru.ac.za. IN SRV
;; ANSWER SECTION:
_ldap._tcp.ru.ac.za. 21600 IN SRV 1 0 389 bushbaby.ru.ac.za.
_ldap._tcp.ru.ac.za. 21600 IN SRV 2 0 389 jackal.ru.ac.za.
_ldap._tcp.ru.ac.za. 21600 IN SRV 2 0 389 gecko.ru.ac.za.
;; Query time: 167 msec
;; SERVER: 75.75.75.75#53(75.75.75.75)
;; WHEN: Sun Nov 15 09:25:27 2020
;; MSG SIZE rcvd: 143
What about sftp? What will the published service name be for that? ssh,
or sftp?
https://github.com/Crosse/sshsrv
TXT records:
one
none
multiple
non-FTP URLs
CLI:
# For host, dig commands
$ apt-get install -y bind9-host dnsutils
$ dig _ftp._tcp.castaglia.local SRV
...
;; ANSWER SECTION:
_ftp._tcp.castaglia.local. 0 IN SRV 3 0 2121 proxy.castaglia.org.
$ dig castaglia.local TXT
...
;; ANSWER SECTION:
castaglia.local. 0 IN TXT "foo bar baz"
References:
https://gist.github.com/ajdavis/e5f5ddbf50b5aecdc5e1d686d72a8a7a
https://stackoverflow.com/questions/58845991/make-the-dns-server-of-docker-container-another-docker-container-running-dnsmasq
https://alejandrocelaya.blog/2017/04/21/set-specific-ip-addresses-to-docker-containers-created-with-docker-compose/
https://oliver-kaestner.de/english-c-query-srv-dns-record-with-example/
https://tools.ietf.org/html/rfc2782 (DNS SRV)
https://people.samba.org/bzr/jerry/slag/unix/query-srv.c
https://docs.mongodb.com/manual/reference/connection-string/
https://jdebp.eu/FGA/dns-srv-record-use-by-clients.html
http://dns.vanrein.org/srv/tools/
https://github.com/lavv17/lftp/blob/master/src/Resolver.cc
https://github.com/systemmonkey42/libsrv/blob/master/src/libsrv.c
https://tools.ietf.org/html/draft-andrews-http-srv-01
https://tools.ietf.org/html/draft-jennings-http-srv-05
Future:
For A records:
https://github.com/haproxy/haproxy/blob/master/src/dns.c#L971
For AAAA records:
https://github.com/haproxy/haproxy/blob/master/src/dns.c#L1032
For CNAME records:
https://github.com/haproxy/haproxy/blob/master/src/dns.c#L981
Tidbits:
This is already handled automagically by res_query(3), but for reference:
static int dns_check_response(ns_msg *msgh, const char *query_type) {
int flag, res;
flag = ns_msg_getflag(*msgh, ns_f_rcode);
switch (flag) {
case ns_r_noerror:
res = 0;
break;
case ns_r_formerr:
pr_trace_msg(trace_channel, 7,
"received 'Format error' response code (%d) in %s answer", flag,
query_type);
errno = EINVAL;
res = -1;
break;
case ns_r_servfail:
pr_trace_msg(trace_channel, 7,
"received 'Server failure' response code (%d) in %s answer", flag,
query_type);
errno = EPERM;
res = -1;
break;
case ns_r_nxdomain:
pr_trace_msg(trace_channel, 7,
"received 'No such domain' response code (%d) in %s answer", flag,
query_type);
errno = ENOENT;
res = -1;
break;
case ns_r_notimpl:
pr_trace_msg(trace_channel, 7,
"received 'Unimplemented' response code (%d) in %s answer", flag,
query_type);
errno = EPERM;
res = -1;
break;
case ns_r_refused:
pr_trace_msg(trace_channel, 7,
"received 'Operation refused' response code (%d) in %s answer", flag,
query_type);
errno = EPERM;
res = -1;
break;
default:
pr_trace_msg(trace_channel, 7,
"received unknown response code (%d) in %s answer", flag, query_type);
errno = EPERM;
res = -1;
}
if (res < 0) {
return -1;
}
if (pr_trace_get_level(trace_channel) <= 7) {
return res;
}
/* Log any other flags of interest.
*
* If the response was truncated, the libc resolver's default behavior is
* to retry the query over TCP. Given that, the main flag of interest is
* whether this answer was authoritative (from the authoritative nameserver)
* or from some cache along the way.
*/
flag = ns_msg_getflag(*msgh, ns_f_aa);
if (flag) {
pr_trace_msg(trace_channel, 9,
"received AUTHORITATIVE answer for %s query", query_type);
} else {
pr_trace_msg(trace_channel, 19,
"received cached answer for %s query", query_type);
}
return res;
}
proftpd-mod_proxy-0.9.5/doc/NOTES.forward-proxy 0000664 0000000 0000000 00000005255 14757370167 0021453 0 ustar 00root root 0000000 0000000
Supported forward proxy method:
USER user@dest-server[:port]
PASS pass
Chilkatsoft.com ChilkatFtp2.ProxyMethod = 2
Net::Config:
ftp_firewall_type 1
USER user@remote.host[:port]
PASS pass
Complications:
'@' symbol in username?
Avoidable if the USER argument is parsed from end-to-beginning
What if no '@' symbol appears in USER argument?
What if single '@' symbol appears in USER argument, as part of actual
username?
What if USER is not first command? I.e. what if we see FEAT, SYST,
etc?
What if USER specifies a host that returns 5xx in the banner? Should
the client be allowed another USER? If so, does that count against
MaxLoginAttempts?
IPv6 dest server?
Support "USER user@[::1]:port". Easy enough.
FTPS?
If USER command is received over SSL/TLS, then connection to dest
server will use SSL/TLS. Configurable?
Connect, then send FEAT. If "AUTH SSL" or "AUTH TLS" appears in
FEAT, AND mod_proxy+mod_tls is available, then automatically try
to do FTPS. No "implicit" FTPS support?
Connection to dest server opened later
Benefits:
use mod_proxy in forward mode to add "IPv6" capability, i.e. talk to IPv6
server, for IPv4-only client.
use mod_proxy in forward mode to add SSL/TLS capability, i.e. to talk to
FTPS server, for non-SSL/TLS clients?
Supporting this means not connecting to the real server until pre-USER time;
mod_proxy needs to be modified to deal with this case. It's work needed
for USER-based backend selection in the reverse proxy case, anyway.
Client configuration:
proxyuser,user@host
FZ:
https://www3.trustwave.com/support/kb/article.aspx?id=13497
See WinSCP
this is what lftp
calls "user" for its ftp:proxy-auth-type
setting.
proxyuser@host,user
Chrome: uses system proxy settings (on Mac, at least?)
FF:
http://www.wikihow.com/Enter-Proxy-Settings-in-Firefox
FZ:
https://www3.trustwave.com/support/kb/article.aspx?id=13497
See WinSCP
this is what lftp
calls "proxy-user@host" for its ftp:proxy-auth-type
setting.
Future methods:
Net::Config:
ftp_firewall_type 2
USER proxy-user
PASS proxy-pass
USER user@remote.host[:port]
PASS pass
ftp_firewall_type 6
USER proxy-user@remote.host[:port]
PASS proxy-pass
USER user
PASS pass
See also this, which has *9* methods:
http://www.mcknight.de/jftpgw/config.html#loginstyle
Limiting the proxied hosts:
ProxyBlock:
http://httpd.apache.org/docs/2.4/mod/mod_proxy.html#proxyblock
proftpd-mod_proxy-0.9.5/doc/NOTES.health-checks 0000664 0000000 0000000 00000024051 14757370167 0021326 0 ustar 00root root 0000000 0000000
Health check (more properly "application server state" checks with applied
interpretation/policy) metrics:
TCP connect to port
conect timeout
number of retries
retry interval
FTP connect to port
require 220 response code, or any? (Keep in mind UseProxyProtocol option)
require specific response string?
connect timeout
number of retries
retry interval
FTP USER
require prompt for PASS, or any?
require specific response string?
timeout
number of retries
retry interval
FTP login
require succesful login (usually via anonymous login)
require specific response string?
timeout
number of retries
retry interval
TLS handshake
SSH key exchange
SSH login
SFTP
All of the metrics for a given health check strategy/type are summed up.
But is it a simple sum (all metrics are equal), or do some metrics have
more weight? If weighted, how are weights calculated?
Examples:
https://www.varnish-cache.org/trac/wiki/BackendPolling
http://www.taiter.com/techlog/2012/09/ftp-load-balanced-through-haproxy.html
Notes:
Notion of "quarantined", where the server is no longer considered "healthy",
BUT there should continue to be periodic checks on its state to see if
it has recovered.
Consider how something like roundRobin or leastConns would work for an
"FTP accelerator" type of client, which might open several concurrent
sessions? Will it matter if those connections go to different servers?
"RoundRobin is suitable where all available servers are assumed to be
largely similar in functionality. Weighed/ratio'd RoundRobin builds
weights into the system to deal with heterogenous capacity of the
servers. (This is harder, since the "weight" factor is admin-assigned,
and how exactly is it computed? 2x CPU? 4x memory? 6x network?)
Implementation
Initially, we will want a simple implementation. Passive health checks,
not active. Observe errors on existing traffic, mark unhealthy backends and
skip them, for some period of time. This will only apply to reverse proxying,
not forward proxying.
What types of errors should we watch for? How many errors before a backend
is unhealthy? How long to skip over unhealthy backends? What happens when
all backends are unhealthy?
Types of errors:
DNS resolution errors
TCP connect errors
TLS handshake errors
FTP connect/login errors (ignoring bad credentials!)
SSH banner error (e.g. ill-formed SSH banner/version)
Specifically, we want to track errors that indicate that that server is
unavailable for service. Thus probably NOT FTP data transfer errors.
Configuration:
use passive health tracking: yes/no
number of failures before unhealthy ("down") [default: 2-3]
number of successes before healthy ("up") [default: 1]
success implies active probes/health checks; this is NOT that
unhealthy timeout (before unhealthy status expires) [default: 10s]
ProxyReverseHealthPolicy
none/off
PassiveChecks
failures N (2)
expires time (30s)
ProxyReverseHealthPolicy PassiveChecks failures 2 expires 30s
ActiveChecks (not implemented)
Retries:
depth-first (retry same target multiple times _first_), or
breadth-first (retry next target _first_, cycling through list until max retries count reached)
NOTE: We only want to call _index_used() if we WANT to move to the next
backend. If we want to retry THIS backend (due to
transient/ignorable/non-fatal error), then we explicitly do NOT call
_index_used. Subtle. Need to capture this in comments for my future self.
int (*policy_used_backend)(pool *p, void *dsh, int policy_id,
unsigned int vhost_id, int backend_id);
int (*policy_update_backend)(pool *p, void *dsh, int policy_id,
unsigned int vhost_id, int backend_id, int conn_incr, long connect_ms);
Maybe we need now:
int (*policy_unhealthy_backend)(pool *p, void *dsh, int health_policy_id,
unsigned int vhost_id, int backend_id, int unhealthy_incr, long unhealthy_ms, const char *unhealthy_reason)
Unhealthy Errors
DNS
Maybe log message should include hint for "Trace dns:20" for more
info
gai_strerror:
EAI_AGAIN (ignore)
EAI_FAIL
EAI_SYSTEM (see errno)
EAI_NONAME
mapped to ENOENT
EAI_FAMILY
mapped to EAFNOSUPPORT
h_errno:
HOST_NOT_FOUND
TRY_AGAIN (ignore)
NO_RECOVERY
NO_DATA
TCP
EADDRINUSE (local error, ignore?)
EADDRNOTAVAIL (local error, ignore?)
ENETDOWN
ENETUNREACH
EHOSTUNREACH
ENETRESET (ignore?)
ECONNABORTED
ECONNRESET
ECONNREFUSED
ETIMEDOUT
TLS
any ignorable?
Maybe log message should include hint for "Trace tls:20" for more
info
FTP
non-220 greeting
banner_ok = FALSE (non-2xx) in reverse_try_connect()
Maybe log message should include hint for "Trace proxy.response:20"
for more info
SSH
illegal SSH version/banner
bad_proto = TRUE in lib/proxy/ssh.c#ssh_get_server_version
Maybe log message should include hint for
"Trace proxy.ssh2:20 ssh2:20" for more info
note that lib/proxy/ssh.c will NOT have the db index as seen
in lib/proxy/reverse.c; may need a way to get it. That said,
lib/proxy/ssh.c#ssh_ssh2_auth_completed_ev() DOES call
proxy_reverse_connect() and thus all of the above. So the lack
of treatment of illegal SSH banner as "unhealthy" is the result;
I think that's OK for now. We can handle this case as unhealthy
in a later pass.
Remember that _index_used is what advances the index, for _next_backend.
lib/proxy/reverse/db.c schema does NOT currently have columns for
unhealthy status; would need to bump schema version!
unhealthy_count INT
unhealthy_ms BIGINT
unhealthy_reason TEXT
need reverse_connect_index_unhealthy() function for recording "down"
backends, it increments unheathy_count, updates _ms, records last
_reason (e.g. "dns: host unknown", "tcp: connection refused")
and in _next per-policy db functions, need to see if selected backend
is unhealthy, or not. If unhealthy, LOG IT. If down status expired,
log expiry (and clear unhealthy columns) and use selected entry.
Otherwise, select NEXT backend.
This is where the health policy failure count is honored/implemented,
by examining unhealthy_count value for exceeding threshold.
If all backend addresses discovered are marked as "unavailable", should
mod_proxy try connecting to one of them anyway?
These _next per-policy db functions thus need to handle this scenario,
where all are unhealthy, none expired; watch for "wrapping" around the
list of targets! This, in turn, means that callers of _next need
to handle NULL/ENOENT returns.
Some policies, like Shuffle or Random, may require a "read-then-write"
approach. Hmm. These are currently implemented as
"get index into list of conns", and idx is a single value, not any
metadata associated with that backend/idx (such as unhealthy fields).
Maybe, in policy_next_backend, it should be:
get index per policy
get backend metadata for that index
exposing/adding a policy_get_backend feels like surfacing this
unnecessarily; it should be an impl detail of policy_next_backend.
However, if implemented at the datastore layer, then the datastore
layer needs accessors to the configured health policy parameters.
I guess this is where having an include/proxy/reverse/health.h API
could come in handy.
int proxy_reverse_health_sess_init(pool *p);
does lookup of ProxyReverseHealthPolicy directive
called by proxy_reverse_sess_init
int proxy_reverse_health_is_healthy(unsigned int unhealthy_count, unsigned long unhealthy_ms, const char *unhealthy_reason);
This way, the Health API knows which policy is configured, knows
its parameters. It logs the fields, handles expiration, etc.
If the result is TRUE, then we clear any unhealthy fields.
check backend metadata for health
if unhealthy
mark index as used
goto top
TRACK starting index, to catch looping around to same index value
again due to ALL backends being unhealthy.
Consider also the case where a configured backend server is a DNS name/URL,
which resolves to multiple IP addresses/ports. These resolved addresses/ports
are not currently tracked in the SQLite database -- thus we currently have
no state for them persisted/shareable outside of that session process. If
we did persist these addresses in the db, how to clean them up? Consider
dynamic backends whose IPs change a lot over time; the cruft would build up
in the db. Hmm.
https://www.haproxy.com/blog/using-haproxy-as-an-api-gateway-part-3-health-checks/
https://docs.nginx.com/nginx/admin-guide/load-balancer/http-health-check/#passive-health-checks
> Note that if there is only a single server in a group, the fail_timeout
> and max_fails parameters are ignored and the server is never marked
> unavailable.
https://docs.nginx.com/nginx/admin-guide/load-balancer/tcp-health-check/#passive-tcp-health-checks
> If several health checks are configured for an upstream group, the failure
> of any check is enough to consider the corresponding server unhealthy.
https://docs.nginx.com/nginx/admin-guide/load-balancer/tcp-health-check/#fine-tuning-tcp-health-checks
Defaults: interval=5s, passes=1, fails=1
Tests:
No regressions (default health policy: none)
health policy: passive checks
DNS errors
TCP errors
TLS errors
FTP errors
count exceeded, not exceeded
expired, not expired
all backends unhealthy for a connect policy + vhost
proftpd-mod_proxy-0.9.5/doc/NOTES.history 0000664 0000000 0000000 00000001067 14757370167 0020326 0 ustar 00root root 0000000 0000000
Why does proftpd have the netio readers for buffering up reads? Seems
overengineered/too complex.
Answer: See issues like this:
http://www.greatcircle.com/firewalls/mhonarc/firewalls.199710/msg00056.html
Where does the formatting for using "USER name@host" for forward proxying
coming from? Or using two USER/PASS commands for doing forward proxying
with proxy auth?
Answer: From TIS' FWTK:
http://www.fwtk.org/fwtk/docs/user_guide.pdf
http://www.fwtk.org/fwtk/docs/manpages.pdf
See the cited example FTP sessions for both types of logins.
proftpd-mod_proxy-0.9.5/doc/NOTES.load-balancing 0000664 0000000 0000000 00000007631 14757370167 0021463 0 ustar 00root root 0000000 0000000
ProxyReverseConnectPolicy Random RoundRobin LeastConns PerUser PerHost
on startup event, iterate through configured backend servers, mark which
ones cannot be connected to as "dead".
Means backend server list management, with attributes:
server:
connCount
connectTime
lastChecked
Or, better: TWO lists: live list, dead list
For tracking connectTime (time to connect, in millisecs):
long timevaldiff(struct timeval *starttime, struct timeval *finishtime) {
long msec;
msec=(finishtime->tv_sec-starttime->tv_sec)*1000;
msec+=(finishtime->tv_usec-starttime->tv_usec)/1000;
return msec;
}
struct timeval start, finish;
long msec;
gettimeofday(&start, NULL);
sleep(1);
gettimeofday(&finish, NULL);
msec = timevaldiff(&start, &finish);
printf("Elapsed time for sleep(1) is: %d milliseconds.\n", msec);
Some platforms have a timersub(3) macro for this, but it'll be more
portable to do our own.
Advanced balancing (selection at USER time):
user-specific (lookup per user)
Randomly select N backend servers for user if not already assigned?
host-specific (lookup per HOST)
Randomly select N backend servers for host if not already assigned?
For stickiness, we have two cases: one where the backend(s) are administratively
assigned, and one where they are randomly chosen/assigned. Assume the former
is the more common case.
Could do:
ConnectPolicy PerUser/PerHost
Servers ...
with no specific assignments. In this case, the username/client IP
is hashed into an index of one of the servers, and kept that way. Let's
start with that as the first iteration.
Next:
ConnectPolicy PerUser/PerHost
ReverseServers ...
FAQ: Why no PerClass stickiness?
A: It's the same as PerHost, with the added use of +ReverseServers
FAQ: Why no per-SSL session/ticket/SNI stickiness?
A: It's too late; TLS is hop-by-hop, and thus the SSL session on the frontend,
with the client, has no relation to the SSL session on the backend. Thus
any routing based on SSL session ID to a backend, based on the client's
session ID, is too late -- and that client session is for the proxy anyway.
We should to load balancing based on TLS protocol version, weak ciphers, etc,
but that would be to a pool of backend servers, NOT "sticky".
Complex/adaptive balancing:
Client IP address/class (connect time selection)
FTP USER (i.e. user affinity to specific server(s)) (USER time selection)
lookup AND hashed (algo? Same algo used by Table API?)
Backend response time (connect time selection)
Balancing Versus Stickiness
For reverse proxy configurations, there is a choice between "balancing"
strategies and "sticky" strategies when it comes to selecting the backend
server that will handle the incoming connection.
Which should you use, and why?
All of the "balancing" strategies are able to select the backend server *at
connect time*. Most of the "sticky" strategies, on the other hand, require
more knowledge about the user (e.g. USER name, HOST name, SSL session ID),
thus backend server selection is delayed until that information is obtained.
Balancing is best when all of your backend severs are identical with regard to
the content they have, AND when it doesn't matter which server handles a
particular client. Maybe all of your backend servers use a shared filesystem
via NFS or similar, thus directory listings will be the same for a user no
matter which backend server is used, and uploading files to one server means
that those files can be downloaded/seen by the other servers.
Stickiness is best when your backend servers are NOT identical, and some
users/clients should only ever go to some particular set of backend servers.
Thus the user/client needs to be "sticky" to a given backend server(s) --
that's when you need the sticky selection strategies.
proftpd-mod_proxy-0.9.5/doc/NOTES.protocol-translation 0000664 0000000 0000000 00000010273 14757370167 0023021 0 ustar 00root root 0000000 0000000
FTP to SFTP (#1):
https://www.bitvise.com/ftp-bridge
https://enterprisedt.com/products/completeftp/doc/guide/html/gateway.html
mindterm source (Java, FTPOverSFTP bridge code for mapping)
For each FTP command, decompose it into SFTP request equivalents; note which
FTP commands have no SFTP request equivalents.
USER
PASS
USERAUTH
ACCT
n/a
CWD
XCWD
CDUP
XCUP
n/a; will mod_proxy have to maintain some state about the FTP session
for such directory traversal commands? Yuck. Maybe use with REALPATH?
SMNT
not implemented
REIN
not implemented
QUIT
CHANNEL_CLOSE
PORT
EPRT
PASV
EPSV
this will be the most interesting; translating SFTP data transfers into
frontend FTP transfers.
TYPE
n/a (binary only?)
STRU
n/a; always F
MODE
n/a; always S
RANG
REST
RETR
OPEN + READ + CLOSE
RANG
REST
APPE
STOR
OPEN + WRITE + CLOSE
STOU
OPEN + WRITE + CLOSE; have mod_proxy generate the unique name for the
backend SFTP file? Use O_CREAT|O_EXCL to force uniqueness, I guess...
ALLO
n/a
RNFR
RNTO
RENAME
ABOR
n/a; maybe just stop the current data transfer, send CLOSE?
DELE
REMOVE
MDTM
STAT
MKD
XMKD
MKDIR
RMD
XRMD
RMDIR
LIST
MLSD
MLST
NLST
OPENDIR + READDIR + CLOSE
MFF
FSETSTAT
MFMT
FSETSTAT
PWD
XPWD
SITE
n/a; support for specific SITE commands *may* be added later, e.g.
SITE CHMOD = SETSTAT
SITE CHGRP = SETSTAT
SITE SYMLINK = SYMLINK (from mod_site_misc)
SITE UTIME = SETSTAT (from mod_site_misc)
SIZE
STAT
SYST
n/a; always "215 UNIX Type: L8"
STAT
STAT
HELP
n/a?
NOOP
no backend equivalent; handle in proxy
FEAT
n/a (SFTP extensions?)
OPTS
LANG
n/a
HOST
n/a
CLNT
n/a (would be part of SSH connect, but is too late in FTP protocol)
AUTH
PBSZ
PROT
n/a (provided by SSH by default!)
For authentication, it will always be password authentication to the backend
SSH server. (Or should this overridable, e.g. password authentication to
the proxy, but hostbased authentication from the proxy to the backend server?)
What does this look like, for an FTP forward proxy configuration to an SFTP
backend? How would mod_proxy know that the destination server is an SFTP
server? I suppose it could do a probe: make the initial TCP connection,
see whether it gets the "220 Server Ready" FTP response, or the "ssh-..."
SSH banner...
Note: This would require that mod_proxy be built _without_ mod_sftp being
present! This means that the logic regarding mod_sftp HOOKs would need
to be revisited.
Implementation:
Implement a "protocol", which handles all of the above FTP commands.
The default Protocol object will do what mod_proxy currently does for
all of the commands; this will thus be a transparent change. These
Protocol objects would then have to maintain/accumulate their own state,
so as to implement/translate RNFR + RNTO = RENAME, *and* be responsible
for translating the responses. Thus these would indeed be more than
just codecs (or, for some value of "codec", very complicated codecs).
Once that's done, we need to determine how to lookup a new Protocol object,
and when do it, and when to register it. For cases where mod_proxy
knows the backend URL at connect time, this is easier. What about for
the auth-time (PerUser, PerGroup) URLs?
FTP Implementation API:
Suitable for plugging into mod_proxy's CMD C_ANY handler, *and*
its POST_CMD C_PROT handler. Thus the API for the given impl
input should be something like:
MODRET (handle_cmd)(pool *p, cmd_rec *cmd, int cmd_phase);
Consider, for example, a logging-only Implementation object, to
demonstrate the concept?
Note that this Impl API works for FTP, but NOT for SSH; SSH packets
are not handled by the C_ANY handler.
SFTP to FTP (#2):
No forward proxying supported here, since SFTP doesn't have that notion;
mod_proxy will know (via the ProxyReversServers URL schemes) which protocol
to use for the backend server.
SCP (#3?):
subset of SFTP to FTP, with no directory listing support.
proftpd-mod_proxy-0.9.5/doc/NOTES.proxy-datatransfer-policy 0000664 0000000 0000000 00000005662 14757370167 0023764 0 ustar 00root root 0000000 0000000
Client:
C -> P: PORT 1,2,3,4,5,6 P -> S: PORT 9,8,7,6,5,4
C <- P: 200 OK P <- S: 200 OK
C -> P: PASV P -> S: PASV
C <- P: 227 (1,2,3,4,5,6) P <- S: 227 (9,8,7,6,5,4)
Order of operations:
C -> P: PORT
Parse addr/port
Check RFC1918 addr
Check AllowForeignAddress
Check high-numbered port
Open local listening conn
Format local listening addr for PORT command
Send new PORT command to S
Receive 200 OK response from S
Send 200 OK response to C
Set frontend_sess_flags = SF_PORT
Set backend_sess_flags = SF_PORT
C -> P: PASV
Send PASV command to S
Receive 227 response from S
Parse addr/port
Check addr against remote addr
Check high-numbered port
Open local listening conn
Format local listening addr for PASV response
Send new PASV response to C
Set frontend_sess_flags = SF_PASSIVE
Set backend_sess_flags = SF_PASSIVE
Active:
C -> P: PORT 1,2,3,4,5,6 P -> S: PORT 9,8,7,6,5,4
C <- P: 200 OK P <- S: 200 OK
C -> P: PASV P -> S: PORT 9,8,7,6,5,4
C <- P: 227 (1,2,3,4,5,6) P <- S: 200 OK
Order of operations:
C -> P: PORT
Parse addr/port
Check RFC1918 addr
Check AllowForeignAddress
Check high-numbered port
Open local listening conn
Format local listening addr for PORT command
Send new PORT command to S
Receive 200 OK response from S
Send 200 OK response to C
Set frontend_sess_flags = SF_PORT
Set backend_sess_flags = SF_PORT
C -> P: PASV
Open local listening conn
Format local listening addr for PORT command
Send new PORT command to S
Receive 200 OK response from S
Open local listening conn
Format local listening addr for PASV response
Send new PASV response to C
Set frontend_sess_flags = SF_PASSIVE
Set backend_sess_flags = SF_PORT
Passive:
C -> P: PORT 1,2,3,4,5,6 P -> S: PASV
C <- P: 200 OK P <- S: 227 (9,8,7,6,5,4)
C -> P: PASV P -> S: PASV
C <- P: 227 (1,2,3,4,5,6) P <- S: 227 (9,8,7,6,5,4)
Order of operations:
C -> P: PORT
Parse addr/port
Check RFC1918 addr
Check AllowForeignAddress
Check high-numbered port
Send new PASV command to S
Receive 227 responses from S
Parse addr/port
Check addr against remote addr
Check high-numbered port
Send 200 OK response to C
Set frontend_sess_flags = SF_PORT
Set backend_sess_flags = SF_PASSIVE
C -> P: PASV
Send PASV command to S
Receive 227 response from S
Parse addr/port
Check addr against remote addr
Check high-numbered port
Open local listening conn
Format local listening addr for PASV response
Send new PASV response to C
Set frontend_sess_flags = SF_PASSIVE
Set backend_sess_flags = SF_PASSIVE
proftpd-mod_proxy-0.9.5/doc/NOTES.reverse-proxy 0000664 0000000 0000000 00000007435 14757370167 0021464 0 ustar 00root root 0000000 0000000 Selection Strategies: Balancing vs Stickiness
Balancing strategies:
leastConns
random
roundRobin
shuffle
Sticky strategies:
client IP
USER
HOST
What's better for FTP, SFTP connections? RoundRobin or LeastConns? And
why? The HAproxy docs for the 'balance' keyword say that 'leastconn' is
best for long-lived connections:
leastconn The server with the lowest number of connections receives the
connection. Round-robin is performed within groups of servers
of the same load to ensure that all servers will be used. Use
of this algorithm is recommended where very long sessions are
expected, such as LDAP, SQL, TSE, etc... but is not very well
suited for protocols using short sessions such as HTTP. This
algorithm is dynamic, which means that server weights may be
adjusted on the fly for slow starts for instance.
(from http://haproxy.1wt.eu/download/1.4/doc/configuration.txt)
Is this true? Corroboration or statistics showing the relationship?
Dangers of LeastConns
Consider that you are bringing up a new server, or restarting a server.
Being new, it will have no connections -- and the load balancer sees
this. Thus in a short period of time, ALL new connections may end up
slamming that server. If the server can handle this type of workload,
then you are good. If, on the other hand, the server performs warm-up
tasks (e.g. filling caches, performing discovery of necessary downstream
resources, etc), then this is quite undesirable.
This is why HAproxy has a "warm up" period.
Links/References
http://www.timgalyean.com/2011/03/load-balancing-with-haproxy-and-apache/
http://blog.loadbalancer.org/load-balancing-windows-terminal-server-haproxy-and-rdp-cookies/
http://blogs.citrix.com/2010/09/02/load-balancing-least-connections/
http://www.brocade.com/support/Product_Manuals/ServerIron_SLBGuide/health.4.19.html#49842
https://kb.wisc.edu/ns/page.php?id=13201
http://serverfault.com/questions/457506/ha-proxy-roundrobin-vs-leastconn
http://blog.exceliance.fr/2011/08/03/layer-7-load-balancing-proxy-mode/
https://devcentral.f5.com/articles/intro-to-load-balancing-for-developers-ndash-the-algorithms#.UsPAPY3TNe4
Nice description of methods, and quite a variety of them.
https://devcentral.f5.com/articles/intro-to-load-balancing-for-developers-ndash-how-they-work#.UsT6cI3TNe4
Reuse the JPG/pic there?
https://devcentral.f5.com/articles/intro-load-balancing-for-developers-ndash-the-architect-rsquos-view#.UsT6dI3TNe4
https://devcentral.f5.com/articles/advanced-load-balancing-for-developers-ndash-adcs-what-rsquos-the-difference#.UsT6XY3TNe4
The 'Programmability' bit is a good point. mod_proxy might need some
ftpdctl actions implemented for altering things on-the-fly.
https://devcentral.f5.com/articles/advanced-load-balancers-for-developers-adcs-the-code#.UsT6Y43TNe4
http://www.onjava.com/pub/a/onjava/2001/09/26/load.html
Nice discussion of DNS round robin
http://www.javaworld.com/article/2077921/architecture-scalability/server-load-balancing-architectures--part-1--transport-level-load-balancing.html
http://www.javaworld.com/article/2077922/architecture-scalability/server-load-balancing-architectures--part-2--application-level-load-balanci.html
http://www.infoq.com/articles/scalability-principles
http://www.infoq.com/articles/ebay-scalability-best-practices
Note Best Practice #4, and how that pertains to Black Pearl (think
intra-service messaging/notifications)
Aside: Software Architect Role
http://www.codingthearchitecture.com/2008/02/11/the_key_difference_between_developer_and_architect_roles.html
http://www.infoq.com/presentations/Role-of-the-Architect
Need to watch this!
proftpd-mod_proxy-0.9.5/doc/NOTES.selection 0000664 0000000 0000000 00000001310 14757370167 0020601 0 ustar 00root root 0000000 0000000
Balancing vs Stickiness
Balancing:
random
shuffle
roundRobin
leastConns
Sticky:
per USER
per HOST
Work on implementing just the balancing strategies, for now.
Note: The state files used for the balancing strategies, especially once
health checks are added, could be externally cached/managed, e.g. via
memcache/redis, using JSON.
Issues there: if each mod_proxy is doing its own health checks of the
servers, it's possible for individual mod_proxy instances to disagree
about the health of a given backend (think split-brain syndrome).
When each mod_proxy instance is managing its own view, this is OK;
when they all share that persisted view, it could be a problem.
proftpd-mod_proxy-0.9.5/doc/NOTES.selection-roundrobin 0000664 0000000 0000000 00000015643 14757370167 0022776 0 ustar 00root root 0000000 0000000
Issues for ProxyBackendSelection 'roundRobin':
1. Selection strategy is per-vhost.
2. Selection lists are per-vhost.
This means that a 'core.fork' event, before the vhost is known,
would not work easily. We don't just want to increment some
counter/index for all vhosts, as that would not actually be
the expected "round robin" behavior.
To implement round robin, we need:
1. An ordered list of backend servers, whose order is preferably stable.
How would healthchecks, with servers moving into and out of the "live"
list, affect this?
What happens if the "previous selection" or "next selection" (whichever
we persist) is not available in the "live" list for the next connection?
2. Knowledge of what the "next" server should be (or, conversely,
what the previous server was)
3. Persistence of #2 in some storage accessible across connections to
that vhost (i.e. vhost-specific storage).
Possibilities: SysV shm, file, SQL, memcache, external process.
What about an mmap'd file? Still needs locking (with retries?)
Any of these would require a postparse (startup?) event listener, to see
if any vhost has RoundRobin selection configured, to create/prep the
shared storage area.
What if there are THREE backend server lists:
configured: conf/
live live/
dead dead/
The "configured" list would be static, wouldn't change, would have stable
ordering. That could then be the reference server/index for round robin.
proxy_select_backends:
vhost_sid INTEGER,
name TEXT, // e.g. "server1.example.com"
backend_id INTEGER, // used for ordering
healthy BOOLEAN // used for healthchecks
nconns INTEGER // used for least conns
SELECT name FROM proxy_backends WHERE vhost_sid = {...} ORDER BY position ASC;
proxy_select_roundrobin:
vhost_sid INTEGER,
current_backend_id INTEGER (FK into proxy_select_backends.backend_id?)
proxy_select_shuffle:
insert rows for used backend IDs; delete them all on _reset()
(OR insert rows for UNUSED backend IDs; delete as used)
a selection policy object, with callbacks into the database
Basic data structure:
vhost1
index (into 'configured' list) of current/selected backend server
max index value (i.e. length of 'configured' list minus 1)
...
vhostN
Could use SID to identify vhost.
unsigned int idx;
int proxy_roundrobin_get_index(main_server->sid, &idx);
/* Get backend for index */
idx++;
if (idx == max_idx) {
idx = 0;
}
int proxy_roundrobin_set_index(main_server->sid, idx);
OR:
unsigned int idx;
int proxy_roundrobin_incr_index(main_server->sid, &idx);
This would "atomically" return the current index, and
increment (with wraparound) the index for the next call.
Callers, then, don't need to know about the max_idx.
With this arrangement, an on-disk mmap'd file would have range
locking, and a "row" would be:
uint32_t sid
uint32_t backend_server_count
uint32_t backend_server_idx
Alternatively, the entire selection database could be a single JSON file;
the "locking" would be done on the basis of the entire file.
OR, alternatively, the selection database could be a SQL database (e.g.
SQLite), a la mod_auth_otp.
Store these databases in a policy-specific, vhost-specific file (to reduce
contention)? This would make it easy for each policy to have its own
format, as needed.
CONF_ROOT|CONF_VIRTUAL, NO .
Leaning toward SQLite.
SELECT ...
INSERT sid, ...
UPDATE ...
Per Policy! Hrm. Maybe have mod_proxy create its own tables, etc.
(just configure a path). That'd work nicely. On startup, delete
table if it exists (warn about this!), create needed schema. Much
less fuss for the admin.
Make lib/db/sqlite.c file, use sqlite3 directly (NOT via
mod_sql+mod_sql_sqlite).
This would be the "select.dat" file/table, the basis for backend
selection.
For health checks:
uint32_t sid;
uint32_t backend_server_count
uint32_t live_servers[backend_server_count]
uint32_t dead_servers[backend_server_count]
...
Note: Use big-endian values for all numeric values on disk, as if writing
to the network, for file "reuse" on other servers?
Note: Would be nice, given the above format, to NOT have to scan the
entire file to find the vhost in question, given the variable length
of the server lists per vhost.
Could deal with that by having a header, which would be the offset of
that SID into the file. I.e.:
off_t sid_offs[vhost_count];
sid 1: off = 0
sid 2: off = 42
and remember that vhost SIDs start at 1!
off_t sid_off = sid_offs[main_server->sid-1];
lseek(fd, sid_off, SEEK_SET);
readv(...)
readv(...)
Note: have a version uint32_t as first value in value, for indicating file
format version.
Note: have ftpdctl proxy action for dumping out (as JSON) the contents of
the select.dat file?
Note: files/structure:
select.c
select-random.c
select-roundrobin.c
select-shuffle.c
...
policy/
random/
{sid}.json
roundrobin/
{sid}.json
shuffle/
{sid}.json
When writing out the file initially, scan each vhost, figure out its
position, then write out header, then vhost entry.
And, to support the leastConns, equalConns, and leastResponseTime policies,
we'll need a separate table (also mmap'd):
unsigned int idx
unsigned long curr_conns
unsigned long curr_response_ms
OR, even better, to handle the leastConns/leastResponseTime etc strategies,
use a fourth list:
configured
ranked
live
dead
Where "ranked" is a list of indices (into the configured list) in
preference/ranked order. In the case of least conns, this ranking is done
by number of current connections. Hmm, no, we'd still need to know that
number of current connections somewhere, not just the relative rank of
that server versus others -- consider that the number of connections may
change, changing the relative ranking, and we'd need to know the number of
current connections in order to do the re-ranking.
To make this more general (e.g. for other selection policies), maybe:
int proxy_reverse_select_index_next(main_server->sid, &idx, policy);
Where policy could be:
POLICY_RANDOM
POLICY_ROUND_ROBIN
POLICY_LEAST_CONNS
POLICY_EQUAL_CONNS
POLICY_LOWEST_RESPONSE_TIME
If that backend is chosen/used successfully, then:
int policy_reverse_select_index_used(main_server->sid, idx, response_ms);
Note when we would need to maintain an fd on the database until the
session ended (e.g. for leastConns), so that we could decrement the number
of connections to a given SID when that connection ended. This is not
necessary for roundRobin, which doesn't care about number of current
connections per sid, only next SID to index. LeastConns, on the other hand,
DOES care about number of current connections.
proftpd-mod_proxy-0.9.5/doc/NOTES.tests 0000664 0000000 0000000 00000000455 14757370167 0017767 0 ustar 00root root 0000000 0000000
Note that to run the API tests for mod_proxy, you must:
$ ./configure --enable-tests ...
Then:
$ cd contrib/mod_proxy/t/
$ make api-tests
Available external FTP sites for testing:
ftp.microsoft.com
ftp.cisco.com*
ftp.kernel.org
ftp.freebsd.org
ftp.seagate.com*
(*) denotes FTPS
proftpd-mod_proxy-0.9.5/doc/NOTES.tls 0000664 0000000 0000000 00000000105 14757370167 0017417 0 ustar 00root root 0000000 0000000
ProxyTLSOptions
IgnoreFEAT
UseCCC
Passphrase provider support?
proftpd-mod_proxy-0.9.5/doc/NOTES.todo 0000664 0000000 0000000 00000000220 14757370167 0017560 0 ustar 00root root 0000000 0000000
default low (1 sec) ConnectTimeout; consider many slow backends
MODE Z proxying (mod_deflate)
setjmp/longjmp for -X command-line option
proftpd-mod_proxy-0.9.5/doc/REFERENCES 0000664 0000000 0000000 00000006526 14757370167 0017364 0 ustar 00root root 0000000 0000000
Stateful Inspection Advantage (Checkpoint):
http://www.checkpoint.com/smb/help/z100g/7.5/7082.htm
Linux Load Balancing with HAProxy + Heartbeat:
https://wiki.gogrid.com/wiki/index.php/Customer:Linux_Load_Balancing_with_HAProxy+Heartbeat
Load Balancing OpenSSH SFTP with HAProxy:
http://jpmorris-iso.blogspot.com/2013/01/load-balancing-openssh-sftp-with-haproxy.html
HAproxy PROXY protocol
http://haproxy.1wt.eu/download/1.5/doc/proxy-protocol.txt
FTP Load Balancing?
http://www.formilux.org/archives/haproxy/0805/0976.html
http://www.formilux.org/archives/haproxy/0805/0977.html
Best way to use HAProxy for FTP?
http://permalink.gmane.org/gmane.comp.web.haproxy/2486
Load Balancing FTP:
http://ben.timby.com/?page_id=210
FTP Load-balanced through haproxy:
http://www.taiter.com/techlog/2012/09/ftp-load-balanced-through-haproxy.html
Network Load Balancing Services (MSFT):
http://en.wikipedia.org/wiki/Network_Load_Balancing_Services
Network Load Balancing:
http://en.wikipedia.org/wiki/Network_Load_Balancing
NcFTP.com "Why PASV Poses Problems for FTP Servers behind Load-Balancing Routers"
http://www.ncftp.com/ncftpd/doc/misc/ftp_and_firewalls.html#PASVLBProblems
ftpcluster:
http://www.awk-scripting.de/cluster/
Brane Dump: Load Balancing FTP servers
http://hezmatt.org/~mpalmer/blog/2008/10/22/load-balancing-ftp-servers.html
Opensource load balancer projects:
http://www.inlab.de/articles/free-and-open-source-load-balancing-software-and-projects.html
http://sourceforge.net/projects/balance/
FTP ALG:
http://www.amaranten.com/support/user%20guide/Application_Layer_Gateways/FTP_ALG.htm
http://www.ietf.org/id/draft-tsou-behave-ftp46-00.txt
Breaking through FTP ALGs: possible?
http://seclists.org/bugtraq/2000/Feb/176
http://seclists.org/vuln-dev/2000/Feb/82
https://listserv.icsalabs.com/pipermail/firewall-wizards/2000-March/008251.html
AWS ELB for FTP:
https://forums.aws.amazon.com/thread.jspa?messageID=139143
Trilent FTP Proxy:
http://trilent-ftp-proxy.en.softonic.com/
ServerFault:
Reverse Proxy FTP traffic:
http://serverfault.com/questions/122636/reverse-proxy-ftp-traffic
Search on an FTP server:
http://serverfault.com/questions/28568/using-the-find-command-on-the-ftp-server?rq=1
FTP Load balancer:
http://serverfault.com/questions/139342/ftp-load-balancer?rq=1
Possible to load balance FTP?
http://serverfault.com/questions/113456/is-it-possible-to-load-balance-an-ftp-server
frox as reverse proxy?
http://serverfault.com/questions/390043/frox-as-reverse-proxy
proxy and reverse proxy on same server
http://serverfault.com/questions/65133/proxy-and-reverse-proxy-on-same-server?rq=1
FTP/SFTP/FTPS proxying
http://serverfault.com/questions/117899/ftp-sftp-ftps-proxying?rq=1
X-Forwarded-For
http://stackoverflow.com/questions/11891185/x-forward-for-over-ssl-using-load-balancers?rq=1
Reverse proxy capabilities:
http://www.rhinosoft.com/pressarchive/PressRelease2012-05-10.asp?prod=rs
Diagrams:
http://pic.dhe.ibm.com/infocenter/ssp/v3r4/index.jsp?topic=%2Fcom.ibm.help.sspftpreverseproxy.doc%2FSSP_FTS_FTPRProxCfg.html
http://pic.dhe.ibm.com/infocenter/ssp/v3r4/index.jsp?topic=%2Fcom.ibm.help.sspoverview.doc%2FSSP_Diag_FinishedFTPRev.html
http://pic.dhe.ibm.com/infocenter/ssp/v3r4/index.jsp?topic=%2Fcom.ibm.help.sspoverview.doc%2FSSP_Diag_FinishSFTPRev.html
proftpd-mod_proxy-0.9.5/include/ 0000775 0000000 0000000 00000000000 14757370167 0016665 5 ustar 00root root 0000000 0000000 proftpd-mod_proxy-0.9.5/include/proxy/ 0000775 0000000 0000000 00000000000 14757370167 0020046 5 ustar 00root root 0000000 0000000 proftpd-mod_proxy-0.9.5/include/proxy/conn.h 0000664 0000000 0000000 00000004726 14757370167 0021165 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy conn API
* Copyright (c) 2012-2020 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_CONN_H
#define MOD_PROXY_CONN_H
#include "mod_proxy.h"
#include "proxy/session.h"
struct proxy_conn;
void proxy_conn_clear_username(const struct proxy_conn *pconn);
void proxy_conn_clear_password(const struct proxy_conn *pconn);
int proxy_conn_connect_timeout_cb(CALLBACK_FRAME);
const struct proxy_conn *proxy_conn_create(pool *p, const char *uri,
unsigned int flags);
#define PROXY_CONN_CREATE_FL_USE_DNS_TTL 0x0001
const pr_netaddr_t *proxy_conn_get_addr(const struct proxy_conn *,
array_header **);
int proxy_conn_get_dns_ttl(const struct proxy_conn *pconn);
const char *proxy_conn_get_host(const struct proxy_conn *pconn);
const char *proxy_conn_get_hostport(const struct proxy_conn *pconn);
int proxy_conn_get_port(const struct proxy_conn *pconn);
conn_t *proxy_conn_get_server_conn(pool *p, struct proxy_session *proxy_sess,
const pr_netaddr_t *remote_addr);
const char *proxy_conn_get_uri(const struct proxy_conn *pconn);
const char *proxy_conn_get_username(const struct proxy_conn *pconn);
const char *proxy_conn_get_password(const struct proxy_conn *pconn);
int proxy_conn_get_tls(const struct proxy_conn *pconn);
int proxy_conn_use_dns_srv(const struct proxy_conn *pconn);
int proxy_conn_use_dns_txt(const struct proxy_conn *pconn);
int proxy_conn_send_proxy_v1(pool *p, conn_t *conn);
int proxy_conn_send_proxy_v2(pool *p, conn_t *conn);
void proxy_conn_free(const struct proxy_conn *pconn);
#endif /* MOD_PROXY_CONN_H */
proftpd-mod_proxy-0.9.5/include/proxy/db.h 0000664 0000000 0000000 00000006123 14757370167 0020606 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy database API
* Copyright (c) 2015-2021 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_DB_H
#define MOD_PROXY_DB_H
#include "mod_proxy.h"
struct proxy_dbh;
int proxy_db_init(pool *p);
int proxy_db_free(void);
/* Create/prepare the database (with the given schema name) at the given path */
struct proxy_dbh *proxy_db_open(pool *p, const char *table_path,
const char *schema_name);
/* Create/prepare the database (with the given schema name) at the given path.
* If the database/schema already exists, check that its schema version is
* greater than or equal to the given minimum version. If not, delete that
* database and create a new one.
*/
struct proxy_dbh *proxy_db_open_with_version(pool *p, const char *table_path,
const char *schema_name, unsigned int schema_version, int flags);
#define PROXY_DB_OPEN_FL_SCHEMA_VERSION_CHECK 0x001
#define PROXY_DB_OPEN_FL_ERROR_ON_SCHEMA_VERSION_SKEW 0x002
#define PROXY_DB_OPEN_FL_INTEGRITY_CHECK 0x004
#define PROXY_DB_OPEN_FL_VACUUM 0x008
#define PROXY_DB_OPEN_FL_SKIP_VACUUM 0x010
/* Close the database. */
int proxy_db_close(pool *p, struct proxy_dbh *dbh);
int proxy_db_prepare_stmt(pool *p, struct proxy_dbh *dbh, const char *stmt);
int proxy_db_finish_stmt(pool *p, struct proxy_dbh *dbh, const char *stmt);
int proxy_db_bind_stmt(pool *p, struct proxy_dbh *dbh, const char *stmt,
int idx, int type, void *data, int datalen);
#define PROXY_DB_BIND_TYPE_INT 1
#define PROXY_DB_BIND_TYPE_LONG 2
#define PROXY_DB_BIND_TYPE_TEXT 3
#define PROXY_DB_BIND_TYPE_BLOB 4
#define PROXY_DB_BIND_TYPE_NULL 5
/* Executes the given statement. Assumes that the caller is not using a SELECT,
* and/or is uninterested in the statement results.
*/
int proxy_db_exec_stmt(pool *p, struct proxy_dbh *dbh, const char *stmt,
const char **errstr);
/* Executes the given statement as a previously prepared statement. */
array_header *proxy_db_exec_prepared_stmt(pool *p, struct proxy_dbh *dbh,
const char *stmt, const char **errstr);
/* Rebuild the named index. */
int proxy_db_reindex(pool *p, struct proxy_dbh *dbh, const char *index_name,
const char **errstr);
#endif /* MOD_PROXY_DB_H */
proftpd-mod_proxy-0.9.5/include/proxy/dns.h 0000664 0000000 0000000 00000003526 14757370167 0021011 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy DNS API
* Copyright (c) 2020 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_DNS_H
#define MOD_PROXY_DNS_H
#include "mod_proxy.h"
typedef enum {
PROXY_DNS_UNKNOWN,
PROXY_DNS_A,
PROXY_DNS_AAAA,
PROXY_DNS_SRV,
PROXY_DNS_TXT
} proxy_dns_type_e;
/* Resolves a given `name` to a list of textual response lines, based on the
* given DNS record type.
*
* For A records, the responses will be IPv4 addresses.
* For AAAA records, the responses will be IPv6 addresses.
* For SRV records, the responses will be the returned address/ports, in
* priority order.
* For TXT records, the responses will be the returned textual lines.
*
* If a `ttl` pointer is provided, the shortest TTL on retrieved records
* retrieved is returned.
*/
int proxy_dns_resolve(pool *p, const char *name, proxy_dns_type_e dns_type,
array_header **resp, uint32_t *ttl);
#endif /* MOD_PROXY_DNS_H */
proftpd-mod_proxy-0.9.5/include/proxy/forward.h 0000664 0000000 0000000 00000004451 14757370167 0021667 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy forward-proxy API
* Copyright (c) 2012-2022 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_FORWARD_H
#define MOD_PROXY_FORWARD_H
#include "mod_proxy.h"
#include "proxy/session.h"
#define PROXY_FORWARD_ENABLED_NOTE "mod_proxy.forward-enabled"
int proxy_forward_init(pool *p, const char *tables_dir);
int proxy_forward_free(pool *p);
int proxy_forward_sess_init(pool *p, const char *tables_dir,
struct proxy_session *proxy_sess);
int proxy_forward_sess_free(pool *p, struct proxy_session *proxy_sess);
int proxy_forward_have_authenticated(cmd_rec *cmd);
int proxy_forward_handle_user(cmd_rec *cmd, struct proxy_session *proxy_sess,
int *successful, int *block_responses);
int proxy_forward_handle_pass(cmd_rec *cmd, struct proxy_session *proxy_sess,
int *successful, int *block_responses);
/* Forward proxy method API */
#define PROXY_FORWARD_METHOD_USER_WITH_PROXY_AUTH 1
#define PROXY_FORWARD_METHOD_USER_NO_PROXY_AUTH 2
#define PROXY_FORWARD_METHOD_PROXY_USER_WITH_PROXY_AUTH 3
#define PROXY_FORWARD_METHOD_USER_SNI_NO_PROXY_AUTH 4
/* Return the method ID for the given string, or -1 if the given method
* is not recognized/supported.
*/
int proxy_forward_get_method(const char *);
/* Returns TRUE if the Forward API is using proxy auth, FALSE otherwise. */
int proxy_forward_use_proxy_auth(void);
#endif /* MOD_PROXY_FORWARD_H */
proftpd-mod_proxy-0.9.5/include/proxy/ftp/ 0000775 0000000 0000000 00000000000 14757370167 0020637 5 ustar 00root root 0000000 0000000 proftpd-mod_proxy-0.9.5/include/proxy/ftp/conn.h 0000664 0000000 0000000 00000002734 14757370167 0021753 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy FTP connection API
* Copyright (c) 2013-2016 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_FTP_CONN_H
#define MOD_PROXY_FTP_CONN_H
#include "mod_proxy.h"
conn_t *proxy_ftp_conn_accept(pool *p, conn_t *data_conn, conn_t *ctrl_conn,
int frontend_data);
conn_t *proxy_ftp_conn_connect(pool *p, const pr_netaddr_t *local_addr,
const pr_netaddr_t *remote_addr, int frontend_data);
conn_t *proxy_ftp_conn_listen(pool *p, const pr_netaddr_t *bind_addr,
int frontend_data);
#endif /* MOD_PROXY_FTP_CONN_H */
proftpd-mod_proxy-0.9.5/include/proxy/ftp/ctrl.h 0000664 0000000 0000000 00000003377 14757370167 0021766 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy FTP control conn API
* Copyright (c) 2012-2023 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_FTP_CTRL_H
#define MOD_PROXY_FTP_CTRL_H
#include "mod_proxy.h"
/* Note: this flag is only used for testing. */
#define PROXY_FTP_CTRL_FL_IGNORE_EOF 0x0001
#define PROXY_FTP_CTRL_FL_IGNORE_BLANK_RESP 0x0002
int proxy_ftp_ctrl_handle_async(pool *p, conn_t *backend_conn,
conn_t *frontend_conn, int flags);
pr_response_t *proxy_ftp_ctrl_recv_resp(pool *p, conn_t *ctrl_conn,
unsigned int *resp_nlines, int flags);
int proxy_ftp_ctrl_send_abort(pool *p, conn_t *ctrl_conn, cmd_rec *cmd);
int proxy_ftp_ctrl_send_cmd(pool *p, conn_t *ctrl_conn, cmd_rec *cmd);
int proxy_ftp_ctrl_send_resp(pool *p, conn_t *ctrl_conn, pr_response_t *resp,
unsigned int resp_nlines);
#endif /* MOD_PROXY_FTP_CTRL_H */
proftpd-mod_proxy-0.9.5/include/proxy/ftp/data.h 0000664 0000000 0000000 00000002501 14757370167 0021717 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy FTP data conn API
* Copyright (c) 2012-2016 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_FTP_DATA_H
#define MOD_PROXY_FTP_DATA_H
#include "mod_proxy.h"
pr_buffer_t *proxy_ftp_data_recv(pool *p, conn_t *conn, int frontend_data);
int proxy_ftp_data_send(pool *p, conn_t *conn, pr_buffer_t *pbuf,
int frontend_data);
#endif /* MOD_PROXY_FTP_DATA_H */
proftpd-mod_proxy-0.9.5/include/proxy/ftp/dirlist.h 0000664 0000000 0000000 00000004720 14757370167 0022465 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy FTP dirlist API
* Copyright (c) 2020 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_FTP_DIRLIST_H
#define MOD_PROXY_FTP_DIRLIST_H
#include "mod_proxy.h"
#include "proxy/session.h"
int proxy_ftp_dirlist_init(pool *p, struct proxy_session *proxy_sess);
int proxy_ftp_dirlist_finish(struct proxy_session *proxy_sess);
struct proxy_dirlist_fileinfo {
pool *pool;
struct stat *st;
unsigned char have_uid, have_gid;
struct tm *tm;
const char *user;
const char *group;
const char *type;
const char *perm;
const char *path;
};
#define PROXY_FTP_DIRLIST_OPT_USE_SLINK 0x0001
struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_dos(pool *p,
const char *text, size_t textlen, unsigned long opts);
struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_unix(pool *p,
const char *text, size_t textlen, struct tm *tm, unsigned long opts);
struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_text(pool *p,
const char *text, size_t textlen, struct tm *tm, void *user_data,
unsigned long opts);
const char *proxy_ftp_dirlist_fileinfo_to_facts(pool *p,
const struct proxy_dirlist_fileinfo *pdf, size_t *textlen);
/* Given a buffer of (possibly incomplete) dirlist data, return the text
* to give to the client. Note that there may be enough data accumulated
* yet to provide text to the client.
*/
int proxy_ftp_dirlist_to_text(pool *p, char *buf, size_t buflen,
size_t max_textsz, char **text, size_t *textlen, void *user_data);
#endif /* MOD_PROXY_FTP_DIRLIST_H */
proftpd-mod_proxy-0.9.5/include/proxy/ftp/facts.h 0000664 0000000 0000000 00000003415 14757370167 0022113 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy FTP Facts API
* Copyright (c) 2020 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_FTP_FACTS_H
#define MOD_PROXY_FTP_FACTS_H
#include "mod_proxy.h"
/* RFC 3659 Facts */
#define PROXY_FTP_FACTS_OPT_SHOW_MODIFY 0x00001
#define PROXY_FTP_FACTS_OPT_SHOW_PERM 0x00002
#define PROXY_FTP_FACTS_OPT_SHOW_SIZE 0x00004
#define PROXY_FTP_FACTS_OPT_SHOW_TYPE 0x00008
#define PROXY_FTP_FACTS_OPT_SHOW_UNIQUE 0x00010
#define PROXY_FTP_FACTS_OPT_SHOW_UNIX_GROUP 0x00020
#define PROXY_FTP_FACTS_OPT_SHOW_UNIX_MODE 0x00040
#define PROXY_FTP_FACTS_OPT_SHOW_UNIX_OWNER 0x00080
#define PROXY_FTP_FACTS_OPT_SHOW_UNIX_OWNER_NAME 0x00100
#define PROXY_FTP_FACTS_OPT_SHOW_UNIX_GROUP_NAME 0x00200
unsigned long proxy_ftp_facts_get_opts(void);
void proxy_ftp_facts_parse_opts(char *facts);
#endif /* MOD_PROXY_FTP_FACTS_H */
proftpd-mod_proxy-0.9.5/include/proxy/ftp/msg.h 0000664 0000000 0000000 00000003630 14757370167 0021600 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy FTP message API
* Copyright (c) 2013-2021 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_FTP_MSG_H
#define MOD_PROXY_FTP_MSG_H
#include "mod_proxy.h"
/* Format a string containg the address for use in a PORT command or a
* PASV response.
*/
const char *proxy_ftp_msg_fmt_addr(pool *, const pr_netaddr_t *,
unsigned short, int);
/* Format a string containg the address for use in an EPRT command or an
* EPSV response.
*/
const char *proxy_ftp_msg_fmt_ext_addr(pool *, const pr_netaddr_t *,
unsigned short, int, int);
/* Parse the address/port out of a string, e.g. from a PORT command or from
* a PASV response.
*/
const pr_netaddr_t *proxy_ftp_msg_parse_addr(pool *, const char *, int);
/* Parse the address/port out of a string, e.g. from an EPRT command or from
* an EPSV response.
*/
const pr_netaddr_t *proxy_ftp_msg_parse_ext_addr(pool *, const char *,
const pr_netaddr_t *, int, const char *);
#endif /* MOD_PROXY_FTP_MSG_H */
proftpd-mod_proxy-0.9.5/include/proxy/ftp/sess.h 0000664 0000000 0000000 00000003341 14757370167 0021766 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy FTP session API
* Copyright (c) 2015-2021 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_FTP_SESS_H
#define MOD_PROXY_FTP_SESS_H
#include "mod_proxy.h"
#include "proxy/session.h"
/* ProxyTLSTransferProtectionPolicy values */
#define PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_REQUIRED 1
#define PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_CLIENT 0
#define PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_CLEAR -1
int proxy_ftp_sess_get_feat(pool *, const struct proxy_session *proxy_sess);
int proxy_ftp_sess_send_auth_tls(pool *p,
const struct proxy_session *proxy_sess);
int proxy_ftp_sess_send_host(pool *, const struct proxy_session *proxy_sess);
int proxy_ftp_sess_send_pbsz_prot(pool *p,
const struct proxy_session *proxy_sess);
#endif /* MOD_PROXY_FTP_SESS_H */
proftpd-mod_proxy-0.9.5/include/proxy/ftp/xfer.h 0000664 0000000 0000000 00000002615 14757370167 0021760 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy FTP data transfer API
* Copyright (c) 2013-2016 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_FTP_XFER_H
#define MOD_PROXY_FTP_XFER_H
#include "mod_proxy.h"
#include "proxy/session.h"
int proxy_ftp_xfer_prepare_active(int, cmd_rec *, const char *,
struct proxy_session *, int);
const pr_netaddr_t *proxy_ftp_xfer_prepare_passive(int, cmd_rec *, const char *,
struct proxy_session *, int);
#endif /* MOD_PROXY_FTP_XFER_H */
proftpd-mod_proxy-0.9.5/include/proxy/inet.h 0000664 0000000 0000000 00000003220 14757370167 0021153 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy Inet API
* Copyright (c) 2015-2016 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_INET_H
#define MOD_PROXY_INET_H
#include "mod_proxy.h"
/* Proxied versions of the core Inet API functions; see include/inet.h. */
conn_t *proxy_inet_accept(pool *p, conn_t *data_conn, conn_t *ctrl_conn,
int rfd, int wfd, int resolve);
void proxy_inet_close(pool *p, conn_t *conn);
int proxy_inet_connect(pool *p, conn_t *conn, const pr_netaddr_t *addr,
int port);
int proxy_inet_listen(pool *p, conn_t *conn, int backlog, int flags);
conn_t *proxy_inet_openrw(pool *p, conn_t *conn, const pr_netaddr_t *addr,
int strm_type, int fd, int rfd, int wfd, int resolve);
#endif /* MOD_PROXY_INET_H */
proftpd-mod_proxy-0.9.5/include/proxy/netio.h 0000664 0000000 0000000 00000004643 14757370167 0021344 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy NetIO API
* Copyright (c) 2015-2016 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_NETIO_H
#define MOD_PROXY_NETIO_H
#include "mod_proxy.h"
pr_netio_t *proxy_netio_unset(int strm_type, const char *fn);
int proxy_netio_set(int strm_type, pr_netio_t *netio);
/* Tells the Proxy NetIO API to use the given netio for the given stream
* type, when proxy_netio_unset() and proxy_netio_set() are called on that
* stream type.
*/
int proxy_netio_use(int strm_type, pr_netio_t *netio);
/* Returns the netio that the Proxy NetIO API is using for a given stream
* type, if any.
*/
int proxy_netio_using(int strm_type, pr_netio_t **netio);
/* Proxied versions of the core NetIO API functions; see include/netio.h. */
pr_netio_stream_t *proxy_netio_open(pool *p, int strm_type, int fd, int mode);
int proxy_netio_close(pr_netio_stream_t *nstrm);
int proxy_netio_postopen(pr_netio_stream_t *nstrm);
int proxy_netio_printf(pr_netio_stream_t *nstrm, const char *fmt, ...);
int proxy_netio_poll(pr_netio_stream_t *nstrm);
int proxy_netio_postopen(pr_netio_stream_t *nstrm);
int proxy_netio_read(pr_netio_stream_t *nstrm, char *buf, size_t bufsz,
int bufmin);
void proxy_netio_reset_poll_interval(pr_netio_stream_t *nstrm);
void proxy_netio_set_poll_interval(pr_netio_stream_t *nstrm, unsigned int secs);
int proxy_netio_shutdown(pr_netio_stream_t *nstrm, int how);
int proxy_netio_write(pr_netio_stream_t *nstrm, char *buf, size_t bufsz);
#endif /* MOD_PROXY_NETIO_H */
proftpd-mod_proxy-0.9.5/include/proxy/random.h 0000664 0000000 0000000 00000002464 14757370167 0021505 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy random number API
* Copyright (c) 2013-2016 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_RANDOM_H
#define MOD_PROXY_RANDOM_H
#include "mod_proxy.h"
int proxy_random_init(void);
/* Return the next random number between the given min/max numbers, inclusive.
*/
long proxy_random_next(long min, long max);
#endif /* MOD_PROXY_RANDOM_H */
proftpd-mod_proxy-0.9.5/include/proxy/reverse.h 0000664 0000000 0000000 00000010211 14757370167 0021665 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy reverse-proxy API
* Copyright (c) 2012-2022 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_REVERSE_H
#define MOD_PROXY_REVERSE_H
#include "mod_proxy.h"
#include "proxy/session.h"
int proxy_reverse_init(pool *p, const char *tables_dir, int flags);
int proxy_reverse_free(pool *p);
int proxy_reverse_have_authenticated(cmd_rec *cmd);
int proxy_reverse_sess_init(pool *p, const char *tables_dir,
struct proxy_session *proxy_sess, int flags);
int proxy_reverse_sess_free(pool *p, struct proxy_session *proxy_sess);
int proxy_reverse_sess_exit(pool *p);
int proxy_reverse_handle_user(cmd_rec *cmd, struct proxy_session *proxy_sess,
int *successful, int *block_responses);
int proxy_reverse_handle_pass(cmd_rec *cmd, struct proxy_session *proxy_sess,
int *successful, int *block_responses);
array_header *proxy_reverse_json_parse_uris(pool *p, const char *path,
unsigned int flags);
/* Connect policy API */
#define PROXY_REVERSE_CONNECT_POLICY_RANDOM 1
#define PROXY_REVERSE_CONNECT_POLICY_ROUND_ROBIN 2
#define PROXY_REVERSE_CONNECT_POLICY_LEAST_CONNS 3
#define PROXY_REVERSE_CONNECT_POLICY_LEAST_RESPONSE_TIME 4
#define PROXY_REVERSE_CONNECT_POLICY_SHUFFLE 5
#define PROXY_REVERSE_CONNECT_POLICY_PER_USER 6
#define PROXY_REVERSE_CONNECT_POLICY_PER_GROUP 7
#define PROXY_REVERSE_CONNECT_POLICY_PER_HOST 8
/* Returns the configured connect policy ID. */
int proxy_reverse_get_connect_policy(void);
/* Return the policy ID for the given string, or -1 if the given policy
* is not recognized/supported.
*/
int proxy_reverse_connect_get_policy_id(const char *policy);
/* Returns TRUE if the given policy ID is a "sticky" policy, i.e. one of
* PerUser, PerGroup, or PerHost.
*/
int proxy_reverse_policy_is_sticky(int policy_id);
/* Returns a textual name for the given policy ID. */
const char *proxy_reverse_policy_name(int policy_id);
/* Returns the per-user/group backends for the given name. */
array_header *proxy_reverse_pername_backends(pool *p, const char *name,
int per_user);
/* Look up, and connect to, the selected backend server. */
int proxy_reverse_connect(pool *p, struct proxy_session *proxy_sess,
const void *connect_data);
/* Returns TRUE if the Reverse API is using proxy auth, FALSE otherwise. */
int proxy_reverse_use_proxy_auth(void);
/* Defines the datastore interface. */
struct proxy_reverse_datastore {
/* Policy callbacks */
int (*policy_init)(pool *p, void *dsh, int policy_id, unsigned int vhost_id,
array_header *backends, unsigned long opts);
const struct proxy_conn *(*policy_next_backend)(pool *p, void *dsh,
int policy_id, unsigned int vhost_id, array_header *default_backends,
const void *policy_data, int *backend_id);
int (*policy_used_backend)(pool *p, void *dsh, int policy_id,
unsigned int vhost_id, int backend_id);
int (*policy_update_backend)(pool *p, void *dsh, int policy_id,
unsigned int vhost_id, int backend_id, int conn_incr, long connect_ms);
void *(*init)(pool *p, const char *path, int flags);
void *(*open)(pool *p, const char *path, array_header *backends);
int (*close)(pool *p, void *dsh);
/* Datastore handle returned by the open callback. */
void *dsh;
int backend_id;
};
#endif /* MOD_PROXY_REVERSE_H */
proftpd-mod_proxy-0.9.5/include/proxy/reverse/ 0000775 0000000 0000000 00000000000 14757370167 0021521 5 ustar 00root root 0000000 0000000 proftpd-mod_proxy-0.9.5/include/proxy/reverse/db.h 0000664 0000000 0000000 00000002446 14757370167 0022265 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy Reverse Database API
* Copyright (c) 2017 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_REVERSE_DB_H
#define MOD_PROXY_REVERSE_DB_H
#include "mod_proxy.h"
#include "proxy/reverse.h"
int proxy_reverse_db_as_datastore(struct proxy_reverse_datastore *ds,
void *ds_data, size_t ds_datasz);
#endif /* MOD_PROXY_REVERSE_DB_H */
proftpd-mod_proxy-0.9.5/include/proxy/reverse/redis.h 0000664 0000000 0000000 00000002525 14757370167 0023004 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy Reverse Redis API
* Copyright (c) 2017-2020 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_REVERSE_REDIS_H
#define MOD_PROXY_REVERSE_REDIS_H
#include "mod_proxy.h"
#include "proxy/reverse.h"
#include "proxy/reverse/redis.h"
int proxy_reverse_redis_as_datastore(struct proxy_reverse_datastore *ds,
void *ds_data, size_t ds_datasz);
#endif /* MOD_PROXY_REVERSE_REDIS_H */
proftpd-mod_proxy-0.9.5/include/proxy/session.h 0000664 0000000 0000000 00000005747 14757370167 0021717 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy sessions
* Copyright (c) 2012-2021 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_SESSION_H
#define MOD_PROXY_SESSION_H
#include "mod_proxy.h"
struct proxy_conn;
struct proxy_session {
struct pool_rec *pool;
int connect_timeout;
int connect_timerno;
int linger_timeout;
/* Frontend connection */
conn_t *frontend_ctrl_conn;
conn_t *frontend_data_conn;
volatile int frontend_sess_flags;
const pr_netaddr_t *frontend_data_addr;
/* Backend connection */
conn_t *backend_ctrl_conn;
conn_t *backend_data_conn;
volatile int backend_sess_flags;
const pr_netaddr_t *backend_data_addr;
/* Address for connections to/from destination server. May be null. */
const pr_netaddr_t *src_addr;
const struct proxy_conn *dst_pconn;
/* Address of the destination server. May be null. */
const pr_netaddr_t *dst_addr;
array_header *other_addrs;
/* Which protocol are we proxying? */
int use_ftp, use_ssh;
/* Features supported by backend server. */
pr_table_t *backend_features;
/* Data transfer pool, for things like the frontend/backend address
* objects.
*/
pool *dataxfer_pool;
/* Data transfer policy: PASV, EPSV, PORT, EPRT, or client. */
int dataxfer_policy;
/* Directory list policy: LIST, or client. */
int dirlist_policy;
unsigned long dirlist_opts;
void *dirlist_ctx;
};
/* Zero indicates "do what the client does". */
#define PROXY_SESS_DATA_TRANSFER_POLICY_DEFAULT 0
#define PROXY_SESS_DIRECTORY_LIST_POLICY_DEFAULT 0
#define PROXY_SESS_DIRECTORY_LIST_POLICY_LIST 1
/* Default MaxLoginAttempts */
#define PROXY_SESS_MAX_LOGIN_ATTEMPTS 3
const struct proxy_session *proxy_session_alloc(pool *p);
int proxy_session_free(pool *p, const struct proxy_session *proxy_sess);
int proxy_session_reset_dataxfer(struct proxy_session *proxy_sess);
int proxy_session_check_password(pool *p, const char *user, const char *passwd);
int proxy_session_setup_env(pool *p, const char *user, int flags);
#define PROXY_SESSION_FL_CHECK_LOGIN_ACL 0x00001
#endif /* MOD_PROXY_SESSION_H */
proftpd-mod_proxy-0.9.5/include/proxy/ssh.h 0000664 0000000 0000000 00000005175 14757370167 0021024 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH API
* Copyright (c) 2021-2023 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_SSH_H
#define MOD_PROXY_SSH_H
#include "mod_proxy.h"
#include "proxy/session.h"
/* ProxySFTPOptions values. NOTE: Make sure these do NOT collide with existing
* PROXY_OPT_ values defined in mod_proxy.h.
*/
#define PROXY_OPT_SSH_PESSIMISTIC_KEXINIT 0x0100
#define PROXY_OPT_SSH_OLD_PROTO_COMPAT 0x0200
#define PROXY_OPT_SSH_ALLOW_WEAK_DH 0x0400
#define PROXY_OPT_SSH_ALLOW_WEAK_SECURITY 0x0800
#define PROXY_OPT_SSH_NO_EXT_INFO 0x1000
#define PROXY_OPT_SSH_NO_HOSTKEY_ROTATION 0x2000
#define PROXY_OPT_SSH_NO_STRICT_KEX 0x4000
int proxy_ssh_init(pool *p, const char *tables_dir, int flags);
int proxy_ssh_free(pool *p);
int proxy_ssh_sess_init(pool *p, struct proxy_session *proxy_sess, int flags);
int proxy_ssh_sess_free(pool *p);
/* Defines the datastore interface. */
struct proxy_ssh_datastore {
/* Keystore callbacks */
int (*hostkey_add)(pool *p, void *dsh, unsigned int vhost_id,
const char *backend_uri, const char *algo,
const unsigned char *hostkey_data, uint32_t hostkey_datalen);
const unsigned char *(*hostkey_get)(pool *p, void *dsh,
unsigned int vhost_id, const char *backend_uri, const char **algo,
uint32_t *hostkey_datalen);
int (*hostkey_update)(pool *p, void *dsh, unsigned int vhost_id,
const char *backend_uri, const char *algo,
const unsigned char *hostkey_data, uint32_t hostkey_datalen);
int (*init)(pool *p, const char *path, int flags);
void *(*open)(pool *p, const char *path, unsigned long opts);
int (*close)(pool *p, void *dsh);
/* Datastore handle returned by the open callback. */
void *dsh;
};
#endif /* MOD_PROXY_SSH_H */
proftpd-mod_proxy-0.9.5/include/proxy/ssh/ 0000775 0000000 0000000 00000000000 14757370167 0020643 5 ustar 00root root 0000000 0000000 proftpd-mod_proxy-0.9.5/include/proxy/ssh/agent.h 0000664 0000000 0000000 00000003202 14757370167 0022107 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH agent API
* Copyright (c) 2021 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_SSH_AGENT_H
#define MOD_PROXY_SSH_AGENT_H
#include "mod_proxy.h"
#if defined(PR_USE_OPENSSL)
struct agent_key {
unsigned char *key_data;
uint32_t key_datalen;
const char *agent_path;
};
int proxy_ssh_agent_get_keys(pool *p, const char *, array_header *);
const unsigned char *proxy_ssh_agent_sign_data(pool *, const char *,
const unsigned char *, uint32_t, const unsigned char *, uint32_t, uint32_t *,
int);
#define PROXY_SSH_AGENT_SIGN_FL_USE_RSA_SHA256 0x001
#define PROXY_SSH_AGENT_SIGN_FL_USE_RSA_SHA512 0x002
#endif /* PR_USE_OPENSSL */
#endif /* MOD_PROXY_SSH_AGENT_H */
proftpd-mod_proxy-0.9.5/include/proxy/ssh/auth.h 0000664 0000000 0000000 00000003316 14757370167 0021760 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH auth API
* Copyright (c) 2021-2022 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_SSH_AUTH_H
#define MOD_PROXY_SSH_AUTH_H
#include "mod_proxy.h"
#include "proxy/session.h"
#include "proxy/ssh/packet.h"
#if defined(PR_USE_OPENSSL)
int proxy_ssh_auth_init(pool *p);
int proxy_ssh_auth_sess_init(pool *p, const struct proxy_session *proxy_sess);
/* Returns 1 for successfully completed authentication, 0 if the client
* needs to make another authentication attempt, and -1 on error.
*/
int proxy_ssh_auth_handle(struct proxy_ssh_packet *pkt,
const struct proxy_session *proxy_sess);
int proxy_ssh_auth_set_frontend_success_handle(pool *p, int (*cb)(pool *p,
const char *user));
#endif /* PR_USE_OPENSSL */
#endif /* MOD_PROXY_SSH_AUTH_H */
proftpd-mod_proxy-0.9.5/include/proxy/ssh/bcrypt.h 0000664 0000000 0000000 00000002666 14757370167 0022331 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH bcrypt PBKDF2
* Copyright (c) 2021 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_SSH_BCRYPT_H
#define MOD_PROXY_SSH_BCRYPT_H
#include "mod_proxy.h"
#if defined(PR_USE_OPENSSL)
#define PROXY_SSH_BCRYPT_DIGEST_LEN 32
int proxy_ssh_bcrypt_pbkdf2(pool *p, const char *passphrase,
size_t passphrase_len, unsigned char *salt, uint32_t salt_len,
uint32_t rounds, unsigned char *key, uint32_t key_len);
#endif /* PR_USE_OPENSSL */
#endif /* MOD_PROXY_SSH_BCRYPT_H */
proftpd-mod_proxy-0.9.5/include/proxy/ssh/cipher.h 0000664 0000000 0000000 00000005322 14757370167 0022270 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH cipher API
* Copyright (c) 2021-2022 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_SSH_CIPHER_H
#define MOD_PROXY_SSH_CIPHER_H
#include "mod_proxy.h"
#if defined(PR_USE_OPENSSL)
int proxy_ssh_cipher_init(void);
int proxy_ssh_cipher_free(void);
/* Returns the cipher block size, or 8, whichever is larger. This value is
* used when reading in the first bytes of a packet in order to determine
* the packet length. See RFC4253, Section 6, "Binary Packet Protocol".
*/
size_t proxy_ssh_cipher_get_read_block_size(void);
size_t proxy_ssh_cipher_get_write_block_size(void);
void proxy_ssh_cipher_set_read_block_size(size_t);
void proxy_ssh_cipher_set_write_block_size(size_t);
/* Returns the cipher authenticated data size, or zero. */
size_t proxy_ssh_cipher_get_read_auth_size(void);
size_t proxy_ssh_cipher_get_read_auth_size2(void);
size_t proxy_ssh_cipher_get_write_auth_size(void);
size_t proxy_ssh_cipher_get_write_auth_size2(void);
const char *proxy_ssh_cipher_get_read_algo(void);
int proxy_ssh_cipher_set_read_algo(pool *p, const char *algo);
int proxy_ssh_cipher_set_read_key(pool *p, const EVP_MD *md,
const unsigned char *k, uint32_t klen, const char *h, uint32_t hlen,
int role);
int proxy_ssh_cipher_read_data(struct proxy_ssh_packet *pkt,
unsigned char *data, uint32_t data_len, unsigned char **buf,
uint32_t *buflen);
const char *proxy_ssh_cipher_get_write_algo(void);
int proxy_ssh_cipher_set_write_algo(pool *p, const char *algo);
int proxy_ssh_cipher_set_write_key(pool *p, const EVP_MD *md,
const unsigned char *k, uint32_t klen, const char *h, uint32_t hlen,
int role);
int proxy_ssh_cipher_write_data(struct proxy_ssh_packet *pkt,
unsigned char *buf, size_t *bufsz);
#endif /* PR_USE_OPENSSL */
#endif /* MOD_PROXY_SSH_CIPHER_H */
proftpd-mod_proxy-0.9.5/include/proxy/ssh/compress.h 0000664 0000000 0000000 00000003414 14757370167 0022651 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH compression API
* Copyright (c) 2021 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_SSH_COMPRESS_H
#define MOD_PROXY_SSH_COMPRESS_H
#include "mod_proxy.h"
#include "proxy/ssh/packet.h"
#if defined(PR_USE_OPENSSL)
#define PROXY_SSH_COMPRESS_FL_NEW_KEY 1
#define PROXY_SSH_COMPRESS_FL_AUTHENTICATED 2
int proxy_ssh_compress_init_read(int);
const char *proxy_ssh_compress_get_read_algo(void);
int proxy_ssh_compress_set_read_algo(pool *p, const char *algo);
int proxy_ssh_compress_read_data(struct proxy_ssh_packet *);
int proxy_ssh_compress_init_write(int);
const char *proxy_ssh_compress_get_write_algo(void);
int proxy_ssh_compress_set_write_algo(pool *p, const char *algo);
int proxy_ssh_compress_write_data(struct proxy_ssh_packet *);
#endif /* PR_USE_OPENSSL */
#endif /* MOD_PROXY_SSH_COMPRESS_H */
proftpd-mod_proxy-0.9.5/include/proxy/ssh/crypto.h 0000664 0000000 0000000 00000003304 14757370167 0022334 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH crypto API
* Copyright (c) 2021-2022 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_SSH_CRYPTO_H
#define MOD_PROXY_SSH_CRYPTO_H
#include "mod_proxy.h"
#if defined(PR_USE_OPENSSL)
#include
void proxy_ssh_crypto_free(int flags);
const EVP_CIPHER *proxy_ssh_crypto_get_cipher(const char *algo, size_t *key_len,
size_t *auth_len, size_t *discard_len);
const EVP_MD *proxy_ssh_crypto_get_digest(const char *algo, uint32_t *mac_len);
const char *proxy_ssh_crypto_get_kexinit_cipher_list(pool *p);
const char *proxy_ssh_crypto_get_kexinit_digest_list(pool *p);
const char *proxy_ssh_crypto_get_errors(void);
size_t proxy_ssh_crypto_get_size(size_t, size_t);
#endif /* PR_USE_OPENSSL */
#endif /* MOD_PROXY_SSH_CRYPTO_H */
proftpd-mod_proxy-0.9.5/include/proxy/ssh/db.h 0000664 0000000 0000000 00000002502 14757370167 0021400 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH Database API
* Copyright (c) 2021 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_SSH_DB_H
#define MOD_PROXY_SSH_DB_H
#include "mod_proxy.h"
#include "proxy/ssh.h"
#if defined(PR_USE_OPENSSL)
int proxy_ssh_db_as_datastore(struct proxy_ssh_datastore *ds, void *ds_data,
size_t ds_datasz);
#endif /* PR_USE_OPENSSL */
#endif /* MOD_PROXY_SSH_DB_H */
proftpd-mod_proxy-0.9.5/include/proxy/ssh/disconnect.h 0000664 0000000 0000000 00000004146 14757370167 0023152 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH disconnect API
* Copyright (c) 2021 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_SSH_DISCONNECT_H
#define MOD_PROXY_SSH_DISCONNECT_H
#include "mod_proxy.h"
#if defined(PR_USE_OPENSSL)
void proxy_ssh_disconnect_conn(conn_t *, uint32_t, const char *, const char *,
int, const char *);
void proxy_ssh_disconnect_send(pool *, conn_t *, uint32_t, const char *,
const char *, int, const char *);
/* Given a disconnect reason code from a server, return a string explaining
* that code.
*/
const char *proxy_ssh_disconnect_get_text(uint32_t);
/* Deal with the fact that __FUNCTION__ is a gcc extension. Sun's compilers
* (e.g. SunStudio) like __func__.
*/
# if defined(__FUNCTION__)
#define PROXY_SSH_DISCONNECT_CONN(c, n, m) \
proxy_ssh_disconnect_conn((c), (n), (m), __FILE__, __LINE__, __FUNCTION__)
# elif defined(__func__)
#define PROXY_SSH_DISCONNECT_CONN(c, n, m) \
proxy_ssh_disconnect_conn((c), (n), (m), __FILE__, __LINE__, __func__)
# else
#define PROXY_SSH_DISCONNECT_CONN(c, n, m) \
proxy_ssh_disconnect_conn((c), (n), (m), __FILE__, __LINE__, "")
# endif
#endif /* PR_USE_OPENSSL */
#endif /* MOD_PROXY_SSH_DISCONNECT_H */
proftpd-mod_proxy-0.9.5/include/proxy/ssh/interop.h 0000664 0000000 0000000 00000007172 14757370167 0022503 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH interop API
* Copyright (c) 2021 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_SSH_INTEROP_H
#define MOD_PROXY_SSH_INTEROP_H
#include "mod_proxy.h"
#include "proxy/session.h"
#if defined(PR_USE_OPENSSL)
/* For servers which do not support IGNORE packets */
#define PROXY_SSH_FEAT_IGNORE_MSG 0x0001
/* For servers which always truncate the HMAC len to 16 bits, regardless
* of the actual HMAC len.
*/
#define PROXY_SSH_FEAT_MAC_LEN 0x0002
/* For servers which do not include K when deriving cipher keys. */
#define PROXY_SSH_FEAT_CIPHER_USE_K 0x0004
/* For servers which do not support rekeying */
#define PROXY_SSH_FEAT_REKEYING 0x0008
/* For servers which do not support USERAUTH_BANNER packets */
#define PROXY_SSH_FEAT_USERAUTH_BANNER 0x0010
/* For servers which do not send a string indicating the public key
* algorithm in their publickey authentication requests. This also
* includes servers which do not use the string "publickey", and the
* string for the public key algorithm, in the public key signature
* (as dictated by Section 7 of RFC4252).
*/
#define PROXY_SSH_FEAT_HAVE_PUBKEY_ALGO 0x0020
/* For servers whose publickey signatures always use a service name of
* "ssh-userauth", regardless of the actual service name included in the
* USERAUTH_REQUEST packet.
*/
#define PROXY_SSH_FEAT_SERVICE_IN_PUBKEY_SIG 0x0040
/* For servers whose DSA publickey signatures do not include the string
* "ssh-dss".
*/
#define PROXY_SSH_FEAT_HAVE_PUBKEY_ALGO_IN_DSA_SIG 0x0080
/* For servers whose hostbased signatures always use a service name of
* "ssh-userauth", regardless of the actual service name included in the
* USERAUTH_REQUEST packet.
*/
#define PROXY_SSH_FEAT_SERVICE_IN_HOST_SIG 0x0100
/* For servers that want the client to pessimistically send its NEWKEYS message
* after they send their NEWKEYS message.
*/
#define PROXY_SSH_FEAT_PESSIMISTIC_NEWKEYS 0x0200
/* For servers which cannot/do not tolerate non-kex related packets after a
* client has requested rekeying.
*/
#define PROXY_SSH_FEAT_NO_DATA_WHILE_REKEYING 0x0400
/* For servers which do not support/implement RFC 4419 DH group exchange. */
#define PROXY_SSH_FEAT_DH_NEW_GEX 0x0800
/* Compares the given server version string against a table of known server
* versions and their interoperability/compatibility issues.
*/
int proxy_ssh_interop_handle_version(pool *, const struct proxy_session *,
const char *);
/* Returns TRUE if the server supports the requested feature, FALSE
* otherwise.
*/
int proxy_ssh_interop_supports_feature(int);
int proxy_ssh_interop_init(void);
int proxy_ssh_interop_free(void);
#endif /* PR_USE_OPENSSL */
#endif /* MOD_PROXY_SSH_INTEROP_H */
proftpd-mod_proxy-0.9.5/include/proxy/ssh/kex.h 0000664 0000000 0000000 00000003401 14757370167 0021601 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH kex API
* Copyright (c) 2021 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_SSH_KEX_H
#define MOD_PROXY_SSH_KEX_H
#include "mod_proxy.h"
#include "proxy/session.h"
#include "proxy/ssh.h"
#if defined(PR_USE_OPENSSL)
int proxy_ssh_kex_handle(struct proxy_ssh_packet *pkt,
const struct proxy_session *proxy_sess);
int proxy_ssh_kex_init(pool *p, const char *client_version,
const char *server_version);
int proxy_ssh_kex_free(void);
int proxy_ssh_kex_sess_init(pool *p, struct proxy_ssh_datastore *ds,
int verify_hostkeys);
int proxy_ssh_kex_sess_free(void);
int proxy_ssh_kex_send_first_kexinit(pool *p,
const struct proxy_session *proxy_sess);
#define PROXY_SSH_KEX_DH_GROUP_MIN 1024
#define PROXY_SSH_KEX_DH_GROUP_MAX 8192
#endif /* PR_USE_OPENSSL */
#endif /* MOD_PROXY_SSH_KEX_H */
proftpd-mod_proxy-0.9.5/include/proxy/ssh/keys.h 0000664 0000000 0000000 00000006052 14757370167 0021772 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH keys API
* Copyright (c) 2021-2022 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_SSH_KEYS_H
#define MOD_PROXY_SSH_KEYS_H
#include "mod_proxy.h"
#if defined(PR_USE_OPENSSL)
#include
enum proxy_ssh_key_type_e {
PROXY_SSH_KEY_UNKNOWN = 0,
PROXY_SSH_KEY_DSA,
PROXY_SSH_KEY_RSA,
PROXY_SSH_KEY_RSA_SHA256,
PROXY_SSH_KEY_RSA_SHA512,
PROXY_SSH_KEY_ECDSA_256,
PROXY_SSH_KEY_ECDSA_384,
PROXY_SSH_KEY_ECDSA_521,
PROXY_SSH_KEY_ED25519,
PROXY_SSH_KEY_ED448
};
/* Returns a string of colon-separated lowercase hex characters, representing
* the key "fingerprint" which has been run through the specified digest
* algorithm.
*
* As per draft-ietf-secsh-fingerprint-00, only MD5 fingerprints are currently
* supported.
*/
const char *proxy_ssh_keys_get_fingerprint(pool *, unsigned char *, uint32_t,
int);
#define PROXY_SSH_KEYS_FP_DIGEST_MD5 1
#define PROXY_SSH_KEYS_FP_DIGEST_SHA1 2
#define PROXY_SSH_KEYS_FP_DIGEST_SHA256 3
enum proxy_ssh_key_type_e proxy_ssh_keys_get_key_type(const char *algo);
const char *proxy_ssh_keys_get_key_type_desc(enum proxy_ssh_key_type_e);
void proxy_ssh_keys_free(void);
int proxy_ssh_keys_have_hostkey(enum proxy_ssh_key_type_e);
int proxy_ssh_keys_get_hostkey(pool *p, const char *);
const unsigned char *proxy_ssh_keys_get_hostkey_data(pool *,
enum proxy_ssh_key_type_e, uint32_t *);
void proxy_ssh_keys_get_passphrases(void);
int proxy_ssh_keys_set_passphrase_provider(const char *);
const unsigned char *proxy_ssh_keys_sign_data(pool *, enum proxy_ssh_key_type_e,
const unsigned char *, size_t, size_t *);
#if defined(PR_USE_OPENSSL_ECC)
int proxy_ssh_keys_validate_ecdsa_params(const EC_GROUP *, const EC_POINT *);
#endif /* PR_USE_OPENSSL_ECC */
int proxy_ssh_keys_verify_pubkey_type(pool *, unsigned char *, uint32_t,
enum proxy_ssh_key_type_e);
int proxy_ssh_keys_verify_signed_data(pool *p, const char *pubkey_algo,
unsigned char *pubkey_data, uint32_t pubkey_datalen,
unsigned char *signature, uint32_t signaturelen,
unsigned char *sig_data, size_t sig_datalen);
#endif /* PR_USE_OPENSSL */
#endif /* MOD_PROXY_SSH_KEYS_H */
proftpd-mod_proxy-0.9.5/include/proxy/ssh/mac.h 0000664 0000000 0000000 00000004251 14757370167 0021556 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH MAC API
* Copyright (c) 2021-2022 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_SSH_MAC_H
#define MOD_PROXY_SSH_MAC_H
#include "mod_proxy.h"
#include "proxy/ssh/packet.h"
#if defined(PR_USE_OPENSSL)
int proxy_ssh_mac_init(void);
int proxy_ssh_mac_free(void);
/* Returns the block size of the negotiated MAC algorithm, or 0 if no MAC
* has been negotiated yet.
*/
size_t proxy_ssh_mac_get_block_size(void);
void proxy_ssh_mac_set_block_size(size_t blocksz);
const char *proxy_ssh_mac_get_read_algo(void);
int proxy_ssh_mac_is_read_etm(void);
int proxy_ssh_mac_set_read_algo(pool *p, const char *algo);
int proxy_ssh_mac_set_read_key(pool *p, const EVP_MD *md,
const unsigned char *k, uint32_t klen, const char *h, uint32_t hlen,
int role);
int proxy_ssh_mac_read_data(struct proxy_ssh_packet *pkt);
const char *proxy_ssh_mac_get_write_algo(void);
int proxy_ssh_mac_is_write_etm(void);
int proxy_ssh_mac_set_write_algo(pool *p, const char *algo);
int proxy_ssh_mac_set_write_key(pool *p, const EVP_MD *md,
const unsigned char *k, uint32_t klen, const char *h, uint32_t hlen,
int role);
int proxy_ssh_mac_write_data(struct proxy_ssh_packet *pkt);
#endif /* PR_USE_OPENSSL */
#endif /* MOD_PROXY_SSH_MAC_H */
proftpd-mod_proxy-0.9.5/include/proxy/ssh/misc.h 0000664 0000000 0000000 00000002553 14757370167 0021754 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH miscellany API
* Copyright (c) 2021 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_SSH_MISC_H
#define MOD_PROXY_SSH_MISC_H
#include "mod_proxy.h"
#if defined(PR_USE_OPENSSL)
int proxy_ssh_misc_namelist_contains(pool *, const char *, const char *);
const char *proxy_ssh_misc_namelist_shared(pool *, const char *, const char *);
#endif /* PR_USE_OPENSSL */
#endif /* MOD_PROXY_SSH_MISC_H */
proftpd-mod_proxy-0.9.5/include/proxy/ssh/msg.h 0000664 0000000 0000000 00000006112 14757370167 0021602 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH message API
* Copyright (c) 2021 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_SSH_MSG_H
#define MOD_PROXY_SSH_MSG_H
#include "mod_proxy.h"
#if defined(PR_USE_OPENSSL)
#if defined(PR_USE_OPENSSL_ECC)
# include
# include
#endif /* PR_USE_OPENSSL_ECC */
uint32_t proxy_ssh_msg_read_byte(pool *p, unsigned char **buf,
uint32_t *buflen, unsigned char *msg);
uint32_t proxy_ssh_msg_read_bool(pool *p, unsigned char **buf,
uint32_t *buflen, int *msg);
uint32_t proxy_ssh_msg_read_data(pool *p, unsigned char **buf,
uint32_t *buflen, size_t msglen, unsigned char **msg);
#if defined(PR_USE_OPENSSL_ECC)
uint32_t proxy_ssh_msg_read_ecpoint(pool *p, unsigned char **buf,
uint32_t *buflen, const EC_GROUP *ec_group, EC_POINT **msg);
#endif /* PR_USE_OPENSSL_ECC */
uint32_t proxy_ssh_msg_read_int(pool *p, unsigned char **buf, uint32_t *buflen,
uint32_t *msg);
uint32_t proxy_ssh_msg_read_long(pool *p, unsigned char **buf, uint32_t *buflen,
uint64_t *msg);
uint32_t proxy_ssh_msg_read_mpint(pool *p, unsigned char **buf,
uint32_t *buflen, const BIGNUM **msg);
uint32_t proxy_ssh_msg_read_string(pool *p, unsigned char **buf,
uint32_t *buflen, char **msg);
uint32_t proxy_ssh_msg_write_byte(unsigned char **buf, uint32_t *buflen,
unsigned char msg);
uint32_t proxy_ssh_msg_write_bool(unsigned char **buf, uint32_t *buflen,
unsigned char msg);
uint32_t proxy_ssh_msg_write_data(unsigned char **buf, uint32_t *buflen,
const unsigned char *msg, size_t msglen, int include_len);
#if defined(PR_USE_OPENSSL_ECC)
uint32_t proxy_ssh_msg_write_ecpoint(unsigned char **buf, uint32_t *buflen,
const EC_GROUP *ec_group, const EC_POINT *ec_point);
#endif /* PR_USE_OPENSSL_ECC */
uint32_t proxy_ssh_msg_write_int(unsigned char **buf, uint32_t *buflen,
uint32_t msg);
uint32_t proxy_ssh_msg_write_long(unsigned char **buf, uint32_t *buflen,
uint64_t msg);
uint32_t proxy_ssh_msg_write_mpint(unsigned char **buf, uint32_t *buflen,
const BIGNUM *msg);
uint32_t proxy_ssh_msg_write_string(unsigned char **buf, uint32_t *buflen,
const char *msg);
#endif /* PR_USE_OPENSSL */
#endif /* MOD_PROXY_SSH_MSG_H */
proftpd-mod_proxy-0.9.5/include/proxy/ssh/packet.h 0000664 0000000 0000000 00000013213 14757370167 0022263 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH packet API
* Copyright (c) 2021-2023 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_SSH_PACKET_H
#define MOD_PROXY_SSH_PACKET_H
#include "mod_proxy.h"
#include "proxy/session.h"
#if defined(PR_USE_OPENSSL)
/* From RFC 4253, Section 6 */
/* NOTE: This struct MUST be kept in sync with the struct used in mod_sftp;
* failure to do so WILL lead to inexplicable and hard-to-diagnose errors!
*/
struct proxy_ssh_packet {
pool *pool;
/* Module that created this packet. */
module *m;
/* Length of the packet, not including mac or packet_len field itself. */
uint32_t packet_len;
/* Length of the padding field. */
unsigned char padding_len;
unsigned char *payload;
uint32_t payload_len;
/* Must be at least 4 bytes of padding, with a maximum of 255 bytes. */
unsigned char *padding;
/* Additional Authenticated Data (AAD). */
unsigned char *aad;
uint32_t aad_len;
/* Message Authentication Code. */
unsigned char *mac;
uint32_t mac_len;
/* Packet sequence number. */
uint32_t seqno;
};
#define PROXY_SSH_MIN_PADDING_LEN 4
#define PROXY_SSH_MAX_PADDING_LEN 255
/* From the SFTP Draft, Section 4. */
struct proxy_sftp_packet {
uint32_t packet_len;
unsigned char packet_type;
uint32_t request_id;
};
struct proxy_ssh_packet *proxy_ssh_packet_create(pool *p);
char proxy_ssh_packet_get_msg_type(struct proxy_ssh_packet *pkt);
char proxy_ssh_packet_peek_msg_type(const struct proxy_ssh_packet *pkt);
const char *proxy_ssh_packet_get_msg_type_desc(unsigned char msg_type);
void proxy_ssh_packet_log_cmd(struct proxy_ssh_packet *pkt, int from_frontend);
#define PROXY_SSH_PACKET_IO_READ 5
#define PROXY_SSH_PACKET_IO_WRITE 7
int proxy_ssh_packet_conn_poll(conn_t *conn, int io);
/* Similar to `proxy_ssh_packet_conn_poll`, but we poll multiple connections.
* 0 is returned if the frontend connection has data, 1 is returned if the
* backend connection has data, and -1 on error/timeout.
*/
int proxy_ssh_packet_conn_mpoll(conn_t *frontend_conn, conn_t *backend_conn,
int io);
int proxy_ssh_packet_conn_read(conn_t *conn, void *buf, size_t reqlen,
int flags);
int proxy_ssh_packet_read(conn_t *conn, struct proxy_ssh_packet *pkt);
/* This proxy_ssh_packet_conn_read() flag is used to tell the function to
* read in as many of the requested length of data as it can, but to NOT
* keep polling until that length has been acquired (i.e. to read the
* requested length pessimistically, assuming that it will not all appear).
*/
#define PROXY_SSH_PACKET_READ_FL_PESSIMISTIC 0x001
int proxy_ssh_packet_send(conn_t *conn, struct proxy_ssh_packet *pkt);
/* Wrapper function around proxy_ssh_packet_send() which handles the sending
* of messages and buffering of messages for network efficiency.
*/
int proxy_ssh_packet_write(conn_t *conn, struct proxy_ssh_packet *pkt);
int proxy_ssh_packet_write_frontend(conn_t *conn, struct proxy_ssh_packet *pkt);
/* Proxy the packet from frontend-to-backend, or backend-to-frontend. */
int proxy_ssh_packet_proxied(const struct proxy_session *proxy_sess,
struct proxy_ssh_packet *pkt, int from_frontend);
/* This function reads in an SSH2 packet from the socket, and dispatches
* the packet to various handlers.
*/
int proxy_ssh_packet_process(pool *p, const struct proxy_session *proxy_sess);
/* Handle any SSH2 packet. */
int proxy_ssh_packet_handle(void *pkt);
/* These specialized functions are for handling the additional message types
* defined in RFC 4253, Section 11, e.g. during KEX.
*/
void proxy_ssh_packet_handle_debug(struct proxy_ssh_packet *pkt);
void proxy_ssh_packet_handle_disconnect(struct proxy_ssh_packet *pkt);
void proxy_ssh_packet_handle_ext_info(struct proxy_ssh_packet *pkt);
void proxy_ssh_packet_handle_ignore(struct proxy_ssh_packet *pkt);
void proxy_ssh_packet_handle_unimplemented(struct proxy_ssh_packet *pkt);
/* These are used for implementing the "strict KEX" mitigations of the Terrapin
* attack (Issue 257).
*/
uint32_t proxy_ssh_packet_get_server_seqno(void);
void proxy_ssh_packet_reset_client_seqno(void);
void proxy_ssh_packet_reset_server_seqno(void);
int proxy_ssh_packet_set_version(const char *client_version);
int proxy_ssh_packet_send_version(conn_t *conn);
int proxy_ssh_packet_get_poll_attempts(unsigned int *nattempts);
int proxy_ssh_packet_set_poll_attempts(unsigned int nattempts);
int proxy_ssh_packet_get_poll_timeout(int *secs, unsigned long *ms);
int proxy_ssh_packet_set_poll_timeout(int secs, unsigned long ms);
int proxy_ssh_packet_set_server_alive(unsigned int, unsigned int);
int proxy_ssh_packet_set_frontend_packet_handle(pool *p, int (*cb)(void *pkt));
void proxy_ssh_packet_set_frontend_packet_write(int (*cb)(int fd, void *pkt));
#endif /* PR_USE_OPENSSL */
#endif /* MOD_PROXY_SSH_PACKET_H */
proftpd-mod_proxy-0.9.5/include/proxy/ssh/redis.h 0000664 0000000 0000000 00000002423 14757370167 0022123 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH Redis API
* Copyright (c) 2022 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_SSH_REDIS_H
#define MOD_PROXY_SSH_REDIS_H
#include "mod_proxy.h"
#include "proxy/ssh.h"
int proxy_ssh_redis_as_datastore(struct proxy_ssh_datastore *ds, void *ds_data,
size_t ds_datasz);
#endif /* MOD_PROXY_SSH_REDIS_H */
proftpd-mod_proxy-0.9.5/include/proxy/ssh/service.h 0000664 0000000 0000000 00000002551 14757370167 0022457 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH service API
* Copyright (c) 2021 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_SSH_SERVICE_H
#define MOD_PROXY_SSH_SERVICE_H
#include "mod_proxy.h"
#include "proxy/session.h"
#include "proxy/ssh/packet.h"
#if defined(PR_USE_OPENSSL)
int proxy_ssh_service_handle(struct proxy_ssh_packet *,
const struct proxy_session *);
#endif /* PR_USE_OPENSSL */
#endif /* MOD_PROXY_SSH_SERVICE_H */
proftpd-mod_proxy-0.9.5/include/proxy/ssh/session.h 0000664 0000000 0000000 00000002734 14757370167 0022505 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH session API
* Copyright (c) 2021 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_SSH_SESSION_H
#define MOD_PROXY_SSH_SESSION_H
#include "mod_proxy.h"
#if defined(PR_USE_OPENSSL)
uint32_t proxy_ssh_session_get_id(const unsigned char **);
/* Note that the provided pool must have the same lifetime as that of the
* entire SSH session, e.g. proxy_pool or similar.
*/
int proxy_ssh_session_set_id(pool *p, const unsigned char *, uint32_t);
#endif /* PR_USE_OPENSSL */
#endif /* MOD_PROXY_SSH_SESSION_H */
proftpd-mod_proxy-0.9.5/include/proxy/ssh/ssh2.h 0000664 0000000 0000000 00000010615 14757370167 0021676 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH2 constants
* Copyright (c) 2021 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_SSH_SSH2_H
#define MOD_PROXY_SSH_SSH2_H
/* As per RFC 4253, Section 6.1, we MUST be able to handle a packet whose
* length is 35000 bytes; we SHOULD be able to handle larger packets. We
* impose a maximum size here to prevent overly-large packets from being
* used by attackers. The maximum size is a bit arbitrary.
*/
#define PROXY_SSH_MAX_PACKET_LEN (1024 * 256)
/* SSH2 message types */
#define PROXY_SSH_MSG_DISCONNECT 1
#define PROXY_SSH_MSG_IGNORE 2
#define PROXY_SSH_MSG_UNIMPLEMENTED 3
#define PROXY_SSH_MSG_DEBUG 4
#define PROXY_SSH_MSG_SERVICE_REQUEST 5
#define PROXY_SSH_MSG_SERVICE_ACCEPT 6
#define PROXY_SSH_MSG_EXT_INFO 7
#define PROXY_SSH_MSG_KEXINIT 20
#define PROXY_SSH_MSG_NEWKEYS 21
/* Key exchange message types */
#define PROXY_SSH_MSG_KEX_DH_INIT 30
#define PROXY_SSH_MSG_KEX_DH_REPLY 31
#define PROXY_SSH_MSG_KEX_DH_GEX_REQUEST_OLD 30
#define PROXY_SSH_MSG_KEX_DH_GEX_GROUP 31
#define PROXY_SSH_MSG_KEX_DH_GEX_INIT 32
#define PROXY_SSH_MSG_KEX_DH_GEX_REPLY 33
#define PROXY_SSH_MSG_KEX_DH_GEX_REQUEST 34
#define PROXY_SSH_MSG_KEXRSA_PUBKEY 30
#define PROXY_SSH_MSG_KEXRSA_SECRET 31
#define PROXY_SSH_MSG_KEXRSA_DONE 32
#define PROXY_SSH_MSG_KEX_ECDH_INIT 30
#define PROXY_SSH_MSG_KEX_ECDH_REPLY 31
/* User authentication message types */
#define PROXY_SSH_MSG_USER_AUTH_REQUEST 50
#define PROXY_SSH_MSG_USER_AUTH_FAILURE 51
#define PROXY_SSH_MSG_USER_AUTH_SUCCESS 52
#define PROXY_SSH_MSG_USER_AUTH_BANNER 53
#define PROXY_SSH_MSG_USER_AUTH_PUBKEY 60
#define PROXY_SSH_MSG_USER_AUTH_PK_OK 60
#define PROXY_SSH_MSG_USER_AUTH_PASSWD 60
#define PROXY_SSH_MSG_USER_AUTH_INFO_REQ 60
#define PROXY_SSH_MSG_USER_AUTH_INFO_RESP 61
/* Request types */
#define PROXY_SSH_MSG_GLOBAL_REQUEST 80
#define PROXY_SSH_MSG_REQUEST_SUCCESS 81
#define PROXY_SSH_MSG_REQUEST_FAILURE 82
/* Channel message types */
#define PROXY_SSH_MSG_CHANNEL_OPEN 90
#define PROXY_SSH_MSG_CHANNEL_OPEN_CONFIRMATION 91
#define PROXY_SSH_MSG_CHANNEL_OPEN_FAILURE 92
#define PROXY_SSH_MSG_CHANNEL_WINDOW_ADJUST 93
#define PROXY_SSH_MSG_CHANNEL_DATA 94
#define PROXY_SSH_MSG_CHANNEL_EXTENDED_DATA 95
#define PROXY_SSH_MSG_CHANNEL_EOF 96
#define PROXY_SSH_MSG_CHANNEL_CLOSE 97
#define PROXY_SSH_MSG_CHANNEL_REQUEST 98
#define PROXY_SSH_MSG_CHANNEL_SUCCESS 99
#define PROXY_SSH_MSG_CHANNEL_FAILURE 100
/* Channel extended data types */
#define PROXY_SSH_MSG_CHANNEL_EXTENDED_DATA_TYPE_STDERR 1
/* SSH Disconnect reason codes */
#define PROXY_SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT 1
#define PROXY_SSH_DISCONNECT_PROTOCOL_ERROR 2
#define PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED 3
#define PROXY_SSH_DISCONNECT_RESERVED 4
#define PROXY_SSH_DISCONNECT_MAC_ERROR 5
#define PROXY_SSH_DISCONNECT_COMPRESSION_ERROR 6
#define PROXY_SSH_DISCONNECT_SERVICE_NOT_AVAILABLE 7
#define PROXY_SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED 8
#define PROXY_SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE 9
#define PROXY_SSH_DISCONNECT_CONNECTION_LOST 10
#define PROXY_SSH_DISCONNECT_BY_APPLICATION 11
#define PROXY_SSH_DISCONNECT_TOO_MANY_CONNECTIONS 12
#define PROXY_SSH_DISCONNECT_AUTH_CANCELLED_BY_USER 13
#define PROXY_SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE 14
#define PROXY_SSH_DISCONNECT_ILLEGAL_USER_NAME 15
#define PROXY_SSH_ID_PREFIX "SSH-2.0-"
#define PROXY_SSH_ID_DEFAULT_STRING PROXY_SSH_ID_PREFIX MOD_PROXY_VERSION
#endif /* MOD_PROXY_SSH_SSH2_H */
proftpd-mod_proxy-0.9.5/include/proxy/ssh/umac.h 0000664 0000000 0000000 00000004220 14757370167 0021737 0 ustar 00root root 0000000 0000000 /* -----------------------------------------------------------------------
*
* umac.h -- C Implementation UMAC Message Authentication
*
* Version 0.93a of rfc4418.txt -- 2006 July 14
*
* For a full description of UMAC message authentication see the UMAC
* world-wide-web page at http://www.cs.ucdavis.edu/~rogaway/umac
* Please report bugs and suggestions to the UMAC webpage.
*
* Copyright (c) 1999-2004 Ted Krovetz
*
* Permission to use, copy, modify, and distribute this software and
* its documentation for any purpose and with or without fee, is hereby
* granted provided that the above copyright notice appears in all copies
* and in supporting documentation, and that the name of the copyright
* holder not be used in advertising or publicity pertaining to
* distribution of the software without specific, written prior permission.
*
* Comments should be directed to Ted Krovetz (tdk@acm.org)
*
* ---------------------------------------------------------------------- */
#ifndef MOD_PROXY_SSH_UMAC_H
#define MOD_PROXY_SSH_UMAC_H
struct umac_ctx *proxy_ssh_umac_alloc(void);
struct umac_ctx *proxy_ssh_umac_new(const unsigned char key[]);
void proxy_ssh_umac_init(struct umac_ctx *ctx, const unsigned char key[]);
int proxy_ssh_umac_reset(struct umac_ctx *ctx);
int proxy_ssh_umac_update(struct umac_ctx *ctx, const unsigned char *input,
long len);
int proxy_ssh_umac_final(struct umac_ctx *ctx, unsigned char tag[],
const unsigned char nonce[8]);
int proxy_ssh_umac_delete(struct umac_ctx *ctx);
struct umac_ctx *proxy_ssh_umac128_alloc(void);
struct umac_ctx *proxy_ssh_umac128_new(const unsigned char key[]);
void proxy_ssh_umac128_init(struct umac_ctx *ctx, const unsigned char key[]);
int proxy_ssh_umac128_reset(struct umac_ctx *ctx);
int proxy_ssh_umac128_update(struct umac_ctx *ctx, const unsigned char *input,
long len);
int proxy_ssh_umac128_final(struct umac_ctx *ctx, unsigned char tag[],
const unsigned char nonce[8]);
int proxy_ssh_umac128_delete(struct umac_ctx *ctx);
#endif /* MOD_PROXY_SSH_UMAC_H */
proftpd-mod_proxy-0.9.5/include/proxy/ssh/utf8.h 0000664 0000000 0000000 00000002775 14757370167 0021715 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH UTF8 API
* Copyright (c) 2021 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_SSH_UTF8_H
#define MOD_PROXY_SSH_UTF8_H
#if defined(PR_USE_OPENSSL)
char *proxy_ssh_utf8_decode_text(pool *p, const char *text);
char *proxy_ssh_utf8_encode_text(pool *p, const char *text);
/* Set the local charset to use explicitly, rather than relying on
* nl_langinfo(3).
*/
int proxy_ssh_utf8_set_charset(const char *charset);
int proxy_ssh_utf8_init(void);
int proxy_ssh_utf8_free(void);
#endif /* PR_USE_OPENSSL */
#endif /* MOD_PROXY_SSH_UTF8_H */
proftpd-mod_proxy-0.9.5/include/proxy/str.h 0000664 0000000 0000000 00000002303 14757370167 0021025 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy String API
* Copyright (c) 2020 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_STR_H
#define MOD_PROXY_STR_H
#include "mod_proxy.h"
char *proxy_strnstr(const char *s1, const char *s2, size_t len);
#endif /* MOD_PROXY_STR_H */
proftpd-mod_proxy-0.9.5/include/proxy/tls.h 0000664 0000000 0000000 00000010574 14757370167 0021030 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy TLS API
* Copyright (c) 2015-2024 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_TLS_H
#define MOD_PROXY_TLS_H
#include "mod_proxy.h"
#include "proxy/session.h"
#ifdef PR_USE_OPENSSL
# include
# include
# include
# include
# include
# include
# include
# include
# include
# if OPENSSL_VERSION_NUMBER > 0x000907000L
# if defined(PR_USE_OPENSSL_ENGINE)
# include
# endif /* PR_USE_OPENSSL_ENGINE */
# include
# endif
# ifdef PR_USE_OPENSSL_ECC
# include
# include
# endif /* PR_USE_OPENSSL_ECC */
#endif
/* ProxyTLSEngine values */
#define PROXY_TLS_ENGINE_ON 1
#define PROXY_TLS_ENGINE_OFF 2
#define PROXY_TLS_ENGINE_AUTO 3
#define PROXY_TLS_ENGINE_IMPLICIT 4
#define PROXY_TLS_ENGINE_MATCH_CLIENT 5
#define PROXY_TLS_IMPLICIT_FTPS_PORT 990
/* ProxyTLSOptions values. NOTE: Make sure these do NOT collide with existing
* PROXY_OPT_ values defined in mod_proxy.h.
*/
#define PROXY_TLS_OPT_ENABLE_DIAGS 0x0100
#define PROXY_TLS_OPT_NO_SESSION_CACHE 0x0200
#define PROXY_TLS_OPT_NO_SESSION_TICKETS 0x0400
#define PROXY_TLS_OPT_ALLOW_WEAK_SECURITY 0x0800
/* ProxyTLSProtocol handling */
#define PROXY_TLS_PROTO_SSL_V3 0x0001
#define PROXY_TLS_PROTO_TLS_V1 0x0002
#define PROXY_TLS_PROTO_TLS_V1_1 0x0004
#define PROXY_TLS_PROTO_TLS_V1_2 0x0008
#define PROXY_TLS_PROTO_TLS_V1_3 0x0010
#if defined(PR_USE_OPENSSL) && \
OPENSSL_VERSION_NUMBER >= 0x10001000L
# if defined(TLS1_3_VERSION)
# define PROXY_TLS_PROTO_DEFAULT (PROXY_TLS_PROTO_TLS_V1|PROXY_TLS_PROTO_TLS_V1_1|PROXY_TLS_PROTO_TLS_V1_2|PROXY_TLS_PROTO_TLS_V1_3)
# else
# define PROXY_TLS_PROTO_DEFAULT (PROXY_TLS_PROTO_TLS_V1|PROXY_TLS_PROTO_TLS_V1_1|PROXY_TLS_PROTO_TLS_V1_2)
# endif /* TLS1_3_VERSION */
#else
# define PROXY_TLS_PROTO_DEFAULT (PROXY_TLS_PROTO_TLS_V1)
#endif /* OpenSSL 1.0.1 or later */
/* This is used for e.g. "ProxyTLSProtocol ALL -SSLv3 ...". */
#define PROXY_TLS_PROTO_ALL (PROXY_TLS_PROTO_SSL_V3|PROXY_TLS_PROTO_TLS_V1|PROXY_TLS_PROTO_TLS_V1_1|PROXY_TLS_PROTO_TLS_V1_2|PROXY_TLS_PROTO_TLS_V1_3)
const char *proxy_tls_get_errors(void);
int proxy_tls_init(pool *p, const char *tables_dir, int flags);
int proxy_tls_free(pool *p);
int proxy_tls_sess_init(pool *p, struct proxy_session *proxy_sess, int flags);
int proxy_tls_sess_free(pool *p);
/* Set whether data transfers require TLS protection, based on e.g. clients'
* PROT commands.
*/
int proxy_tls_set_data_prot(int);
/* Programmatically set the ProxyTLSEngine value. */
int proxy_tls_set_tls(int);
/* Returns the ProxyTLSEngine value; see above. */
int proxy_tls_using_tls(void);
/* Implements the ProxyTLSEngine MatchClient functionality. */
int proxy_tls_match_client_tls(void);
/* Defines the datastore interface. */
struct proxy_tls_datastore {
#ifdef PR_USE_OPENSSL
int (*add_sess)(pool *p, void *dsh, const char *key, SSL_SESSION *sess);
int (*remove_sess)(pool *p, void *dsh, const char *key);
SSL_SESSION *(*get_sess)(pool *p, void *dsh, const char *key);
int (*count_sess)(pool *p, void *dsh);
#endif /* PR_USE_OPENSSL */
int (*init)(pool *p, const char *path, int flags);
void *(*open)(pool *p, const char *path, unsigned long opts);
int (*close)(pool *p, void *dsh);
/* Datastore handle returned by the open callback. */
void *dsh;
};
#endif /* MOD_PROXY_TLS_H */
proftpd-mod_proxy-0.9.5/include/proxy/tls/ 0000775 0000000 0000000 00000000000 14757370167 0020650 5 ustar 00root root 0000000 0000000 proftpd-mod_proxy-0.9.5/include/proxy/tls/db.h 0000664 0000000 0000000 00000002412 14757370167 0021405 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy TLS Database API
* Copyright (c) 2017 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_TLS_DB_H
#define MOD_PROXY_TLS_DB_H
#include "mod_proxy.h"
#include "proxy/tls.h"
int proxy_tls_db_as_datastore(struct proxy_tls_datastore *ds, void *ds_data,
size_t ds_datasz);
#endif /* MOD_PROXY_TLS_DB_H */
proftpd-mod_proxy-0.9.5/include/proxy/tls/redis.h 0000664 0000000 0000000 00000002423 14757370167 0022130 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy TLS Redis API
* Copyright (c) 2017 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_TLS_REDIS_H
#define MOD_PROXY_TLS_REDIS_H
#include "mod_proxy.h"
#include "proxy/tls.h"
int proxy_tls_redis_as_datastore(struct proxy_tls_datastore *ds, void *ds_data,
size_t ds_datasz);
#endif /* MOD_PROXY_TLS_REDIS_H */
proftpd-mod_proxy-0.9.5/include/proxy/uri.h 0000664 0000000 0000000 00000002407 14757370167 0021021 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy URI API
* Copyright (c) 2012-2016 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_URI_H
#define MOD_PROXY_URI_H
#include "mod_proxy.h"
int proxy_uri_parse(pool *p, const char *uri, char **scheme, char **host,
unsigned int *port, char **username, char **password);
#endif /* MOD_PROXY_URI_H */
proftpd-mod_proxy-0.9.5/install-sh 0000775 0000000 0000000 00000032464 14757370167 0017257 0 ustar 00root root 0000000 0000000 #!/bin/sh
# install - install a program, script, or datafile
scriptversion=2006-12-25.00
# This originates from X11R5 (mit/util/scripts/install.sh), which was
# later released in X11R6 (xc/config/util/install.sh) with the
# following copyright and license.
#
# Copyright (C) 1994 X Consortium
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC-
# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Except as contained in this notice, the name of the X Consortium shall not
# be used in advertising or otherwise to promote the sale, use or other deal-
# ings in this Software without prior written authorization from the X Consor-
# tium.
#
#
# FSF changes to this file are in the public domain.
#
# Calling this script install-sh is preferred over install.sh, to prevent
# `make' implicit rules from creating a file called install from it
# when there is no Makefile.
#
# This script is compatible with the BSD install script, but was written
# from scratch.
nl='
'
IFS=" "" $nl"
# set DOITPROG to echo to test this script
# Don't use :- since 4.3BSD and earlier shells don't like it.
doit=${DOITPROG-}
if test -z "$doit"; then
doit_exec=exec
else
doit_exec=$doit
fi
# Put in absolute file names if you don't have them in your path;
# or use environment vars.
chgrpprog=${CHGRPPROG-chgrp}
chmodprog=${CHMODPROG-chmod}
chownprog=${CHOWNPROG-chown}
cmpprog=${CMPPROG-cmp}
cpprog=${CPPROG-cp}
mkdirprog=${MKDIRPROG-mkdir}
mvprog=${MVPROG-mv}
rmprog=${RMPROG-rm}
stripprog=${STRIPPROG-strip}
posix_glob='?'
initialize_posix_glob='
test "$posix_glob" != "?" || {
if (set -f) 2>/dev/null; then
posix_glob=
else
posix_glob=:
fi
}
'
posix_mkdir=
# Desired mode of installed file.
mode=0755
chgrpcmd=
chmodcmd=$chmodprog
chowncmd=
mvcmd=$mvprog
rmcmd="$rmprog -f"
stripcmd=
src=
dst=
dir_arg=
dst_arg=
copy_on_change=false
no_target_directory=
usage="\
Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE
or: $0 [OPTION]... SRCFILES... DIRECTORY
or: $0 [OPTION]... -t DIRECTORY SRCFILES...
or: $0 [OPTION]... -d DIRECTORIES...
In the 1st form, copy SRCFILE to DSTFILE.
In the 2nd and 3rd, copy all SRCFILES to DIRECTORY.
In the 4th, create DIRECTORIES.
Options:
--help display this help and exit.
--version display version info and exit.
-c (ignored)
-C install only if different (preserve the last data modification time)
-d create directories instead of installing files.
-g GROUP $chgrpprog installed files to GROUP.
-m MODE $chmodprog installed files to MODE.
-o USER $chownprog installed files to USER.
-s $stripprog installed files.
-t DIRECTORY install into DIRECTORY.
-T report an error if DSTFILE is a directory.
Environment variables override the default commands:
CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG
RMPROG STRIPPROG
"
while test $# -ne 0; do
case $1 in
-c) ;;
-C) copy_on_change=true;;
-d) dir_arg=true;;
-g) chgrpcmd="$chgrpprog $2"
shift;;
--help) echo "$usage"; exit $?;;
-m) mode=$2
case $mode in
*' '* | *' '* | *'
'* | *'*'* | *'?'* | *'['*)
echo "$0: invalid mode: $mode" >&2
exit 1;;
esac
shift;;
-o) chowncmd="$chownprog $2"
shift;;
-s) stripcmd=$stripprog;;
-t) dst_arg=$2
shift;;
-T) no_target_directory=true;;
--version) echo "$0 $scriptversion"; exit $?;;
--) shift
break;;
-*) echo "$0: invalid option: $1" >&2
exit 1;;
*) break;;
esac
shift
done
if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then
# When -d is used, all remaining arguments are directories to create.
# When -t is used, the destination is already specified.
# Otherwise, the last argument is the destination. Remove it from $@.
for arg
do
if test -n "$dst_arg"; then
# $@ is not empty: it contains at least $arg.
set fnord "$@" "$dst_arg"
shift # fnord
fi
shift # arg
dst_arg=$arg
done
fi
if test $# -eq 0; then
if test -z "$dir_arg"; then
echo "$0: no input file specified." >&2
exit 1
fi
# It's OK to call `install-sh -d' without argument.
# This can happen when creating conditional directories.
exit 0
fi
if test -z "$dir_arg"; then
trap '(exit $?); exit' 1 2 13 15
# Set umask so as not to create temps with too-generous modes.
# However, 'strip' requires both read and write access to temps.
case $mode in
# Optimize common cases.
*644) cp_umask=133;;
*755) cp_umask=22;;
*[0-7])
if test -z "$stripcmd"; then
u_plus_rw=
else
u_plus_rw='% 200'
fi
cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;;
*)
if test -z "$stripcmd"; then
u_plus_rw=
else
u_plus_rw=,u+rw
fi
cp_umask=$mode$u_plus_rw;;
esac
fi
for src
do
# Protect names starting with `-'.
case $src in
-*) src=./$src;;
esac
if test -n "$dir_arg"; then
dst=$src
dstdir=$dst
test -d "$dstdir"
dstdir_status=$?
else
# Waiting for this to be detected by the "$cpprog $src $dsttmp" command
# might cause directories to be created, which would be especially bad
# if $src (and thus $dsttmp) contains '*'.
if test ! -f "$src" && test ! -d "$src"; then
echo "$0: $src does not exist." >&2
exit 1
fi
if test -z "$dst_arg"; then
echo "$0: no destination specified." >&2
exit 1
fi
dst=$dst_arg
# Protect names starting with `-'.
case $dst in
-*) dst=./$dst;;
esac
# If destination is a directory, append the input filename; won't work
# if double slashes aren't ignored.
if test -d "$dst"; then
if test -n "$no_target_directory"; then
echo "$0: $dst_arg: Is a directory" >&2
exit 1
fi
dstdir=$dst
dst=$dstdir/`basename "$src"`
dstdir_status=0
else
# Prefer dirname, but fall back on a substitute if dirname fails.
dstdir=`
(dirname "$dst") 2>/dev/null ||
expr X"$dst" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
X"$dst" : 'X\(//\)[^/]' \| \
X"$dst" : 'X\(//\)$' \| \
X"$dst" : 'X\(/\)' \| . 2>/dev/null ||
echo X"$dst" |
sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
s//\1/
q
}
/^X\(\/\/\)[^/].*/{
s//\1/
q
}
/^X\(\/\/\)$/{
s//\1/
q
}
/^X\(\/\).*/{
s//\1/
q
}
s/.*/./; q'
`
test -d "$dstdir"
dstdir_status=$?
fi
fi
obsolete_mkdir_used=false
if test $dstdir_status != 0; then
case $posix_mkdir in
'')
# Create intermediate dirs using mode 755 as modified by the umask.
# This is like FreeBSD 'install' as of 1997-10-28.
umask=`umask`
case $stripcmd.$umask in
# Optimize common cases.
*[2367][2367]) mkdir_umask=$umask;;
.*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;;
*[0-7])
mkdir_umask=`expr $umask + 22 \
- $umask % 100 % 40 + $umask % 20 \
- $umask % 10 % 4 + $umask % 2
`;;
*) mkdir_umask=$umask,go-w;;
esac
# With -d, create the new directory with the user-specified mode.
# Otherwise, rely on $mkdir_umask.
if test -n "$dir_arg"; then
mkdir_mode=-m$mode
else
mkdir_mode=
fi
posix_mkdir=false
case $umask in
*[123567][0-7][0-7])
# POSIX mkdir -p sets u+wx bits regardless of umask, which
# is incompatible with FreeBSD 'install' when (umask & 300) != 0.
;;
*)
tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$
trap 'ret=$?; rmdir "$tmpdir/d" "$tmpdir" 2>/dev/null; exit $ret' 0
if (umask $mkdir_umask &&
exec $mkdirprog $mkdir_mode -p -- "$tmpdir/d") >/dev/null 2>&1
then
if test -z "$dir_arg" || {
# Check for POSIX incompatibilities with -m.
# HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or
# other-writeable bit of parent directory when it shouldn't.
# FreeBSD 6.1 mkdir -m -p sets mode of existing directory.
ls_ld_tmpdir=`ls -ld "$tmpdir"`
case $ls_ld_tmpdir in
d????-?r-*) different_mode=700;;
d????-?--*) different_mode=755;;
*) false;;
esac &&
$mkdirprog -m$different_mode -p -- "$tmpdir" && {
ls_ld_tmpdir_1=`ls -ld "$tmpdir"`
test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1"
}
}
then posix_mkdir=:
fi
rmdir "$tmpdir/d" "$tmpdir"
else
# Remove any dirs left behind by ancient mkdir implementations.
rmdir ./$mkdir_mode ./-p ./-- 2>/dev/null
fi
trap '' 0;;
esac;;
esac
if
$posix_mkdir && (
umask $mkdir_umask &&
$doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir"
)
then :
else
# The umask is ridiculous, or mkdir does not conform to POSIX,
# or it failed possibly due to a race condition. Create the
# directory the slow way, step by step, checking for races as we go.
case $dstdir in
/*) prefix='/';;
-*) prefix='./';;
*) prefix='';;
esac
eval "$initialize_posix_glob"
oIFS=$IFS
IFS=/
$posix_glob set -f
set fnord $dstdir
shift
$posix_glob set +f
IFS=$oIFS
prefixes=
for d
do
test -z "$d" && continue
prefix=$prefix$d
if test -d "$prefix"; then
prefixes=
else
if $posix_mkdir; then
(umask=$mkdir_umask &&
$doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break
# Don't fail if two instances are running concurrently.
test -d "$prefix" || exit 1
else
case $prefix in
*\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;;
*) qprefix=$prefix;;
esac
prefixes="$prefixes '$qprefix'"
fi
fi
prefix=$prefix/
done
if test -n "$prefixes"; then
# Don't fail if two instances are running concurrently.
(umask $mkdir_umask &&
eval "\$doit_exec \$mkdirprog $prefixes") ||
test -d "$dstdir" || exit 1
obsolete_mkdir_used=true
fi
fi
fi
if test -n "$dir_arg"; then
{ test -z "$chowncmd" || $doit $chowncmd "$dst"; } &&
{ test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } &&
{ test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false ||
test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1
else
# Make a couple of temp file names in the proper directory.
dsttmp=$dstdir/_inst.$$_
rmtmp=$dstdir/_rm.$$_
# Trap to clean up those temp files at exit.
trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0
# Copy the file name to the temp name.
(umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") &&
# and set any options; do chmod last to preserve setuid bits.
#
# If any of these fail, we abort the whole thing. If we want to
# ignore errors from any of these, just make sure not to ignore
# errors from the above "$doit $cpprog $src $dsttmp" command.
#
{ test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } &&
{ test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } &&
{ test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } &&
{ test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } &&
# If -C, don't bother to copy if it wouldn't change the file.
if $copy_on_change &&
old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` &&
new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` &&
eval "$initialize_posix_glob" &&
$posix_glob set -f &&
set X $old && old=:$2:$4:$5:$6 &&
set X $new && new=:$2:$4:$5:$6 &&
$posix_glob set +f &&
test "$old" = "$new" &&
$cmpprog "$dst" "$dsttmp" >/dev/null 2>&1
then
rm -f "$dsttmp"
else
# Rename the file to the real destination.
$doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null ||
# The rename failed, perhaps because mv can't rename something else
# to itself, or perhaps because mv is so ancient that it does not
# support -f.
{
# Now remove or move aside any old file at destination location.
# We try this two ways since rm can't unlink itself on some
# systems and the destination file might be busy for other
# reasons. In this case, the final cleanup might fail but the new
# file should still install successfully.
{
test ! -f "$dst" ||
$doit $rmcmd -f "$dst" 2>/dev/null ||
{ $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null &&
{ $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; }
} ||
{ echo "$0: cannot unlink or rename $dst" >&2
(exit 1); exit 1
}
} &&
# Now rename the file to the real destination.
$doit $mvcmd "$dsttmp" "$dst"
}
fi || exit 1
trap '' 0
fi
done
# Local variables:
# eval: (add-hook 'write-file-hooks 'time-stamp)
# time-stamp-start: "scriptversion="
# time-stamp-format: "%:y-%02m-%02d.%02H"
# time-stamp-end: "$"
# End:
proftpd-mod_proxy-0.9.5/lib/ 0000775 0000000 0000000 00000000000 14757370167 0016010 5 ustar 00root root 0000000 0000000 proftpd-mod_proxy-0.9.5/lib/proxy/ 0000775 0000000 0000000 00000000000 14757370167 0017171 5 ustar 00root root 0000000 0000000 proftpd-mod_proxy-0.9.5/lib/proxy/conn.c 0000664 0000000 0000000 00000152054 14757370167 0020301 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy conn implementation
* Copyright (c) 2012-2025 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#ifdef HAVE_SYS_UIO_H
# include
#endif /* HAVE_SYS_UIO_H */
#include "proxy/conn.h"
#include "proxy/dns.h"
#include "proxy/netio.h"
#include "proxy/inet.h"
#include "proxy/session.h"
#include "proxy/tls.h"
#include "proxy/uri.h"
struct proxy_conn {
pool *pconn_pool;
const char *pconn_uri;
const char *pconn_proto;
const char *pconn_host;
const char *pconn_hostport;
int pconn_port;
int pconn_tls;
int pconn_use_dns_srv;
int pconn_use_dns_txt;
/* These are only used for DNS SRV, DNS TXT URLs. */
int pconn_dns_ttl;
int pconn_dns_timer_id;
/* Note that these are deliberately NOT 'const', so that they can be
* scrubbed in the per-session memory space, once backend authentication
* has occurred.
*/
char *pconn_username;
char *pconn_password;
const pr_netaddr_t *pconn_addr;
array_header *pconn_addrs;
};
static const char *supported_protocols[] = {
"ftp",
"ftp+srv",
"ftp+txt",
"ftps",
"ftps+srv",
"ftps+txt",
"sftp",
"sftp+srv",
"sftp+txt",
NULL
};
/* PROXY protocol V2 */
#define PROXY_PROTOCOL_V2_SIGLEN 12
#define PROXY_PROTOCOL_V2_HDRLEN 16
#define PROXY_PROTOCOL_V2_TRANSPORT_STREAM 0x01
#define PROXY_PROTOCOL_V2_FAMILY_INET 0x10
#define PROXY_PROTOCOL_V2_FAMILY_INET6 0x20
#define PROXY_PROTOCOL_V2_ADDRLEN_INET (4 + 4 + 2 + 2)
#define PROXY_PROTOCOL_V2_ADDRLEN_INET6 (16 + 16 + 2 + 2)
static uint8_t proxy_protocol_v2_sig[PROXY_PROTOCOL_V2_SIGLEN] = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A";
#define PROXY_PROTOCOL_V2_TLV_ALPN 0x01
#define PROXY_PROTOCOL_V2_TLV_AUTHORITY 0x02
#define PROXY_PROTOCOL_V2_TLV_UNIQUE_ID 0x05
#define PROXY_PROTOCOL_V2_TLV_TLS 0x20
#define PROXY_PROTOCOL_V2_TLV_TLS_VERSION 0x21
#define PROXY_PROTOCOL_V2_TLV_TLS_CN 0x22
#define PROXY_PROTOCOL_V2_TLV_TLS_CIPHER 0x23
#define PROXY_PROTOCOL_V2_TLV_TLS_SIG_ALGO 0x24
#define PROXY_PROTOCOL_V2_TLV_TLS_KEY_ALGO 0x25
static const char *trace_channel = "proxy.conn";
static int supported_protocol(const char *proto) {
register unsigned int i;
for (i = 0; supported_protocols[i] != NULL; i++) {
if (strcmp(proto, supported_protocols[i]) == 0) {
return 0;
}
}
errno = ENOENT;
return -1;
}
int proxy_conn_connect_timeout_cb(CALLBACK_FRAME) {
const struct proxy_session *proxy_sess;
const pr_netaddr_t *server_addr;
proxy_sess = pr_table_get(session.notes, "mod_proxy.proxy-session", NULL);
server_addr = pr_table_get(session.notes, "mod_proxy.proxy-connect-address",
NULL);
if (proxy_sess == NULL ||
server_addr == NULL) {
/* Do not restart the timer. */
return 0;
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"timed out connecting to %s:%d after %d %s",
pr_netaddr_get_ipstr(server_addr), ntohs(pr_netaddr_get_port(server_addr)),
proxy_sess->connect_timeout,
proxy_sess->connect_timeout != 1 ? "seconds" : "second");
pr_event_generate("mod_proxy.timeout-connect", NULL);
#if 0
/* XXX We might not want to disconnect the frontend client here, right? */
pr_log_pri(PR_LOG_NOTICE, "%s", "Connect timed out, disconnected");
pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_TIMEOUT,
"ProxyTimeoutConnect");
#endif
/* Do not restart the timer. */
return 0;
}
static struct proxy_conn *proxy_conn_get_addrs(pool *p, const char *uri,
struct proxy_conn *pconn) {
pr_netaddr_t *pconn_addr;
pconn_addr = (pr_netaddr_t *) pr_netaddr_get_addr(pconn->pconn_pool,
pconn->pconn_host, &(pconn->pconn_addrs));
if (pconn_addr == NULL) {
pr_trace_msg(trace_channel, 2, "unable to resolve '%s' from URI '%s': %s",
pconn->pconn_host, uri, strerror(errno));
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to resolve '%s' from URI '%s'", pconn->pconn_host, uri);
errno = EINVAL;
return NULL;
}
if (pr_netaddr_set_port2(pconn_addr, pconn->pconn_port) < 0) {
int xerrno = errno;
pr_trace_msg(trace_channel, 3,
"unable to set port %d from URI '%s': %s", pconn->pconn_port, uri,
strerror(xerrno));
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to set port %d from URI '%s': %s", pconn->pconn_port, uri,
strerror(xerrno));
errno = EINVAL;
return NULL;
}
pconn->pconn_addr = pconn_addr;
if (pconn->pconn_addrs != NULL) {
register unsigned int i;
pr_netaddr_t **elts;
elts = pconn->pconn_addrs->elts;
for (i = 0; i < pconn->pconn_addrs->nelts; i++) {
pr_netaddr_t *elt;
elt = elts[i];
if (pr_netaddr_set_port2(elt, pconn->pconn_port) < 0) {
pr_trace_msg(trace_channel, 3,
"unable to set port %d from URI '%s': %s", pconn->pconn_port, uri,
strerror(errno));
}
}
}
return pconn;
}
static struct proxy_conn *proxy_conn_use_dns_srv_addrs(pool *p, const char *uri,
struct proxy_conn *pconn, unsigned int flags) {
int res;
const char *name;
proxy_dns_type_e dns_type = PROXY_DNS_SRV;
array_header *resp = NULL;
uint32_t srv_ttl = 0;
name = pconn->pconn_host;
res = proxy_dns_resolve(pconn->pconn_pool, name, dns_type, &resp, &srv_ttl);
if (res > 0) {
pr_netaddr_t **elts, *first_addr;
elts = resp->elts;
/* Slightly naughty way to pop the first address of the array. */
first_addr = elts[0];
resp->elts = &(elts[1]);
resp->nelts--;
pconn->pconn_addr = first_addr;
pconn->pconn_port = ntohs(pr_netaddr_get_port(first_addr));
pconn->pconn_addrs = resp;
pconn->pconn_dns_ttl = (int) srv_ttl;
if (flags & PROXY_CONN_CREATE_FL_USE_DNS_TTL) {
/* XXX TODO: Schedule timer for re-resolving URL on TTL.
*
* The existing Timer API does not provide room for custom "user data"
* pointers; need to fix that. In the mean time, we'll just need to track
* things ourselves with a lookup table: timer ID -> pconn.
*
* This has the advantage of providing a way to iterate through the table,
* removing all timer IDs (then destroying the table) in a session
* process.
*
* What memory pool should be used for this table, that would be available
* at startup time? proxy_pool?
*
* pconn->pconn_dns_timer_id = pr_timer_add(pconn->pconn_dns_ttl, -1,
* &proxy_module, proxy_conn_resolve_cb, ...);
*/
}
return pconn;
}
/* Always fall back to normal name resolution. */
return proxy_conn_get_addrs(p, uri, pconn);
}
static struct proxy_conn *proxy_conn_use_dns_txt_addrs(pool *p, const char *uri,
struct proxy_conn *pconn, unsigned int flags) {
int res;
const char *name;
proxy_dns_type_e dns_type = PROXY_DNS_TXT;
array_header *resp = NULL;
name = pconn->pconn_host;
res = proxy_dns_resolve(p, name, dns_type, &resp, NULL);
if (res > 0) {
register unsigned int i;
const char **elts;
elts = resp->elts;
for (i = 0; i < resp->nelts; i++) {
const char *elt;
char *scheme, *host;
unsigned int port;
int str_flags = PR_STR_FL_IGNORE_CASE;
struct proxy_conn *elt_pconn;
elt = elts[i];
/* Many domains have multiple TXT records, for SPF, domain validation,
* etc. So we are only interested in any TXT records are that valid
* (to us) URLs.
*/
res = proxy_uri_parse(p, elt, &scheme, &host, &port, NULL, NULL);
if (res < 0) {
pr_trace_msg(trace_channel, 19,
"skipping non-URL TXT record '%s' discovered for '%s'", elt, uri);
continue;
}
/* If the URL found in a TXT record itself uses a DNS SRV or TXT
* variant, skip it. That way lies circular madness.
*/
if (pr_strnrstr(scheme, 0, "+srv", 0, str_flags) == TRUE ||
pr_strnrstr(scheme, 0, "+txt", 0, str_flags) == TRUE) {
pr_trace_msg(trace_channel, 19,
"skipping URL TXT record '%s' discovered for '%s'", elt, uri);
continue;
}
elt_pconn = (struct proxy_conn *) proxy_conn_create(p, elt, 0);
if (elt_pconn != NULL) {
destroy_pool(pconn->pconn_pool);
return elt_pconn;
}
}
}
/* Always fall back to normal name resolution. */
return proxy_conn_get_addrs(p, uri, pconn);
}
const struct proxy_conn *proxy_conn_create(pool *p, const char *uri,
unsigned int flags) {
int res, xerrno;
int use_dns_srv = FALSE, use_dns_txt = FALSE, use_tls = PROXY_TLS_ENGINE_AUTO;
char *ptr = NULL;
char hostport[512], *proto, *remote_host, *username = NULL, *password = NULL;
unsigned int remote_port;
struct proxy_conn *pconn, *pconn2;
pool *pconn_pool;
if (p == NULL ||
uri == NULL) {
errno = EINVAL;
return NULL;
}
res = proxy_uri_parse(p, uri, &proto, &remote_host, &remote_port, &username,
&password);
if (res < 0) {
return NULL;
}
if (supported_protocol(proto) < 0) {
pr_trace_msg(trace_channel, 4, "unsupported protocol '%s' in URI '%.100s'",
proto, uri);
errno = EPERM;
return NULL;
}
if (strcmp(proto, "ftps") == 0 ||
strncmp(proto, "ftps+", 5) == 0) {
/* If the 'ftps' scheme is used, then FTPS is REQUIRED for connections
* to this server.
*/
use_tls = PROXY_TLS_ENGINE_ON;
/* We automatically (and only) use implicit FTPS for port 990. Note that
* we do NOT support implicit FTPS for URLs using DNS SRV, TXT.
*/
if (strcmp(proto, "ftps") == 0 &&
remote_port == PROXY_TLS_IMPLICIT_FTPS_PORT) {
use_tls = PROXY_TLS_ENGINE_IMPLICIT;
}
} else if (strcmp(proto, "sftp") == 0 ||
strncmp(proto, "sftp+", 5) == 0) {
/* As might be obvious, do not try to use TLS against an SSH2/SFTP
* server.
*/
use_tls = PROXY_TLS_ENGINE_OFF;
}
if (pr_strnrstr(proto, 0, "+srv", 0, PR_STR_FL_IGNORE_CASE) == TRUE) {
use_dns_srv = TRUE;
}
if (pr_strnrstr(proto, 0, "+txt", 0, PR_STR_FL_IGNORE_CASE) == TRUE) {
use_dns_txt = TRUE;
}
memset(hostport, '\0', sizeof(hostport));
snprintf(hostport, sizeof(hostport)-1, "%s:%u", remote_host, remote_port);
pconn_pool = pr_pool_create_sz(p, 128);
pr_pool_tag(pconn_pool, "proxy connection pool");
pconn = pcalloc(pconn_pool, sizeof(struct proxy_conn));
pconn->pconn_pool = pconn_pool;
pconn->pconn_host = pstrdup(pconn_pool, remote_host);
pconn->pconn_port = remote_port;
pconn->pconn_hostport = pstrdup(pconn_pool, hostport);
pconn->pconn_uri = pstrdup(pconn_pool, uri);
pconn->pconn_tls = use_tls;
pconn->pconn_use_dns_srv = use_dns_srv;
pconn->pconn_use_dns_txt = use_dns_txt;
/* Adjust the proto (scheme, actually) to account for possible DNS SRV,
* TXT usage.
*/
ptr = strchr(proto, '+');
if (ptr != NULL) {
pconn->pconn_proto = pstrndup(pconn_pool, proto, ptr - proto);
} else {
pconn->pconn_proto = pstrdup(pconn_pool, proto);
}
if (username != NULL) {
pconn->pconn_username = pstrdup(pconn_pool, username);
}
if (password != NULL) {
pconn->pconn_password = pstrdup(pconn_pool, password);
}
/* Here is where we discover the addresses for this URI. We might use
* DNS SRV, DNS TXT, or normal DNS A/AAAA records.
*/
if (use_dns_srv == TRUE ||
use_dns_txt == TRUE) {
pr_trace_msg(trace_channel, 5,
"ignoring port %u from URI '%.100s' since port will be discovered "
"from %s DNS records", remote_port, uri, use_dns_srv ? "SRV" : "TXT");
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"ignoring port %u from URI '%.100s' since port will be discovered "
"from %s DNS records", remote_port, uri, use_dns_srv ? "SRV" : "TXT");
}
if (use_dns_srv == TRUE) {
pconn2 = proxy_conn_use_dns_srv_addrs(p, uri, pconn, flags);
xerrno = errno;
} else if (use_dns_txt == TRUE) {
pconn2 = proxy_conn_use_dns_txt_addrs(p, uri, pconn, flags);
xerrno = errno;
} else {
pconn2 = proxy_conn_get_addrs(p, uri, pconn);
xerrno = errno;
}
if (pconn2 == NULL) {
destroy_pool(pconn->pconn_pool);
errno = xerrno;
return NULL;
}
return pconn2;
}
void proxy_conn_free(const struct proxy_conn *pconn) {
if (pconn == NULL) {
return;
}
destroy_pool(pconn->pconn_pool);
}
const pr_netaddr_t *proxy_conn_get_addr(const struct proxy_conn *pconn,
array_header **addrs) {
if (pconn == NULL) {
errno = EINVAL;
return NULL;
}
if (addrs != NULL) {
*addrs = pconn->pconn_addrs;
}
return pconn->pconn_addr;
}
int proxy_conn_get_dns_ttl(const struct proxy_conn *pconn) {
if (pconn == NULL) {
errno = EINVAL;
return -1;
}
/* We really only care about/honor DNS TTLs for the DNS SRV. */
if (pconn->pconn_use_dns_srv == FALSE) {
errno = EPERM;
return -1;
}
if (pconn->pconn_dns_ttl <= 0) {
errno = ENOENT;
return -1;
}
return pconn->pconn_dns_ttl;
}
const char *proxy_conn_get_host(const struct proxy_conn *pconn) {
if (pconn == NULL) {
errno = EINVAL;
return NULL;
}
return pconn->pconn_host;
}
const char *proxy_conn_get_hostport(const struct proxy_conn *pconn) {
if (pconn == NULL) {
errno = EINVAL;
return NULL;
}
return pconn->pconn_hostport;
}
int proxy_conn_get_port(const struct proxy_conn *pconn) {
if (pconn == NULL) {
errno = EINVAL;
return -1;
}
return pconn->pconn_port;
}
void proxy_conn_clear_username(const struct proxy_conn *pconn) {
size_t len;
struct proxy_conn *conn;
if (pconn == NULL) {
return;
}
if (pconn->pconn_username == NULL) {
return;
}
len = strlen(pconn->pconn_username);
conn = (struct proxy_conn *) pconn;
pr_memscrub(conn->pconn_username, len);
conn->pconn_username = NULL;
}
const char *proxy_conn_get_username(const struct proxy_conn *pconn) {
if (pconn == NULL) {
errno = EINVAL;
return NULL;
}
return pconn->pconn_username;
}
void proxy_conn_clear_password(const struct proxy_conn *pconn) {
size_t len;
struct proxy_conn *conn;
if (pconn == NULL) {
return;
}
if (pconn->pconn_password == NULL) {
return;
}
len = strlen(pconn->pconn_password);
conn = (struct proxy_conn *) pconn;
pr_memscrub(conn->pconn_password, len);
conn->pconn_password = NULL;
}
const char *proxy_conn_get_password(const struct proxy_conn *pconn) {
if (pconn == NULL) {
errno = EINVAL;
return NULL;
}
return pconn->pconn_password;
}
int proxy_conn_get_tls(const struct proxy_conn *pconn) {
if (pconn == NULL) {
errno = EINVAL;
return -1;
}
return pconn->pconn_tls;
}
int proxy_conn_use_dns_srv(const struct proxy_conn *pconn) {
if (pconn == NULL) {
errno = EINVAL;
return -1;
}
return pconn->pconn_use_dns_srv;
}
int proxy_conn_use_dns_txt(const struct proxy_conn *pconn) {
if (pconn == NULL) {
errno = EINVAL;
return -1;
}
return pconn->pconn_use_dns_txt;
}
/* Borrowed from proftpd/src/netaddr.c. */
static int addr_ncmp(const unsigned char *aptr, const unsigned char *bptr,
unsigned int masklen) {
unsigned char nbits, nbytes;
int res;
nbytes = masklen / 8;
nbits = masklen % 8;
res = memcmp(aptr, bptr, nbytes);
if (res != 0) {
return -1;
}
if (nbits > 0) {
unsigned char abyte, bbyte, mask;
abyte = aptr[nbytes];
bbyte = bptr[nbytes];
mask = (0xff << (8 - nbits)) & 0xff;
if ((abyte & mask) > (bbyte & mask)) {
return 1;
}
if ((abyte & mask) < (bbyte & mask)) {
return -1;
}
}
return 0;
}
static int is_127_xxx_addr(uint32_t addrno) {
uint32_t rfc1918_addrno;
rfc1918_addrno = htonl(0x7f000000);
return addr_ncmp((const unsigned char *) &addrno,
(const unsigned char *) &rfc1918_addrno, 8);
}
static int netaddr_is_private(const pr_netaddr_t *addr) {
if (pr_netaddr_is_rfc1918(addr) == TRUE) {
return TRUE;
}
switch (pr_netaddr_get_family(addr)) {
case AF_INET: {
uint32_t addrno;
addrno = pr_netaddr_get_addrno(addr);
if (is_127_xxx_addr(addrno) == 0) {
return TRUE;
}
break;
}
#if defined(PR_USE_IPV6)
case AF_INET6:
if (pr_netaddr_is_v4mappedv6(addr) == TRUE) {
pool *tmp_pool;
pr_netaddr_t *v4addr;
int res;
tmp_pool = make_sub_pool(proxy_pool);
v4addr = pr_netaddr_v6tov4(tmp_pool, addr);
res = netaddr_is_private(v4addr);
destroy_pool(tmp_pool);
return res;
}
break;
#endif /* PR_USE_IPV6 */
}
return FALSE;
}
conn_t *proxy_conn_get_server_conn(pool *p, struct proxy_session *proxy_sess,
const pr_netaddr_t *remote_addr) {
const pr_netaddr_t *bind_addr = NULL, *local_addr = NULL;
const char *remote_ipstr = NULL;
unsigned int remote_port;
conn_t *server_conn, *ctrl_conn;
int res, default_inet_family = 0, xerrno;
if (proxy_sess->connect_timeout > 0) {
const char *notes_key = "mod_proxy.proxy-connect-address";
proxy_sess->connect_timerno = pr_timer_add(proxy_sess->connect_timeout,
-1, &proxy_module, proxy_conn_connect_timeout_cb, "ProxyTimeoutConnect");
(void) pr_table_remove(session.notes, notes_key, NULL);
if (pr_table_add(session.notes, notes_key, remote_addr,
sizeof(pr_netaddr_t)) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error stashing proxy connect address note: %s", strerror(errno));
}
}
remote_ipstr = pr_netaddr_get_ipstr(remote_addr);
remote_port = ntohs(pr_netaddr_get_port(remote_addr));
/* Check the family of the retrieved address vs what we'll be using
* to connect. If there's a mismatch, we need to get an addr with the
* matching family.
*/
if (pr_netaddr_get_family(session.c->local_addr) == pr_netaddr_get_family(remote_addr)) {
local_addr = session.c->local_addr;
} else {
/* In this scenario, the proxy has an IPv6 socket, but the remote/backend
* server has an IPv4 (or IPv4-mapped IPv6) address. OR it's the proxy
* which has an IPv4 socket, and the remote/backend server has an IPv6
* address.
*/
if (pr_netaddr_get_family(session.c->local_addr) == AF_INET) {
char *ip_str;
/* Convert the local address from an IPv4 to an IPv6 addr. */
ip_str = pcalloc(p, INET6_ADDRSTRLEN + 1);
snprintf(ip_str, INET6_ADDRSTRLEN, "::ffff:%s",
pr_netaddr_get_ipstr(session.c->local_addr));
local_addr = pr_netaddr_get_addr(p, ip_str, NULL);
} else {
local_addr = pr_netaddr_v6tov4(p, session.c->local_addr);
if (local_addr == NULL) {
pr_trace_msg(trace_channel, 4,
"error converting IPv6 local address %s to IPv4 address: %s",
pr_netaddr_get_ipstr(session.c->local_addr), strerror(errno));
if (proxy_sess->src_addr == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"local address '%s' is an IPv6 address, and remote address "
"'%s' is an IPv4 address; consider using ProxySourceAddress "
"directive to configure an IPv4 address",
pr_netaddr_get_ipstr(session.c->local_addr),
pr_netaddr_get_ipstr(remote_addr));
}
} else {
pr_trace_msg(trace_channel, 9,
"converted IPv6 local address %s to IPv4 address %s",
pr_netaddr_get_ipstr(session.c->local_addr),
pr_netaddr_get_ipstr(local_addr));
}
}
if (local_addr == NULL) {
local_addr = session.c->local_addr;
}
}
bind_addr = proxy_sess->src_addr;
/* We need to set the default inet family to use for the local address of
* our socket. We do NOT want to just use the family of the local address of
* our control connection, since we could be listening on an IPv6 address
* and want to connect to a backend IPv4 address, or vice versa; see
* Issue #272.
*/
if (bind_addr == NULL) {
int remote_family;
remote_family = pr_netaddr_get_family(remote_addr);
pr_trace_msg(trace_channel, 9, "using %s family for socket local address",
remote_family == AF_INET ? "IPv4" : "IPv6");
default_inet_family = pr_inet_set_default_family(p, remote_family);
}
/* Note: IF mod_proxy is running on localhost, and the connection to be
* made is to a public IP address, then this connect(2) attempt would most
* likely fail with ENETUNREACH, since localhost is a loopback network,
* and of course not reachable from a public IP. Thus we check for this
* edge case (which happens often for development).
*/
if (bind_addr != NULL &&
pr_netaddr_is_loopback(bind_addr) == TRUE &&
pr_netaddr_is_loopback(remote_addr) != TRUE) {
const char *local_name;
const pr_netaddr_t *new_local_addr;
local_name = pr_netaddr_get_localaddr_str(p);
new_local_addr = pr_netaddr_get_addr(p, local_name, NULL);
if (new_local_addr != NULL) {
int local_family, remote_family;
/* We need to make sure our local address family matches that
* of the remote address.
*/
local_family = pr_netaddr_get_family(new_local_addr);
remote_family = pr_netaddr_get_family(remote_addr);
if (local_family != remote_family) {
pr_netaddr_t *new_addr = NULL;
#if defined(PR_USE_IPV6)
if (local_family == AF_INET) {
new_addr = pr_netaddr_v4tov6(p, new_local_addr);
} else {
new_addr = pr_netaddr_v6tov4(p, new_local_addr);
}
#endif /* PR_USE_IPV6 */
if (new_addr != NULL) {
new_local_addr = new_addr;
}
}
pr_trace_msg(trace_channel, 14,
"%s is a loopback address, and unable to reach %s; using %s instead",
pr_netaddr_get_ipstr(bind_addr), remote_ipstr,
pr_netaddr_get_ipstr(new_local_addr));
bind_addr = new_local_addr;
}
}
server_conn = pr_inet_create_conn(p, -1, bind_addr, INPORT_ANY, FALSE);
xerrno = errno;
/* Restore the previous default inet family if necessary. */
if (bind_addr == NULL) {
(void) pr_inet_set_default_family(p, default_inet_family);
}
if (server_conn == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error creating connection to %s: %s", pr_netaddr_get_ipstr(bind_addr),
strerror(xerrno));
pr_timer_remove(proxy_sess->connect_timerno, &proxy_module);
errno = xerrno;
return NULL;
}
pr_trace_msg(trace_channel, 12,
"connecting to backend address %s#%u from %s#%u", remote_ipstr, remote_port,
pr_netaddr_get_ipstr(server_conn->local_addr), server_conn->local_port);
res = pr_inet_connect_nowait(p, server_conn, remote_addr,
ntohs(pr_netaddr_get_port(remote_addr)));
if (res < 0) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error starting connect to %s#%u: %s", remote_ipstr, remote_port,
strerror(xerrno));
/* If there is a mismatch in public/private addresses for our
* source/destination addresses, it might cause a connection error. Check
* for those particular errors, and if so, log a suggestion to explicitly
* configure an appropriate ProxySourceAddress (Issue #213).
*/
if (pr_netaddr_get_family(bind_addr) == pr_netaddr_get_family(remote_addr)) {
if (netaddr_is_private(bind_addr) == TRUE) {
if (netaddr_is_private(remote_addr) != TRUE) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"local address '%s' is a private network address, and remote "
"address '%s' is a public address; consider using "
"ProxySourceAddress directive to configure a public local address",
pr_netaddr_get_ipstr(bind_addr), remote_ipstr);
}
} else {
if (netaddr_is_private(remote_addr) == TRUE) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"local address '%s' is a public address, and remote address '%s' "
"is a private network address; consider using ProxySourceAddress "
"directive to configure a private local address",
pr_netaddr_get_ipstr(bind_addr), remote_ipstr);
}
}
}
pr_timer_remove(proxy_sess->connect_timerno, &proxy_module);
errno = xerrno;
return NULL;
}
if (res == 0) {
pr_netio_stream_t *nstrm;
int connected = FALSE, nstrm_mode = PR_NETIO_IO_RD, use_tls;
if ((proxy_opts & PROXY_OPT_USE_PROXY_PROTOCOL_V1) ||
(proxy_opts & PROXY_OPT_USE_PROXY_PROTOCOL_V2)) {
/* Rather than waiting for the stream to be readable (because the
* other end sent us something), wait for the stream to be writable
* so that we can send something to the other end).
*/
nstrm_mode = PR_NETIO_IO_WR;
}
use_tls = proxy_tls_using_tls();
if (use_tls == PROXY_TLS_ENGINE_IMPLICIT) {
/* For implicit FTPS connections, we will be initiating the TLS
* handshake, and thus we need to wait for the stream to be writable.
*/
nstrm_mode = PR_NETIO_IO_WR;
}
/* Not yet connected. */
nstrm = proxy_netio_open(p, PR_NETIO_STRM_OTHR, server_conn->listen_fd,
nstrm_mode);
if (nstrm == NULL) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error opening stream to %s#%u: %s", remote_ipstr, remote_port,
strerror(xerrno));
pr_timer_remove(proxy_sess->connect_timerno, &proxy_module);
pr_inet_close(p, server_conn);
errno = xerrno;
return NULL;
}
proxy_netio_set_poll_interval(nstrm, 1);
while (connected == FALSE) {
int polled;
pr_signals_handle();
polled = proxy_netio_poll(nstrm);
switch (polled) {
case 1: {
/* Aborted, timed out. Note that we shouldn't reach here. */
xerrno = ETIMEDOUT;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error connecting to %s#%u: %s", remote_ipstr, remote_port,
strerror(xerrno));
pr_timer_remove(proxy_sess->connect_timerno, &proxy_module);
proxy_netio_close(nstrm);
pr_inet_close(p, server_conn);
errno = xerrno;
return NULL;
}
case -1: {
/* Error */
xerrno = nstrm->strm_errno;
if (xerrno == 0) {
xerrno = errno;
}
if (xerrno == EINTR) {
/* Treat this as a timeout. */
xerrno = ETIMEDOUT;
} else if (xerrno == EOF) {
xerrno = ECONNREFUSED;
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error connecting to %s#%u: %s", remote_ipstr, remote_port,
strerror(xerrno));
pr_timer_remove(proxy_sess->connect_timerno, &proxy_module);
proxy_netio_close(nstrm);
pr_inet_close(p, server_conn);
errno = xerrno;
return NULL;
}
default: {
/* Connected */
server_conn->mode = CM_OPEN;
pr_timer_remove(proxy_sess->connect_timerno, &proxy_module);
pr_table_remove(session.notes, "mod_proxy.proxy-connect-addr", NULL);
res = pr_inet_get_conn_info(server_conn, server_conn->listen_fd);
if (res < 0) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error obtaining local socket info on fd %d: %s",
server_conn->listen_fd, strerror(xerrno));
proxy_netio_close(nstrm);
pr_inet_close(p, server_conn);
errno = xerrno;
return NULL;
}
proxy_netio_reset_poll_interval(nstrm);
connected = TRUE;
break;
}
}
}
}
pr_trace_msg(trace_channel, 5,
"successfully connected to %s#%u from %s#%d", remote_ipstr, remote_port,
pr_netaddr_get_ipstr(server_conn->local_addr),
ntohs(pr_netaddr_get_port(server_conn->local_addr)));
ctrl_conn = proxy_inet_openrw(p, server_conn, NULL, PR_NETIO_STRM_CTRL, -1,
-1, -1, FALSE);
if (ctrl_conn == NULL) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to open control connection to %s#%u: %s", remote_ipstr,
remote_port, strerror(xerrno));
pr_inet_close(p, server_conn);
errno = xerrno;
return NULL;
}
/* Remember that pr_inet_openrw() makes a copy of the input connection;
* we thus do not need server_conn now.
*/
pr_inet_close(p, server_conn);
pr_pool_tag(ctrl_conn->pool, "proxy backend ctrl conn pool");
return ctrl_conn;
}
const char *proxy_conn_get_uri(const struct proxy_conn *pconn) {
if (pconn == NULL) {
errno = EINVAL;
return NULL;
}
return pconn->pconn_uri;
}
int proxy_conn_send_proxy_v1(pool *p, conn_t *conn) {
int res, src_port, dst_port;
const char *proto, *src_ipstr, *dst_ipstr;
pool *sub_pool = NULL;
if (p == NULL ||
conn == NULL) {
errno = EINVAL;
return -1;
}
/* "PROXY" "TCP4"|"TCP6"|"UNKNOWN"
* session.c->remote_addr session.c->local_addr
* session.c->remote_port, session.c->local_port "\r\n"
*/
if (pr_netaddr_get_family(session.c->remote_addr) == AF_INET &&
pr_netaddr_get_family(session.c->local_addr) == AF_INET) {
proto = "TCP4";
src_ipstr = pr_netaddr_get_ipstr(session.c->remote_addr);
src_port = session.c->remote_port;
dst_ipstr = pr_netaddr_get_ipstr(session.c->local_addr);
dst_port = session.c->local_port;
} else {
proto = "TCP6";
sub_pool = make_sub_pool(p);
if (pr_netaddr_get_family(session.c->remote_addr) == AF_INET) {
const char *ipstr;
ipstr = pr_netaddr_get_ipstr(session.c->remote_addr);
src_ipstr = pstrcat(sub_pool, "::ffff:", ipstr, NULL);
} else {
src_ipstr = pr_netaddr_get_ipstr(session.c->remote_addr);
}
src_port = session.c->remote_port;
if (pr_netaddr_get_family(session.c->local_addr) == AF_INET) {
const char *ipstr;
ipstr = pr_netaddr_get_ipstr(session.c->local_addr);
dst_ipstr = pstrcat(sub_pool, "::ffff:", ipstr, NULL);
} else {
dst_ipstr = pr_netaddr_get_ipstr(session.c->local_addr);
}
dst_port = session.c->local_port;
/* What should we do if the entire frontend connection is IPv6, but the
* backend server is IPv4? Sending "PROXY TCP6" there may not work as
* expected, e.g. the backend server may not want to handle IPv6 addresses
* (even though it does not have to); should that be handled using
* "PROXY UNKNOWN"?
*/
if (pr_netaddr_get_family(conn->remote_addr) == AF_INET) {
proto = "UNKNOWN";
pr_trace_msg(trace_channel, 9,
"client address '%s' and local address '%s' are both IPv6, "
"but backend address '%s' is IPv4, using '%s' proto", src_ipstr,
dst_ipstr, pr_netaddr_get_ipstr(conn->remote_addr), proto);
}
}
pr_trace_msg(trace_channel, 9,
"sending PROXY protocol V1 message: 'PROXY %s %s %s %d %d' to backend",
proto, src_ipstr, dst_ipstr, src_port, dst_port);
res = proxy_netio_printf(conn->outstrm, "PROXY %s %s %s %d %d\r\n",
proto, src_ipstr, dst_ipstr, src_port, dst_port);
if (sub_pool != NULL) {
destroy_pool(sub_pool);
}
return res;
}
static int writev_conn(conn_t *conn, const struct iovec *iov, int iov_count) {
int res, xerrno;
if (pr_netio_poll(conn->outstrm) < 0) {
return -1;
}
res = writev(conn->wfd, iov, iov_count);
xerrno = errno;
while (res <= 0) {
if (res < 0) {
if (xerrno == EINTR) {
pr_signals_handle();
if (pr_netio_poll(conn->outstrm) < 0) {
return -1;
}
res = writev(conn->wfd, iov, iov_count);
xerrno = errno;
continue;
}
pr_trace_msg(trace_channel, 16,
"error writing to client (fd %d): %s", conn->wfd, strerror(xerrno));
errno = errno;
return -1;
}
}
session.total_raw_out += res;
return res;
}
static const char *get_v2_tlv_alpn(pool *p) {
const char *alpn = NULL;
/* Note that in a proxy chain, we will want to also honor
* and preserve any ALPN TLV sent via PROXY protocol.
*/
alpn = pr_table_get(session.notes, "mod_proxy_protocol.alpn", NULL);
if (alpn == NULL) {
alpn = pstrdup(p, pr_session_get_protocol(0));
}
pr_trace_msg(trace_channel, 22, "adding ALPN V2 TLV: '%s'", alpn);
return alpn;
}
static uint16_t add_v2_tlv_alpn(pool *p, struct iovec *v2_iov,
unsigned int *v2_niov) {
uint8_t *tlv_type;
uint16_t *tlv_len, total_len;
const char *tlv_val;
size_t tlv_valsz = 0;
unsigned int niov;
tlv_type = pcalloc(p, sizeof(uint8_t));
*tlv_type = PROXY_PROTOCOL_V2_TLV_ALPN;
tlv_val = get_v2_tlv_alpn(p);
tlv_valsz = strlen(tlv_val);
tlv_len = pcalloc(p, sizeof(uint16_t));
*tlv_len = htons(tlv_valsz);
niov = *v2_niov;
v2_iov[niov].iov_base = (void *) tlv_type;
v2_iov[niov].iov_len = sizeof(uint8_t);
niov++;
v2_iov[niov].iov_base = (void *) tlv_len;
v2_iov[niov].iov_len = sizeof(uint16_t);
niov++;
v2_iov[niov].iov_base = (void *) tlv_val;
v2_iov[niov].iov_len = tlv_valsz;
/* Make sure to increment niov one more, for the next TLV. */
*v2_niov = niov + 1;
total_len = sizeof(uint8_t) + sizeof(uint16_t) + tlv_valsz;
return total_len;
}
static const void *get_v2_tlv_authority(pool *p) {
const void *authority = NULL;
/* Add the Authority TLV if the client sent an FTP HOST command, or
* used TLS SNI. Note that in a proxy chain, we will want to also honor
* and preserve any Authority TLV sent via PROXY protocol.
*/
authority = pr_table_get(session.notes, "mod_proxy_protocol.authority", NULL);
if (authority == NULL) {
authority = pr_table_get(session.notes, "mod_core.host", NULL);
}
if (authority == NULL) {
authority = pr_table_get(session.notes, "mod_tls.sni", NULL);
}
return authority;
}
static uint16_t add_v2_tlv_authority(pool *p, struct iovec *v2_iov,
unsigned int *v2_niov) {
uint8_t *tlv_type;
uint16_t *tlv_len, total_len;
const char *tlv_val;
size_t tlv_valsz = 0;
unsigned int niov;
const void *val = NULL;
val = get_v2_tlv_authority(p);
if (val == NULL) {
return 0;
}
pr_trace_msg(trace_channel, 22, "adding Authority V2 TLV: '%s'",
(const char *) val);
tlv_type = pcalloc(p, sizeof(uint8_t));
*tlv_type = PROXY_PROTOCOL_V2_TLV_AUTHORITY;
tlv_val = pstrdup(p, val);
tlv_valsz = strlen(tlv_val);
tlv_len = pcalloc(p, sizeof(uint16_t));
*tlv_len = htons(tlv_valsz);
niov = *v2_niov;
v2_iov[niov].iov_base = (void *) tlv_type;
v2_iov[niov].iov_len = sizeof(uint8_t);
niov++;
v2_iov[niov].iov_base = (void *) tlv_len;
v2_iov[niov].iov_len = sizeof(uint16_t);
niov++;
v2_iov[niov].iov_base = (void *) tlv_val;
v2_iov[niov].iov_len = tlv_valsz;
/* Make sure to increment niov one more, for the next TLV. */
*v2_niov = niov + 1;
total_len = sizeof(uint8_t) + sizeof(uint16_t) + tlv_valsz;
return total_len;
}
static const char *get_v2_tlv_tls_version(pool *p) {
const char *tls_version = NULL;
tls_version = pr_table_get(session.notes, "mod_proxy_protocol.tls.version",
NULL);
if (tls_version == NULL) {
tls_version = pr_table_get(session.notes, "TLS_PROTOCOL", NULL);
}
if (tls_version != NULL) {
pr_trace_msg(trace_channel, 22, "adding TLS V2 TLV: TLS version '%s'",
tls_version);
}
return tls_version;
}
static const char *get_v2_tlv_tls_common_name(pool *p) {
const char *tls_common_name = NULL;
tls_common_name = pr_table_get(session.notes,
"mod_proxy_protoocl.tls.common-name", NULL);
if (tls_common_name == NULL) {
tls_common_name = pr_table_get(session.notes, "TLS_CLIENT_S_DN_CN", NULL);
}
if (tls_common_name != NULL) {
pr_trace_msg(trace_channel, 22, "adding TLS V2 TLV: TLS Common Name '%s'",
tls_common_name);
}
return tls_common_name;
}
static const char *get_v2_tlv_tls_cipher(pool *p) {
const char *tls_cipher = NULL;
tls_cipher = pr_table_get(session.notes, "mod_proxy_protocol.tls.cipher",
NULL);
if (tls_cipher == NULL) {
tls_cipher = pr_table_get(session.notes, "TLS_CIPHER", NULL);
}
if (tls_cipher != NULL) {
pr_trace_msg(trace_channel, 22, "adding TLS V2 TLV: TLS Cipher '%s'",
tls_cipher);
}
return tls_cipher;
}
static const char *get_v2_tlv_tls_sig_algo(pool *p) {
const char *tls_sig_algo = NULL;
tls_sig_algo = pr_table_get(session.notes,
"mod_proxy_protocol.tls.signature-algo", NULL);
if (tls_sig_algo == NULL) {
tls_sig_algo = pr_table_get(session.notes, "TLS_CLIENT_A_SIG", NULL);
}
if (tls_sig_algo != NULL) {
pr_trace_msg(trace_channel, 22,
"adding TLS V2 TLV: TLS Signature Algorithm '%s'", tls_sig_algo);
}
return tls_sig_algo;
}
static const char *get_v2_tlv_tls_key_algo(pool *p) {
const char *tls_key_algo = NULL;
tls_key_algo = pr_table_get(session.notes, "mod_proxy_protocol.tls.key-algo",
NULL);
if (tls_key_algo == NULL) {
tls_key_algo = pr_table_get(session.notes, "TLS_CLIENT_A_KEY", NULL);
}
if (tls_key_algo != NULL) {
pr_trace_msg(trace_channel, 22,
"adding TLS V2 TLV: TLS Key Algorithm '%s'", tls_key_algo);
}
return tls_key_algo;
}
static uint16_t add_v2_tlv_tls(pool *p, struct iovec *v2_iov,
unsigned int *v2_niov) {
uint8_t *tlv_type, client = 0;
uint16_t *tlv_len, total_len;
uint32_t verify;
char *tlv_ptr;
void *tlv_val;
size_t tlv_valsz = 0, valsz = 0;
unsigned int niov;
const char *tls_version, *tls_common_name, *tls_cipher, *tls_sig_algo, *tls_key_algo;
/* Even if FTPS is not in use right now for us, the original client may
* have used FTPS at the sort of a proxy chain. Thus we'll add any TLS
* TLVs, if they happen to be present.
*/
tlv_type = pcalloc(p, sizeof(uint8_t));
*tlv_type = PROXY_PROTOCOL_V2_TLV_TLS;
/* This is more complicated, due to the nested nature of TLS sub-TLVs. */
tls_version = get_v2_tlv_tls_version(p);
tls_common_name = get_v2_tlv_tls_common_name(p);
tls_cipher = get_v2_tlv_tls_cipher(p);
tls_sig_algo = get_v2_tlv_tls_sig_algo(p);
tls_key_algo = get_v2_tlv_tls_key_algo(p);
valsz = sizeof(client) + sizeof(verify);
/* If any of these TLS settings is present, then we set client to 1 to
* indicate that TLS was in fact used.
*/
if (tls_version != NULL) {
client = 0x01;
valsz += (sizeof(uint8_t) + sizeof(uint16_t) + strlen(tls_version));
}
if (tls_common_name != NULL) {
client = 0x01;
valsz += (sizeof(uint8_t) + sizeof(uint16_t) + strlen(tls_common_name));
}
if (tls_cipher != NULL) {
client = 0x01;
valsz += (sizeof(uint8_t) + sizeof(uint16_t) + strlen(tls_cipher));
}
if (tls_sig_algo != NULL) {
client = 0x01;
valsz += (sizeof(uint8_t) + sizeof(uint16_t) + strlen(tls_sig_algo));
}
if (tls_key_algo != NULL) {
client = 0x01;
valsz += (sizeof(uint8_t) + sizeof(uint16_t) + strlen(tls_key_algo));
}
tlv_ptr = tlv_val = pcalloc(p, valsz);
tlv_valsz = valsz;
/* Client field: 0x01 to indicate TLS was used, 0x02 when a client cert
* was also presented.
*/
if (tls_common_name != NULL) {
client = 0x02;
}
memcpy(tlv_ptr, &client, sizeof(client));
tlv_ptr += sizeof(client);
/* Verify field; 0 if a client cert was presented, otherwise non-zero. */
verify = htonl(1);
if (tls_common_name != NULL) {
verify = 0;
}
memcpy(tlv_ptr, &verify, sizeof(verify));
tlv_ptr += sizeof(verify);
if (tls_version != NULL) {
uint8_t tlv_subtype;
uint16_t tlv_sublen;
size_t tlv_subvalsz;
tlv_subtype = PROXY_PROTOCOL_V2_TLV_TLS_VERSION;
tlv_subvalsz = strlen(tls_version);
tlv_sublen = htons(tlv_subvalsz);
memcpy(tlv_ptr, &tlv_subtype, sizeof(tlv_subtype));
tlv_ptr += sizeof(tlv_subtype);
memcpy(tlv_ptr, &tlv_sublen, sizeof(tlv_sublen));
tlv_ptr += sizeof(tlv_sublen);
memcpy(tlv_ptr, tls_version, tlv_subvalsz);
tlv_ptr += tlv_subvalsz;
}
if (tls_common_name != NULL) {
uint8_t tlv_subtype;
uint16_t tlv_sublen;
size_t tlv_subvalsz;
tlv_subtype = PROXY_PROTOCOL_V2_TLV_TLS_CN;
tlv_subvalsz = strlen(tls_common_name);
tlv_sublen = htons(tlv_subvalsz);
memcpy(tlv_ptr, &tlv_subtype, sizeof(tlv_subtype));
tlv_ptr += sizeof(tlv_subtype);
memcpy(tlv_ptr, &tlv_sublen, sizeof(tlv_sublen));
tlv_ptr += sizeof(tlv_sublen);
memcpy(tlv_ptr, tls_common_name, tlv_subvalsz);
tlv_ptr += tlv_subvalsz;
}
if (tls_cipher != NULL) {
uint8_t tlv_subtype;
uint16_t tlv_sublen;
size_t tlv_subvalsz;
tlv_subtype = PROXY_PROTOCOL_V2_TLV_TLS_CIPHER;
tlv_subvalsz = strlen(tls_cipher);
tlv_sublen = htons(tlv_subvalsz);
memcpy(tlv_ptr, &tlv_subtype, sizeof(tlv_subtype));
tlv_ptr += sizeof(tlv_subtype);
memcpy(tlv_ptr, &tlv_sublen, sizeof(tlv_sublen));
tlv_ptr += sizeof(tlv_sublen);
memcpy(tlv_ptr, tls_cipher, tlv_subvalsz);
tlv_ptr += tlv_subvalsz;
}
if (tls_sig_algo != NULL) {
uint8_t tlv_subtype;
uint16_t tlv_sublen;
size_t tlv_subvalsz;
tlv_subtype = PROXY_PROTOCOL_V2_TLV_TLS_SIG_ALGO;
tlv_subvalsz = strlen(tls_sig_algo);
tlv_sublen = htons(tlv_subvalsz);
memcpy(tlv_ptr, &tlv_subtype, sizeof(tlv_subtype));
tlv_ptr += sizeof(tlv_subtype);
memcpy(tlv_ptr, &tlv_sublen, sizeof(tlv_sublen));
tlv_ptr += sizeof(tlv_sublen);
memcpy(tlv_ptr, tls_sig_algo, tlv_subvalsz);
tlv_ptr += tlv_subvalsz;
}
if (tls_key_algo != NULL) {
uint8_t tlv_subtype;
uint16_t tlv_sublen;
size_t tlv_subvalsz;
tlv_subtype = PROXY_PROTOCOL_V2_TLV_TLS_KEY_ALGO;
tlv_subvalsz = strlen(tls_sig_algo);
tlv_sublen = htons(tlv_subvalsz);
memcpy(tlv_ptr, &tlv_subtype, sizeof(tlv_subtype));
tlv_ptr += sizeof(tlv_subtype);
memcpy(tlv_ptr, &tlv_sublen, sizeof(tlv_sublen));
tlv_ptr += sizeof(tlv_sublen);
memcpy(tlv_ptr, tls_key_algo, tlv_subvalsz);
tlv_ptr += tlv_subvalsz;
}
tlv_len = pcalloc(p, sizeof(uint16_t));
*tlv_len = htons(tlv_valsz);
niov = *v2_niov;
v2_iov[niov].iov_base = (void *) tlv_type;
v2_iov[niov].iov_len = sizeof(uint8_t);
niov++;
v2_iov[niov].iov_base = (void *) tlv_len;
v2_iov[niov].iov_len = sizeof(uint16_t);
niov++;
v2_iov[niov].iov_base = (void *) tlv_val;
v2_iov[niov].iov_len = tlv_valsz;
/* Make sure to increment niov one more, for the next TLV. */
*v2_niov = niov + 1;
total_len = sizeof(uint8_t) + sizeof(uint16_t) + tlv_valsz;
return total_len;
}
static const char *get_v2_tlv_unique_id(pool *p) {
const char *unique_id = NULL;
/* Only add the Unique ID TLV if mod_unique_id generated one.
* Note that in a proxy chain, we will want to also honor
* and preserve any Unique ID TLV sent via PROXY protocol.
*/
unique_id = pr_table_get(session.notes, "mod_proxy_protocol.unique-id", NULL);
if (unique_id == NULL) {
const void *val;
val = pr_table_get(session.notes, "UNIQUE_ID", NULL);
if (val == NULL) {
return NULL;
}
unique_id = pstrdup(p, val);
}
return unique_id;
}
static uint16_t add_v2_tlv_unique_id(pool *p, struct iovec *v2_iov,
unsigned int *v2_niov) {
uint8_t *tlv_type;
uint16_t *tlv_len, total_len;
const char *tlv_val;
size_t tlv_valsz = 0;
unsigned int niov;
tlv_val = get_v2_tlv_unique_id(p);
if (tlv_val == NULL) {
return 0;
}
pr_trace_msg(trace_channel, 22, "adding Unique ID V2 TLV: '%s'",
(const char *) tlv_val);
tlv_type = pcalloc(p, sizeof(uint8_t));
*tlv_type = PROXY_PROTOCOL_V2_TLV_UNIQUE_ID;
tlv_valsz = strlen(tlv_val);
tlv_len = pcalloc(p, sizeof(uint16_t));
*tlv_len = htons(tlv_valsz);
niov = *v2_niov;
v2_iov[niov].iov_base = (void *) tlv_type;
v2_iov[niov].iov_len = sizeof(uint8_t);
niov++;
v2_iov[niov].iov_base = (void *) tlv_len;
v2_iov[niov].iov_len = sizeof(uint16_t);
niov++;
v2_iov[niov].iov_base = (void *) tlv_val;
v2_iov[niov].iov_len = tlv_valsz;
/* Make sure to increment niov one more, for the next TLV. */
*v2_niov = niov + 1;
total_len = sizeof(uint8_t) + sizeof(uint16_t) + tlv_valsz;
return total_len;
}
static uint16_t add_v2_tlv_aws(pool *p, struct iovec *v2_iov,
unsigned int *v2_niov) {
uint8_t *tlv_type, tlv_subtype;
uint16_t *tlv_len, tlv_sublen, total_len;
char *tlv_ptr;
void *tlv_val;
const void *tlv_subval;
size_t tlv_valsz = 0, tlv_subvalsz = 0, valsz = 0;
unsigned int niov;
tlv_subval = pr_table_get(session.notes,
"mod_proxy_protocol.aws.vpc-endpoint-id", &tlv_subvalsz);
if (tlv_subval == NULL) {
return 0;
}
pr_trace_msg(trace_channel, 22, "adding AWS V2 TLV: VPC Endpoint ID '%s'",
(const char *) tlv_subval);
/* mod_proxy_protocol treats its notes as NUL-terminated strings, but the
* AWS TLVs may not actually be strings. So subtract one for the NUL that
* mod_proxy_protocol adds.
*/
tlv_subvalsz -= 1;
tlv_type = pcalloc(p, sizeof(uint8_t));
/* AWS custom type for its TLVs. */
*tlv_type = 0xEA;
/* This is more complicated, due to the nested nature of sub-TLVs. */
valsz = (sizeof(uint8_t) + sizeof(uint16_t) + tlv_subvalsz);
tlv_ptr = tlv_val = pcalloc(p, valsz);
tlv_valsz = valsz;
/* VPC Endpoint ID */
tlv_subtype = 0x01;
tlv_sublen = htons(tlv_subvalsz);
memcpy(tlv_ptr, &tlv_subtype, sizeof(tlv_subtype));
tlv_ptr += sizeof(tlv_subtype);
memcpy(tlv_ptr, &tlv_sublen, sizeof(tlv_sublen));
tlv_ptr += sizeof(tlv_sublen);
memcpy(tlv_ptr, tlv_subval, tlv_subvalsz);
tlv_ptr += tlv_subvalsz;
tlv_len = pcalloc(p, sizeof(uint16_t));
*tlv_len = htons(tlv_valsz);
niov = *v2_niov;
v2_iov[niov].iov_base = (void *) tlv_type;
v2_iov[niov].iov_len = sizeof(uint8_t);
niov++;
v2_iov[niov].iov_base = (void *) tlv_len;
v2_iov[niov].iov_len = sizeof(uint16_t);
niov++;
v2_iov[niov].iov_base = (void *) tlv_val;
v2_iov[niov].iov_len = tlv_valsz;
/* Make sure to increment niov one more, for the next TLV. */
*v2_niov = niov + 1;
total_len = sizeof(uint8_t) + sizeof(uint16_t) + tlv_valsz;
return total_len;
}
static int parse_ul(const char *text, uint32_t *num) {
char *endp = NULL;
unsigned long res;
res = strtoul(text, &endp, 10);
if (endp && *endp) {
errno = EINVAL;
return -1;
}
*num = (uint32_t) res;
return 0;
}
static uint16_t add_v2_tlv_azure(pool *p, struct iovec *v2_iov,
unsigned int *v2_niov) {
uint8_t *tlv_type, tlv_subtype;
uint16_t *tlv_len, tlv_sublen, total_len;
char *tlv_ptr;
void *tlv_val;
const void *tlv_subval;
size_t tlv_valsz = 0, tlv_subvalsz = 0, valsz = 0;
unsigned int niov;
uint32_t link_id;
tlv_subval = pr_table_get(session.notes,
"mod_proxy_protocol.azure.private-endpoint-linkid", &tlv_subvalsz);
if (tlv_subval == NULL) {
return 0;
}
/* Per Azure docs, the linkid is a uint32_t value, little-endian. But
* mod_proxy_protocol wants to store session notes as strings, so it does an
* snprintf("%u") on the value. We, of course, want to encode it on the
* wire per the docs. So we need to change the text back to the uint32_t
* value.
*/
if (parse_ul(tlv_subval, &link_id) < 0) {
return 0;
}
pr_trace_msg(trace_channel, 22,
"adding Azure V2 TLV: Private Endpoint Link ID %lu",
(unsigned long) link_id);
tlv_subval = &link_id;
tlv_subvalsz = 4;
tlv_type = pcalloc(p, sizeof(uint8_t));
/* Azure custom type for its TLVs. */
*tlv_type = 0xEE;
/* This is more complicated, due to the nested nature of sub-TLVs. */
valsz = (sizeof(uint8_t) + sizeof(uint16_t) + tlv_subvalsz);
tlv_ptr = tlv_val = pcalloc(p, valsz);
tlv_valsz = valsz;
/* Private Endpoint LinkID */
tlv_subtype = 0x01;
tlv_sublen = htons(tlv_subvalsz);
memcpy(tlv_ptr, &tlv_subtype, sizeof(tlv_subtype));
tlv_ptr += sizeof(tlv_subtype);
memcpy(tlv_ptr, &tlv_sublen, sizeof(tlv_sublen));
tlv_ptr += sizeof(tlv_sublen);
memcpy(tlv_ptr, tlv_subval, tlv_subvalsz);
tlv_ptr += tlv_subvalsz;
tlv_len = pcalloc(p, sizeof(uint16_t));
*tlv_len = htons(tlv_valsz);
niov = *v2_niov;
v2_iov[niov].iov_base = (void *) tlv_type;
v2_iov[niov].iov_len = sizeof(uint8_t);
niov++;
v2_iov[niov].iov_base = (void *) tlv_len;
v2_iov[niov].iov_len = sizeof(uint16_t);
niov++;
v2_iov[niov].iov_base = (void *) tlv_val;
v2_iov[niov].iov_len = tlv_valsz;
/* Make sure to increment niov one more, for the next TLV. */
*v2_niov = niov + 1;
total_len = sizeof(uint8_t) + sizeof(uint16_t) + tlv_valsz;
return total_len;
}
static uint16_t add_v2_tlv_other(pool *p, struct iovec *v2_iov,
unsigned int *v2_niov) {
uint16_t len, total_len = 0;
len = add_v2_tlv_aws(p, v2_iov, v2_niov);
total_len += len;
len = add_v2_tlv_azure(p, v2_iov, v2_niov);
total_len += len;
return total_len;
}
int proxy_conn_send_proxy_v2(pool *p, conn_t *conn) {
int res, xerrno;
uint8_t ver_cmd, trans_fam, src_ipv6[16], dst_ipv6[16];
uint16_t v2_len, src_port, dst_port;
uint32_t src_ipv4, dst_ipv4;
struct iovec v2_iov[32];
unsigned int v2_niov = 8;
pool *sub_pool = NULL, *tlv_pool = NULL;
char *proto;
const pr_netaddr_t *src_addr = NULL, *dst_addr = NULL;
if (p == NULL ||
conn == NULL) {
errno = EINVAL;
return -1;
}
v2_iov[0].iov_base = (void *) proxy_protocol_v2_sig;
v2_iov[0].iov_len = PROXY_PROTOCOL_V2_SIGLEN;
/* PROXY protocol v2 + PROXY command */
ver_cmd = (0x20|0x01);
v2_iov[1].iov_base = (void *) &ver_cmd;
v2_iov[1].iov_len = sizeof(ver_cmd);
src_addr = session.c->remote_addr;
dst_addr = session.c->local_addr;
if (pr_netaddr_get_family(src_addr) == AF_INET &&
pr_netaddr_get_family(dst_addr) == AF_INET) {
struct sockaddr_in *saddr;
proto = "TCP/IPv4";
trans_fam = (PROXY_PROTOCOL_V2_TRANSPORT_STREAM|PROXY_PROTOCOL_V2_FAMILY_INET);
v2_len = PROXY_PROTOCOL_V2_ADDRLEN_INET;
saddr = (struct sockaddr_in *) pr_netaddr_get_sockaddr(src_addr);
src_ipv4 = saddr->sin_addr.s_addr;
v2_iov[4].iov_base = (void *) &src_ipv4;
v2_iov[4].iov_len = sizeof(src_ipv4);
saddr = (struct sockaddr_in *) pr_netaddr_get_sockaddr(dst_addr);
dst_ipv4 = saddr->sin_addr.s_addr;
v2_iov[5].iov_base = (void *) &dst_ipv4;
v2_iov[5].iov_len = sizeof(dst_ipv4);
/* Quell compiler warnings about unused variables. */
(void) src_ipv6;
(void) dst_ipv6;
} else {
struct sockaddr_in6 *saddr;
proto = "TCP/IPv6";
trans_fam = (PROXY_PROTOCOL_V2_TRANSPORT_STREAM|PROXY_PROTOCOL_V2_FAMILY_INET6);
v2_len = PROXY_PROTOCOL_V2_ADDRLEN_INET6;
sub_pool = make_sub_pool(p);
if (pr_netaddr_get_family(src_addr) == AF_INET) {
src_addr = pr_netaddr_v4tov6(sub_pool, src_addr);
}
saddr = (struct sockaddr_in6 *) pr_netaddr_get_sockaddr(src_addr);
memcpy(&src_ipv6, &(saddr->sin6_addr), sizeof(src_ipv6));
v2_iov[4].iov_base = (void *) &src_ipv6;
v2_iov[4].iov_len = sizeof(src_ipv6);
if (pr_netaddr_get_family(dst_addr) == AF_INET) {
dst_addr = pr_netaddr_v4tov6(sub_pool, dst_addr);
}
saddr = (struct sockaddr_in6 *) pr_netaddr_get_sockaddr(dst_addr);
memcpy(&dst_ipv6, &(saddr->sin6_addr), sizeof(dst_ipv6));
v2_iov[5].iov_base = (void *) &dst_ipv6;
v2_iov[5].iov_len = sizeof(dst_ipv6);
/* Quell compiler warnings about unused variables. */
(void) src_ipv4;
(void) dst_ipv4;
}
v2_iov[2].iov_base = (void *) &trans_fam;
v2_iov[2].iov_len = sizeof(trans_fam);
if (proxy_opts & PROXY_OPT_USE_PROXY_PROTOCOL_V2_TLVS) {
uint16_t tlv_len;
tlv_pool = make_sub_pool(p);
tlv_len = add_v2_tlv_alpn(tlv_pool, v2_iov, &v2_niov);
if (tlv_len > 0) {
v2_len += tlv_len;
}
tlv_len = add_v2_tlv_authority(tlv_pool, v2_iov, &v2_niov);
if (tlv_len > 0) {
v2_len += tlv_len;
}
tlv_len = add_v2_tlv_tls(tlv_pool, v2_iov, &v2_niov);
if (tlv_len > 0) {
v2_len += tlv_len;
}
tlv_len = add_v2_tlv_unique_id(tlv_pool, v2_iov, &v2_niov);
if (tlv_len > 0) {
v2_len += tlv_len;
}
/* Make sure we propagate any of the other custom TLVs, such as
* for AWS or Azure.
*/
tlv_len = add_v2_tlv_other(tlv_pool, v2_iov, &v2_niov);
if (tlv_len > 0) {
v2_len += tlv_len;
}
}
v2_len = htons(v2_len);
v2_iov[3].iov_base = (void *) &v2_len;
v2_iov[3].iov_len = sizeof(v2_len);
src_port = htons(session.c->remote_port);
v2_iov[6].iov_base = (void *) &src_port;
v2_iov[6].iov_len = sizeof(src_port);
dst_port = htons(session.c->local_port);
v2_iov[7].iov_base = (void *) &dst_port;
v2_iov[7].iov_len = sizeof(dst_port);
pr_trace_msg(trace_channel, 9,
"sending PROXY protocol V2 message for %s %s#%u %s#%u to backend",
proto, pr_netaddr_get_ipstr(src_addr), (unsigned int) ntohs(src_port),
pr_netaddr_get_ipstr(dst_addr), (unsigned int) ntohs(dst_port));
res = writev_conn(conn, v2_iov, v2_niov);
xerrno = errno;
if (sub_pool != NULL) {
destroy_pool(sub_pool);
}
if (tlv_pool != NULL) {
destroy_pool(tlv_pool);
}
errno = xerrno;
return res;
}
proftpd-mod_proxy-0.9.5/lib/proxy/db.c 0000664 0000000 0000000 00000071226 14757370167 0017732 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy database implementation
* Copyright (c) 2015-2024 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "proxy/db.h"
#include
struct proxy_dbh {
pool *pool;
sqlite3 *db;
const char *schema;
pr_table_t *prepared_stmts;
};
static const char *current_schema = NULL;
static const char *trace_channel = "proxy.db";
#define PROXY_DB_SQLITE_MAX_RETRY_COUNT 20
#define PROXY_DB_SQLITE_MAX_RETRY_DELAY_MS 100
#define PROXY_DB_SQLITE_TRACE_LEVEL 17
static int db_busy(void *user_data, int busy_count) {
int retry = FALSE;
/* How many retries do we want to allow? */
if (busy_count <= PROXY_DB_SQLITE_MAX_RETRY_COUNT) {
retry = TRUE;
}
if (current_schema != NULL) {
pr_trace_msg(trace_channel, 1,
"(sqlite3): schema '%s': busy count = %d, retry = %s", current_schema,
busy_count, retry ? "true" : "false");
} else {
pr_trace_msg(trace_channel, 1, "(sqlite3): busy count = %d, retry = %s",
busy_count, retry ? "true" : "false");
}
/* If we're busy, then sleep for a short while, on the assumption that the
* other process will finish its business with our tables.
*/
(void) pr_timer_usleep(PROXY_DB_SQLITE_MAX_RETRY_DELAY_MS * 1000);
return retry;
}
#if defined(SQLITE_CONFIG_LOG)
static void db_err(void *user_data, int err_code, const char *err_msg) {
if (current_schema != NULL) {
pr_trace_msg(trace_channel, 1, "(sqlite3): schema '%s': [error %d] %s",
current_schema, err_code, err_msg);
} else {
pr_trace_msg(trace_channel, 1, "(sqlite3): [error %d] %s", err_code,
err_msg);
}
}
#endif /* SQLITE_CONFIG_LOG */
#if defined(SQLITE_CONFIG_SQLLOG)
static void db_sql(void *user_data, sqlite3 *db, const char *info,
int event_type) {
switch (event_type) {
case 0:
/* Opening database. */
pr_trace_msg(trace_channel, 1, "(sqlite3): opened database: %s", info);
break;
case 1:
if (current_schema != NULL) {
pr_trace_msg(trace_channel, 1,
"(sqlite3): schema '%s': executed statement: %s", current_schema,
info);
} else {
pr_trace_msg(trace_channel, 1, "(sqlite3): executed statement: %s",
info);
}
break;
case 2:
/* Closing database. */
pr_trace_msg(trace_channel, 1, "(sqlite3): closed database: %s",
sqlite3_db_filename(db, "main"));
break;
}
}
#endif /* SQLITE_CONFIG_SQLLOG */
#if defined(HAVE_SQLITE3_TRACE_V2)
static int db_trace2(unsigned int trace_type, void *user_data, void *ptr,
void *ptr_data) {
const char *schema_name;
schema_name = user_data;
switch (trace_type) {
case SQLITE_TRACE_STMT: {
const char *stmt;
stmt = ptr_data;
if (schema_name == NULL) {
pr_trace_msg(trace_channel, PROXY_DB_SQLITE_TRACE_LEVEL,
"(sqlite3): executing stmt '%s'", stmt);
} else {
pr_trace_msg(trace_channel, PROXY_DB_SQLITE_TRACE_LEVEL,
"(sqlite3): schema '%s': executing stmt '%s'", schema_name, stmt);
}
break;
}
case SQLITE_TRACE_PROFILE: {
sqlite3_stmt *pstmt;
int64_t ns = 0;
char *expanded_sql = NULL, *orig_sql = NULL;
pstmt = ptr;
ns = *((int64_t *) ptr_data);
orig_sql = expanded_sql = sqlite3_expanded_sql(pstmt);
/* There are some SQL statements whose values we do NOT want to log.
* Thus we have a hacky way to look for them. Sigh.
*/
if (expanded_sql != NULL &&
strstr(expanded_sql, "SSL SESSION PARAMETERS") != NULL) {
expanded_sql = "(full SQL statement redacted)";
}
if (schema_name == NULL) {
pr_trace_msg(trace_channel, PROXY_DB_SQLITE_TRACE_LEVEL,
"(sqlite3): stmt '%s' ran for %lu nanosecs", expanded_sql,
(unsigned long) ns);
} else {
pr_trace_msg(trace_channel, PROXY_DB_SQLITE_TRACE_LEVEL,
"(sqlite3): schema '%s': stmt '%s' ran for %lu nanosecs", schema_name,
expanded_sql, (unsigned long) ns);
}
sqlite3_free(orig_sql);
break;
}
case SQLITE_TRACE_ROW: {
sqlite3_stmt *pstmt;
char *expanded_sql = NULL, *orig_sql = NULL;
pstmt = ptr;
orig_sql = expanded_sql = sqlite3_expanded_sql(pstmt);
/* There are some SQL statements whose values we do NOT want to log.
* Thus we have a hacky way to look for them. Sigh.
*/
if (expanded_sql != NULL &&
strstr(expanded_sql, "SSL SESSION PARAMETERS") != NULL) {
expanded_sql = "(full SQL statement redacted)";
}
if (schema_name == NULL) {
pr_trace_msg(trace_channel, PROXY_DB_SQLITE_TRACE_LEVEL,
"(sqlite3): returning result row for stmt '%s'", expanded_sql);
} else {
pr_trace_msg(trace_channel, PROXY_DB_SQLITE_TRACE_LEVEL,
"(sqlite3): schema '%s': returning result row for stmt '%s'",
schema_name, expanded_sql);
}
sqlite3_free(orig_sql);
break;
}
case SQLITE_TRACE_CLOSE: {
sqlite3 *db;
db = ptr;
if (schema_name == NULL) {
pr_trace_msg(trace_channel, PROXY_DB_SQLITE_TRACE_LEVEL,
"(sqlite3): closing database connection to %s",
sqlite3_db_filename(db, "main"));
} else {
pr_trace_msg(trace_channel, PROXY_DB_SQLITE_TRACE_LEVEL,
"(sqlite3): schema '%s': closing database connection to %s",
schema_name, sqlite3_db_filename(db, "main"));
}
break;
}
default:
break;
}
return 0;
}
#elif defined(HAVE_SQLITE3_TRACE)
static void db_trace(void *user_data, const char *trace_msg) {
if (user_data != NULL) {
const char *schema_name;
schema_name = user_data;
pr_trace_msg(trace_channel, PROXY_DB_SQLITE_TRACE_LEVEL,
"(sqlite3): schema '%s': %s", schema_name, trace_msg);
} else {
pr_trace_msg(trace_channel, PROXY_DB_SQLITE_TRACE_LEVEL,
"(sqlite3): %s", trace_msg);
}
}
#endif /* HAVE_SQLITE3_TRACE */
static int stmt_cb(void *v, int ncols, char **cols, char **col_names) {
register int i;
const char *stmt;
stmt = v;
pr_trace_msg(trace_channel, 9, "results for '%s':", stmt);
for (i = 0; i < ncols; i++) {
pr_trace_msg(trace_channel, 9, "col #%d [%s]: %s", i+1,
col_names[i], cols[i]);
}
return 0;
}
int proxy_db_exec_stmt(pool *p, struct proxy_dbh *dbh, const char *stmt,
const char **errstr) {
int res;
char *ptr = NULL;
unsigned int nretries = 0;
if (dbh == NULL ||
stmt == NULL) {
errno = EINVAL;
return -1;
}
pr_trace_msg(trace_channel, 10, "schema '%s': executing statement '%s'",
dbh->schema, stmt);
current_schema = dbh->schema;
res = sqlite3_exec(dbh->db, stmt, stmt_cb, (void *) stmt, &ptr);
while (res != SQLITE_OK) {
if (res == SQLITE_BUSY) {
struct timeval tv;
sqlite3_free(ptr);
nretries++;
pr_trace_msg(trace_channel, 3,
"attempt #%u, database busy, trying '%s' again", nretries, stmt);
/* Sleep for short bit, then try again. */
tv.tv_sec = 0;
tv.tv_usec = 500000L;
if (select(0, NULL, NULL, NULL, &tv) < 0) {
if (errno == EINTR) {
pr_signals_handle();
}
}
res = sqlite3_exec(dbh->db, stmt, NULL, NULL, &ptr);
continue;
}
pr_trace_msg(trace_channel, 1,
"error executing '%s': (%d) %s", stmt, res, ptr);
if (errstr != NULL) {
*errstr = pstrdup(p, ptr);
}
current_schema = NULL;
sqlite3_free(ptr);
errno = EINVAL;
return -1;
}
current_schema = NULL;
sqlite3_free(ptr);
pr_trace_msg(trace_channel, 13, "successfully executed '%s'", stmt);
return 0;
}
/* Prepared statements */
int proxy_db_prepare_stmt(pool *p, struct proxy_dbh *dbh, const char *stmt) {
sqlite3_stmt *pstmt = NULL;
int res;
if (p == NULL ||
dbh == NULL ||
stmt == NULL) {
errno = EINVAL;
return -1;
}
pstmt = (sqlite3_stmt *) pr_table_get(dbh->prepared_stmts, stmt, NULL);
if (pstmt != NULL) {
res = sqlite3_clear_bindings(pstmt);
if (res != SQLITE_OK) {
pr_trace_msg(trace_channel, 3,
"error clearing bindings from prepared statement '%s': %s", stmt,
sqlite3_errmsg(dbh->db));
}
res = sqlite3_reset(pstmt);
if (res != SQLITE_OK) {
pr_trace_msg(trace_channel, 3,
"error resetting prepared statement '%s': %s", stmt,
sqlite3_errmsg(dbh->db));
errno = EPERM;
return -1;
}
return 0;
}
res = sqlite3_prepare_v2(dbh->db, stmt, -1, &pstmt, NULL);
if (res != SQLITE_OK) {
pr_trace_msg(trace_channel, 4,
"schema '%s': error preparing statement '%s': %s", dbh->schema, stmt,
sqlite3_errmsg(dbh->db));
errno = EINVAL;
return -1;
}
/* The prepared statement handling here relies on this cache, thus if we fail
* to stash the prepared statement here, it will cause problems later.
*/
res = pr_table_add(dbh->prepared_stmts, pstrdup(dbh->pool, stmt), pstmt,
sizeof(sqlite3_stmt *));
if (res < 0) {
int xerrno = errno;
pr_trace_msg(trace_channel, 4,
"error stashing prepared statement '%s': %s", stmt, strerror(xerrno));
errno = xerrno;
return -1;
}
return 0;
}
int proxy_db_bind_stmt(pool *p, struct proxy_dbh *dbh, const char *stmt,
int idx, int type, void *data, int datalen) {
sqlite3_stmt *pstmt;
int res;
if (p == NULL ||
dbh == NULL ||
stmt == NULL) {
errno = EINVAL;
return -1;
}
/* SQLite3 bind parameters start at index 1. */
if (idx < 1) {
errno = EINVAL;
return -1;
}
if (dbh->prepared_stmts == NULL) {
errno = ENOENT;
return -1;
}
pstmt = (sqlite3_stmt *) pr_table_get(dbh->prepared_stmts, stmt, NULL);
if (pstmt == NULL) {
pr_trace_msg(trace_channel, 19,
"unable to find prepared statement for '%s'", stmt);
errno = ENOENT;
return -1;
}
switch (type) {
case PROXY_DB_BIND_TYPE_INT: {
int i;
if (data == NULL) {
errno = EINVAL;
return -1;
}
i = *((int *) data);
res = sqlite3_bind_int(pstmt, idx, i);
if (res != SQLITE_OK) {
pr_trace_msg(trace_channel, 4,
"error binding parameter %d of '%s' to INT %d: %s", idx, stmt, i,
sqlite3_errmsg(dbh->db));
errno = EPERM;
return -1;
}
break;
}
case PROXY_DB_BIND_TYPE_LONG: {
long l;
if (data == NULL) {
errno = EINVAL;
return -1;
}
l = *((long *) data);
res = sqlite3_bind_int(pstmt, idx, l);
if (res != SQLITE_OK) {
pr_trace_msg(trace_channel, 4,
"error binding parameter %d of '%s' to LONG %ld: %s", idx, stmt, l,
sqlite3_errmsg(dbh->db));
errno = EPERM;
return -1;
}
break;
}
case PROXY_DB_BIND_TYPE_TEXT: {
const char *text;
if (data == NULL) {
errno = EINVAL;
return -1;
}
text = (const char *) data;
res = sqlite3_bind_text(pstmt, idx, text, datalen, NULL);
if (res != SQLITE_OK) {
pr_trace_msg(trace_channel, 4,
"error binding parameter %d of '%s' to TEXT '%s': %s", idx, stmt,
text, sqlite3_errmsg(dbh->db));
errno = EPERM;
return -1;
}
break;
}
case PROXY_DB_BIND_TYPE_BLOB: {
if (data == NULL) {
errno = EINVAL;
return -1;
}
res = sqlite3_bind_blob(pstmt, idx, data, datalen, NULL);
if (res != SQLITE_OK) {
pr_trace_msg(trace_channel, 4,
"error binding parameter %d of '%s' to BLOB (%d bytes): %s", idx,
stmt, datalen, sqlite3_errmsg(dbh->db));
errno = EPERM;
return -1;
}
break;
}
case PROXY_DB_BIND_TYPE_NULL:
res = sqlite3_bind_null(pstmt, idx);
if (res != SQLITE_OK) {
pr_trace_msg(trace_channel, 4,
"error binding parameter %d of '%s' to NULL: %s", idx, stmt,
sqlite3_errmsg(dbh->db));
errno = EPERM;
return -1;
}
break;
default:
pr_trace_msg(trace_channel, 2,
"unknown/unsupported bind data type %d", type);
errno = EINVAL;
return -1;
}
return 0;
}
int proxy_db_finish_stmt(pool *p, struct proxy_dbh *dbh, const char *stmt) {
sqlite3_stmt *pstmt;
int res;
if (p == NULL ||
dbh == NULL ||
stmt == NULL) {
errno = EINVAL;
return -1;
}
if (dbh->prepared_stmts == NULL) {
errno = ENOENT;
return -1;
}
pstmt = (sqlite3_stmt *) pr_table_get(dbh->prepared_stmts, stmt, NULL);
if (pstmt == NULL) {
pr_trace_msg(trace_channel, 19,
"unable to find prepared statement for '%s'", stmt);
errno = ENOENT;
return -1;
}
res = sqlite3_finalize(pstmt);
if (res != SQLITE_OK) {
pr_trace_msg(trace_channel, 3,
"schema '%s': error finishing prepared statement '%s': %s", dbh->schema,
stmt, sqlite3_errmsg(dbh->db));
errno = EPERM;
return -1;
}
(void) pr_table_remove(dbh->prepared_stmts, stmt, NULL);
return 0;
}
array_header *proxy_db_exec_prepared_stmt(pool *p, struct proxy_dbh *dbh,
const char *stmt, const char **errstr) {
sqlite3_stmt *pstmt;
int readonly = FALSE, res;
array_header *results = NULL;
if (p == NULL ||
dbh == NULL ||
stmt == NULL) {
errno = EINVAL;
return NULL;
}
if (dbh->prepared_stmts == NULL) {
errno = ENOENT;
return NULL;
}
pstmt = (sqlite3_stmt *) pr_table_get(dbh->prepared_stmts, stmt, NULL);
if (pstmt == NULL) {
pr_trace_msg(trace_channel, 19,
"unable to find prepared statement for '%s'", stmt);
errno = ENOENT;
return NULL;
}
current_schema = dbh->schema;
/* The sqlit3_stmt_readonly() function first appeared in SQLite 3.7.x. */
#if defined(HAVE_SQLITE3_STMT_READONLY)
readonly = sqlite3_stmt_readonly(pstmt);
#else
readonly = FALSE;
#endif /* SQLite 3.7.x or earlier */
if (readonly == FALSE) {
/* Assume this is an INSERT/UPDATE/DELETE. */
res = sqlite3_step(pstmt);
if (res != SQLITE_DONE) {
const char *errmsg;
errmsg = sqlite3_errmsg(dbh->db);
if (errstr != NULL) {
*errstr = pstrdup(p, errmsg);
}
pr_trace_msg(trace_channel, 2,
"error executing '%s': %s", stmt, errmsg);
current_schema = NULL;
errno = EPERM;
return NULL;
}
current_schema = NULL;
/* Indicate success for non-readonly statements by returning an empty
* result set.
*/
pr_trace_msg(trace_channel, 13, "successfully executed '%s'", stmt);
results = make_array(p, 0, sizeof(char *));
return results;
}
results = make_array(p, 0, sizeof(char *));
res = sqlite3_step(pstmt);
while (res == SQLITE_ROW) {
register int i;
int ncols;
ncols = sqlite3_column_count(pstmt);
pr_trace_msg(trace_channel, 12,
"schema '%s': executing prepared statement '%s' returned row "
"(columns: %d)", dbh->schema, stmt, ncols);
for (i = 0; i < ncols; i++) {
char *val = NULL;
int col_type;
pr_signals_handle();
col_type = sqlite3_column_type(pstmt, i);
if (col_type != SQLITE_BLOB) {
/* By using sqlite3_column_text, SQLite will coerce the column value
* into a string.
*/
val = pstrdup(p, (const char *) sqlite3_column_text(pstmt, i));
pr_trace_msg(trace_channel, 17,
"column %s [%u]: %s", sqlite3_column_name(pstmt, i), i, val);
*((char **) push_array(results)) = val;
} else {
int bloblen;
char bloblen_text[64];
bloblen = sqlite3_column_bytes(pstmt, i);
val = palloc(p, bloblen);
memcpy(val, sqlite3_column_blob(pstmt, i), bloblen);
*((char **) push_array(results)) = val;
/* For BLOBs, we need to provide the length as well. */
memset(&bloblen_text, '\0', sizeof(bloblen_text));
pr_snprintf(bloblen_text, sizeof(bloblen_text)-1, "%d", bloblen);
*((char **) push_array(results)) = pstrdup(p, bloblen_text);
}
}
res = sqlite3_step(pstmt);
}
if (res != SQLITE_DONE) {
const char *errmsg;
errmsg = sqlite3_errmsg(dbh->db);
if (errstr != NULL) {
*errstr = pstrdup(p, errmsg);
}
current_schema = NULL;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"schema '%s': executing prepared statement '%s' did not complete "
"successfully: %s", dbh->schema, stmt, errmsg);
errno = EPERM;
return NULL;
}
current_schema = NULL;
pr_trace_msg(trace_channel, 13, "successfully executed '%s'", stmt);
return results;
}
/* Database opening/closing. */
struct proxy_dbh *proxy_db_open(pool *p, const char *table_path,
const char *schema_name) {
int res, flags;
pool *sub_pool;
const char *stmt;
sqlite3 *db = NULL;
struct proxy_dbh *dbh;
if (p == NULL ||
table_path == NULL ||
schema_name == NULL) {
errno = EINVAL;
return NULL;
}
pr_trace_msg(trace_channel, 19, "attempting to open %s tables at path '%s'",
schema_name, table_path);
flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE;
#if defined(SQLITE_OPEN_PRIVATECACHE)
/* By default, disable the shared cache mode. */
flags |= SQLITE_OPEN_PRIVATECACHE;
#endif
res = sqlite3_open_v2(table_path, &db, flags, NULL);
if (res != SQLITE_OK) {
pr_log_debug(DEBUG0, MOD_PROXY_VERSION
": error opening SQLite database '%s': %s", table_path,
sqlite3_errmsg(db));
if (db != NULL) {
sqlite3_close(db);
}
errno = EPERM;
return NULL;
}
/* Make sure we configure a busy handler. */
sqlite3_busy_handler(db, db_busy, (void *) schema_name);
if (pr_trace_get_level(trace_channel) >= PROXY_DB_SQLITE_TRACE_LEVEL) {
#if defined(HAVE_SQLITE3_TRACE_V2)
sqlite3_trace_v2(db, SQLITE_TRACE_STMT|SQLITE_TRACE_PROFILE|SQLITE_TRACE_ROW|SQLITE_TRACE_CLOSE,
db_trace2, (void *) schema_name);
#elif defined(HAVE_SQLITE3_TRACE)
sqlite3_trace(db, db_trace, (void *) schema_name);
#endif /* HAVE_SQLITE3_TRACE or HAVE_SQLITE3_TRACE_V2 */
}
sub_pool = make_sub_pool(p);
pr_pool_tag(sub_pool, "Proxy Database Pool");
dbh = pcalloc(sub_pool, sizeof(struct proxy_dbh));
dbh->pool = sub_pool;
dbh->db = db;
dbh->schema = pstrdup(dbh->pool, schema_name);
stmt = "PRAGMA temp_store = MEMORY;";
res = proxy_db_exec_stmt(p, dbh, stmt, NULL);
if (res < 0) {
pr_trace_msg(trace_channel, 2,
"error setting MEMORY temp store on SQLite database '%s': %s",
table_path, sqlite3_errmsg(dbh->db));
}
/* Tell SQLite to only use in-memory journals. This is necessary for
* working properly when a chroot is used. Note that the MEMORY journal mode
* of SQLite is supported only for SQLite-3.6.5 and later.
*/
stmt = "PRAGMA journal_mode = MEMORY;";
res = proxy_db_exec_stmt(p, dbh, stmt, NULL);
if (res < 0) {
pr_trace_msg(trace_channel, 2,
"error setting MEMORY journal mode on SQLite database '%s': %s",
table_path, sqlite3_errmsg(dbh->db));
}
dbh->prepared_stmts = pr_table_nalloc(dbh->pool, 0, 4);
pr_trace_msg(trace_channel, 9, "opened SQLite table '%s'", table_path);
return dbh;
}
static int get_schema_version(pool *p, struct proxy_dbh *dbh,
const char *schema_name, unsigned int *schema_version) {
int res, version;
const char *stmt, *errstr = NULL;
array_header *results;
stmt = "SELECT version FROM schema_version WHERE schema = ?;";
res = proxy_db_prepare_stmt(p, dbh, stmt);
if (res < 0) {
/* This can happen when the schema_version table does not exist; treat
* as "missing".
*/
pr_trace_msg(trace_channel, 5,
"error preparing statement '%s', treating as missing schema version",
stmt);
*schema_version = 0;
return 0;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_TEXT,
(void *) schema_name, -1);
if (res < 0) {
return -1;
}
results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr);
if (results == NULL) {
*schema_version = 0;
return 0;
}
if (results->nelts != 1) {
pr_log_debug(DEBUG3, MOD_PROXY_VERSION
": expected 1 result from statement '%s', got %d", stmt,
results->nelts);
errno = EINVAL;
return -1;
}
version = atoi(((char **) results->elts)[0]);
if (version < 0) {
/* Invalid schema version; treat as "missing". */
pr_trace_msg(trace_channel, 5,
"statement '%s' yielded invalid schema version %d, treating as missing",
stmt, version);
*schema_version = 0;
return 0;
}
*schema_version = version;
return 0;
}
static int set_schema_version(pool *p, struct proxy_dbh *dbh,
const char *schema_name, unsigned int schema_version) {
int res, xerrno = 0;
const char *stmt, *errstr = NULL;
array_header *results;
/* CREATE TABLE schema_version (
* schema TEXT NOT NULL PRIMARY KEY,
* version INTEGER NOT NULL
* );
*/
stmt = "CREATE TABLE IF NOT EXISTS schema_version (schema TEXT NOT NULL PRIMARY KEY, version INTEGER NOT NULL);";
res = proxy_db_exec_stmt(p, dbh, stmt, &errstr);
if (res < 0) {
(void) pr_log_debug(DEBUG3, MOD_PROXY_VERSION
": error executing statement '%s': %s", stmt, errstr);
errno = EPERM;
return -1;
}
stmt = "INSERT INTO schema_version (schema, version) VALUES (?, ?);";
res = proxy_db_prepare_stmt(p, dbh, stmt);
if (res < 0) {
xerrno = errno;
(void) pr_log_debug(DEBUG3, MOD_PROXY_VERSION
": schema '%s': error preparing statement '%s': %s", dbh->schema, stmt,
strerror(xerrno));
errno = xerrno;
return -1;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_TEXT,
(void *) schema_name, -1);
if (res < 0) {
return -1;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_INT,
(void *) &schema_version, 0);
if (res < 0) {
return -1;
}
results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr);
if (results == NULL) {
(void) pr_log_debug(DEBUG3, MOD_PROXY_VERSION
": error executing statement '%s': %s", stmt,
errstr ? errstr : strerror(errno));
errno = EPERM;
return -1;
}
return 0;
}
static void check_db_integrity(pool *p, struct proxy_dbh *dbh, int flags) {
int res;
const char *stmt, *errstr = NULL;
if (flags & PROXY_DB_OPEN_FL_INTEGRITY_CHECK) {
stmt = "PRAGMA integrity_check;";
res = proxy_db_exec_stmt(p, dbh, stmt, &errstr);
if (res < 0) {
(void) pr_log_debug(DEBUG3, MOD_PROXY_VERSION
": error executing statement '%s': %s", stmt, errstr);
}
}
if (flags & PROXY_DB_OPEN_FL_VACUUM) {
stmt = "VACUUM;";
res = proxy_db_exec_stmt(p, dbh, stmt, &errstr);
if (res < 0) {
(void) pr_log_debug(DEBUG3, MOD_PROXY_VERSION
": error executing statement '%s': %s", stmt, errstr);
}
}
}
struct proxy_dbh *proxy_db_open_with_version(pool *p, const char *table_path,
const char *schema_name, unsigned int schema_version, int flags) {
pool *tmp_pool = NULL;
struct proxy_dbh *dbh = NULL;
int res = 0, xerrno = 0;
unsigned int current_version = 0;
dbh = proxy_db_open(p, table_path, schema_name);
if (dbh == NULL) {
return NULL;
}
if (flags & PROXY_DB_OPEN_FL_SCHEMA_VERSION_CHECK) {
pr_trace_msg(trace_channel, 19,
"ensuring that schema at path '%s' has at least schema version %u",
table_path, schema_version);
tmp_pool = make_sub_pool(p);
res = get_schema_version(tmp_pool, dbh, schema_name, ¤t_version);
if (res < 0) {
xerrno = errno;
proxy_db_close(p, dbh);
destroy_pool(tmp_pool);
errno = xerrno;
return NULL;
}
if (current_version >= schema_version) {
pr_trace_msg(trace_channel, 11,
"schema version %u >= desired version %u for path '%s'",
current_version, schema_version, table_path);
check_db_integrity(tmp_pool, dbh, flags);
destroy_pool(tmp_pool);
return dbh;
}
if (flags & PROXY_DB_OPEN_FL_ERROR_ON_SCHEMA_VERSION_SKEW) {
pr_trace_msg(trace_channel, 5,
"schema version %u < desired version %u for path '%s', failing",
current_version, schema_version, table_path);
proxy_db_close(p, dbh);
destroy_pool(tmp_pool);
errno = EPERM;
return NULL;
}
/* The schema version is skewed; delete the old table, create a new one. */
pr_trace_msg(trace_channel, 4,
"schema version %u < desired version %u for path '%s', deleting file",
current_version, schema_version, table_path);
if (proxy_db_close(p, dbh) < 0) {
pr_log_debug(DEBUG0, MOD_PROXY_VERSION
": error closing '%s' database: %s", table_path, strerror(errno));
}
if (unlink(table_path) < 0) {
pr_log_pri(PR_LOG_NOTICE, MOD_PROXY_VERSION
": error deleting '%s': %s", table_path, strerror(errno));
}
dbh = proxy_db_open(p, table_path, schema_name);
if (dbh == NULL) {
xerrno = errno;
destroy_pool(tmp_pool);
errno = xerrno;
return NULL;
}
res = set_schema_version(tmp_pool, dbh, schema_name, schema_version);
xerrno = errno;
} else {
check_db_integrity(tmp_pool, dbh, flags);
}
destroy_pool(tmp_pool);
if (res < 0) {
errno = xerrno;
return NULL;
}
return dbh;
}
int proxy_db_close(pool *p, struct proxy_dbh *dbh) {
pool *tmp_pool;
sqlite3_stmt *pstmt;
int res;
if (p == NULL ||
dbh == NULL) {
errno = EINVAL;
return -1;
}
pr_trace_msg(trace_channel, 19, "closing '%s' database handle", dbh->schema);
tmp_pool = make_sub_pool(p);
/* Make sure to close/finish any prepared statements associated with
* the database.
*/
pstmt = sqlite3_next_stmt(dbh->db, NULL);
while (pstmt != NULL) {
sqlite3_stmt *next;
const char *sql;
pr_signals_handle();
next = sqlite3_next_stmt(dbh->db, pstmt);
sql = pstrdup(tmp_pool, sqlite3_sql(pstmt));
res = sqlite3_finalize(pstmt);
if (res != SQLITE_OK) {
pr_trace_msg(trace_channel, 2,
"schema '%s': error finishing prepared statement '%s': %s", dbh->schema,
sql, sqlite3_errmsg(dbh->db));
} else {
pr_trace_msg(trace_channel, 18, "finished prepared statement '%s'", sql);
}
pstmt = next;
}
destroy_pool(tmp_pool);
res = sqlite3_close(dbh->db);
if (res != SQLITE_OK) {
pr_trace_msg(trace_channel, 2,
"error closing SQLite database: %s", sqlite3_errmsg(dbh->db));
errno = EPERM;
return -1;
}
pr_table_empty(dbh->prepared_stmts);
pr_table_free(dbh->prepared_stmts);
destroy_pool(dbh->pool);
pr_trace_msg(trace_channel, 18, "%s", "closed SQLite database");
return 0;
}
int proxy_db_reindex(pool *p, struct proxy_dbh *dbh, const char *index_name,
const char **errstr) {
int res;
const char *stmt;
if (p == NULL ||
dbh == NULL ||
index_name == NULL) {
errno = EINVAL;
return -1;
}
stmt = pstrcat(p, "REINDEX ", index_name, ";", NULL);
res = proxy_db_exec_stmt(p, dbh, stmt, errstr);
return res;
}
int proxy_db_init(pool *p) {
const char *version;
if (p == NULL) {
errno = EINVAL;
return -1;
}
#if defined(SQLITE_CONFIG_SINGLETHREAD)
/* Tell SQLite that we are not a multi-threaded application. */
sqlite3_config(SQLITE_CONFIG_SINGLETHREAD);
#endif /* SQLITE_CONFIG_SINGLETHREAD */
#if defined(SQLITE_CONFIG_LOG)
/* Register an error logging callback with SQLite3. */
sqlite3_config(SQLITE_CONFIG_LOG, db_err, NULL);
#endif /* SQLITE_CONFIG_LOG */
#if defined(SQLITE_CONFIG_SQLLOG)
sqlite3_config(SQLITE_CONFIG_SQLLOG, db_sql, NULL);
#endif /* SQLITE_CONFIG_SQLLOG */
/* Check that the SQLite headers used match the version of the SQLite
* library used.
*
* For now, we only log if there is a difference.
*/
version = sqlite3_libversion();
if (strcmp(version, SQLITE_VERSION) != 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"compiled using SQLite version '%s' headers, but linked to "
"SQLite version '%s' library", SQLITE_VERSION, version);
}
pr_trace_msg(trace_channel, 9, "using SQLite %s", version);
return 0;
}
int proxy_db_free(void) {
return 0;
}
proftpd-mod_proxy-0.9.5/lib/proxy/dns.c 0000664 0000000 0000000 00000042722 14757370167 0020130 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy DNS resolution
* Copyright (c) 2020-2023 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "proxy/dns.h"
/* The C_ANY macro is defined in ProFTPD's ftp.h file for "any" FTP command,
* and may conflict with the DNS macros. This API does not use ProFTPD's C_ANY
* macro, so remove it and avoid the collision.
*/
#undef C_ANY
#include
#include
static const char *trace_channel = "proxy.dns";
struct srv_record {
uint16_t priority;
uint16_t weight;
uint16_t port;
const char *target;
};
/* Sorting algorithm: priority first, then weight. */
static int srv_cmp(const void *left, const void *right) {
const struct srv_record *a, *b;
a = left;
b = right;
/* Lower priority wins */
if (a->priority < b->priority) {
return -1;
}
if (b->priority < a->priority) {
return 1;
}
/* For equal priorities, higher weight wins.
*
* Yes, I know that RFC 2782 prescribes a more nuanced algorithm, with
* weighted random selection of records with equal priorities.
*/
if (a->weight > b->weight) {
return -1;
}
if (b->weight > a->weight) {
return 1;
}
return 0;
}
static int dns_query_error(const char *query_type, const char *query) {
pr_trace_msg(trace_channel, 3, "failed to resolve %s records for '%s': %s",
query_type, query, hstrerror(h_errno));
/* Try to set an appropriate errno. */
switch (h_errno) {
#if defined(HOST_NOT_FOUND)
case HOST_NOT_FOUND:
errno = ENOENT;
break;
#endif /* HOST_NOT_FOUND */
#if defined(NO_DATA)
case NO_DATA:
errno = ENOENT;
break;
#endif /* NO_DATA */
default:
errno = EPERM;
}
return -1;
}
static int dns_resolve_srv_a(pool *p, struct srv_record *srv, ns_rr rr,
array_header *resp) {
int xerrno;
char text[INET_ADDRSTRLEN];
const pr_netaddr_t *addr;
pr_inet_ntop(AF_INET, ns_rr_rdata(rr), text, sizeof(text));
addr = pr_netaddr_get_addr(p, text, NULL);
xerrno = errno;
if (addr == NULL) {
pr_trace_msg(trace_channel, 3, "error resolving SRV A record '%s': %s",
text, strerror(xerrno));
errno = xerrno;
return -1;
}
pr_netaddr_set_port2((pr_netaddr_t *) addr, srv->port);
pr_trace_msg(trace_channel, 19, "adding SRV A record for %s#%u",
pr_netaddr_get_ipstr(addr), ntohs(pr_netaddr_get_port(addr)));
*((const pr_netaddr_t **) push_array(resp)) = addr;
return 0;
}
static int dns_resolve_srv_aaaa(pool *p, struct srv_record *srv, ns_rr rr,
array_header *resp) {
#if defined(PR_USE_IPV6)
int xerrno;
char text[INET6_ADDRSTRLEN];
const pr_netaddr_t *addr;
pr_inet_ntop(AF_INET6, ns_rr_rdata(rr), text, sizeof(text));
addr = pr_netaddr_get_addr(p, text, NULL);
xerrno = errno;
if (addr == NULL) {
pr_trace_msg(trace_channel, 3, "error resolving SRV A record '%s': %s",
text, strerror(xerrno));
errno = xerrno;
return -1;
}
pr_netaddr_set_port2((pr_netaddr_t *) addr, srv->port);
pr_trace_msg(trace_channel, 19, "adding SRV AAAA record for %s#%u",
pr_netaddr_get_ipstr(addr), ntohs(pr_netaddr_get_port(addr)));
*((const pr_netaddr_t **) push_array(resp)) = addr;
return 0;
#endif /* PR_USE_IPV6 */
errno = ENOSYS;
return -1;
}
static int dns_resolve_srv_name(pool *p, struct srv_record *srv,
array_header *resp) {
int xerrno;
pool *tmp_pool;
pr_netaddr_t *addr;
const pr_netaddr_t *res;
array_header *addrs = NULL;
tmp_pool = make_sub_pool(p);
pr_pool_tag(tmp_pool, "SRV name resolution");
res = pr_netaddr_get_addr(tmp_pool, srv->target, &addrs);
xerrno = errno;
if (res == NULL) {
destroy_pool(tmp_pool);
pr_trace_msg(trace_channel, 3, "error resolving SRV target '%s': %s",
srv->target, strerror(xerrno));
errno = xerrno;
return -1;
}
addr = pr_netaddr_dup(p, res);
pr_netaddr_set_port2(addr, srv->port);
pr_trace_msg(trace_channel, 19, "adding '%s' resolved record for %s#%u",
srv->target, pr_netaddr_get_ipstr(addr), ntohs(pr_netaddr_get_port(addr)));
*((pr_netaddr_t **) push_array(resp)) = addr;
if (addrs != NULL) {
register unsigned int i;
pr_netaddr_t **elts;
/* Other addresses were found associated with this name. */
elts = addrs->elts;
for (i = 0; i < addrs->nelts; i++) {
pr_netaddr_t *elt;
elt = elts[i];
addr = pr_netaddr_dup(p, elt);
pr_netaddr_set_port2(addr, srv->port);
pr_trace_msg(trace_channel, 19, "adding '%s' resolved record for %s#%u",
srv->target, pr_netaddr_get_ipstr(addr),
ntohs(pr_netaddr_get_port(addr)));
*((pr_netaddr_t **) push_array(resp)) = addr;
}
}
return 0;
}
static int dns_resolve_srv_target(pool *p, const char *query,
struct srv_record *srv, ns_msg msgh, array_header **resp, uint32_t *ttl) {
register unsigned int i;
unsigned int count, found = 0;
/* Look for A, AAAA records in the "Additional Data" (`ns_s_ar`) section.
* These SHOULD be the records for the targets mentioned by the SRV records.
* If no matching A, AAAA records are found, we resort to our normal
* resolution routine, i.e. pr_netaddr_get_addr().
*
* If we see a CNAME record in the "Additional Data" section, ignore it; it
* will be treated as if there are no A, AAAA records found.
*/
count = ns_msg_count(msgh, ns_s_ar);
pr_trace_msg(trace_channel, 17,
"found %u %s in the '%s' SRV additional data section", count,
count != 1 ? "records" : "record", query);
for (i = 0; i < count; i++) {
ns_rr record;
uint32_t record_ttl;
const char *record_name;
pr_signals_handle();
if (ns_parserr(&msgh, ns_s_ar, i, &record) < 0) {
pr_trace_msg(trace_channel, 4,
"error parsing DNS resource record #%u, skipping: %s", i + 1,
strerror(errno));
continue;
}
record_name = ns_rr_name(record);
/* Remember that DNS names are case-insensitive. */
if (strcasecmp(srv->target, record_name) != 0) {
pr_trace_msg(trace_channel, 9, "additional resource record (#%u, %s) "
"does not match target '%s', skipping", i + 1, record_name,
srv->target);
continue;
}
record_ttl = ns_rr_ttl(record);
switch (ns_rr_type(record)) {
case ns_t_a:
if (ns_rr_rdlen(record) == 4) {
pr_trace_msg(trace_channel, 4,
"found additional A resource record (#%u, %s) for '%s' (TTL %lu)",
i + 1, record_name, query, (unsigned long) record_ttl);
if (dns_resolve_srv_a(p, srv, record, *resp) == 0) {
if (ttl != NULL) {
if (record_ttl < *ttl) {
*ttl = record_ttl;
}
}
found++;
}
} else {
pr_trace_msg(trace_channel, 9,
"found additional A resource record (#%u, %s) for '%s' with bad "
"length (%d), skipping", i + 1, record_name, query,
ns_rr_rdlen(record));
}
break;
case ns_t_aaaa:
if (ns_rr_rdlen(record) == 16) {
pr_trace_msg(trace_channel, 4,
"found additional AAAA resource record (#%u, %s) for '%s' "
"(TTL %lu)", i + 1, record_name, query, (unsigned long) record_ttl);
if (dns_resolve_srv_aaaa(p, srv, record, *resp) == 0) {
if (ttl != NULL) {
if (record_ttl < *ttl) {
*ttl = record_ttl;
}
}
found++;
}
} else {
pr_trace_msg(trace_channel, 9,
"found additional AAAA resource record (#%u, %s) for '%s' with bad "
"length (%d), skipping", i + 1, record_name, query,
ns_rr_rdlen(record));
}
break;
case ns_t_cname:
pr_trace_msg(trace_channel, 9,
"found additional CNAME resource record (#%u, %s) for '%s' "
"(TTL %lu), skipping", i + 1, record_name, query,
(unsigned long) record_ttl);
break;
default:
pr_trace_msg(trace_channel, 9,
"found additional unexpected resource record (#%u, %d, %s) for '%s', "
"skipping", i + 1, ns_rr_type(record), record_name, query);
break;
}
}
if (found == 0) {
/* No matching addresses found in "Additional data"; resolve manually. */
if (dns_resolve_srv_name(p, srv, *resp) < 0) {
return -1;
}
}
return 0;
}
static int dns_resolve_srv_targets(pool *p, const char *query,
array_header *srvs, ns_msg msgh, array_header **resp, uint32_t *ttl) {
register unsigned int i;
struct srv_record **elts;
*resp = make_array(p, srvs->nelts, sizeof(pr_netaddr_t *));
elts = srvs->elts;
for (i = 0; i < srvs->nelts; i++) {
struct srv_record *srv;
srv = elts[i];
if (dns_resolve_srv_target(p, query, srv, msgh, resp, ttl) < 0) {
pr_trace_msg(trace_channel, 3,
"error resolving SRV target '%s' to address: %s", srv->target,
strerror(errno));
}
}
return 0;
}
static int dns_resolve_srv(pool *p, const char *name, array_header **resp,
uint32_t *ttl) {
register unsigned int i;
int answerlen, res;
unsigned char answer[NS_PACKETSZ * 4];
unsigned int count;
ns_msg msgh;
pool *srv_pool;
array_header *srvs;
pr_trace_msg(trace_channel, 17, "querying DNS for SRV records for '%s'",
name);
answerlen = res_query(name, ns_c_in, ns_t_srv, answer, sizeof(answer)-1);
pr_trace_msg(trace_channel, 22, "received answer (%d bytes) of SRV records "
"for '%s'", answerlen, name);
if (answerlen < 0) {
return dns_query_error("SRV", name);
}
if (ns_initparse(answer, answerlen, &msgh) < 0) {
pr_trace_msg(trace_channel, 2, "failed parsing SRV response for '%s'",
name);
errno = EINVAL;
return -1;
}
count = ns_msg_count(msgh, ns_s_an);
pr_trace_msg(trace_channel, 17, "found %u %s in the '%s' SRV answer section",
count, count != 1 ? "records" : "record", name);
srv_pool = make_sub_pool(p);
pr_pool_tag(srv_pool, "SRV records");
srvs = make_array(srv_pool, count, sizeof(struct srv_record *));
/* Note: What does it mean, if there are more than one SRV records for a
* given service for a domain?
*
* Answer: Consider the different priorities, different weights. So yes,
* it's quite probable. Hopefully each of the different SRV records has
* a different target. Right?
*/
for (i = 0; i < count; i++) {
ns_rr record;
uint16_t priority, weight, port, offset;
uint32_t record_ttl;
size_t target_len;
char *target_text;
int expanded_namelen;
char expanded_name[NS_MAXDNAME];
struct srv_record *srv;
pr_signals_handle();
if (ns_parserr(&msgh, ns_s_an, i, &record) < 0) {
pr_trace_msg(trace_channel, 4,
"error parsing DNS resource record #%u, skipping: %s", i + 1,
strerror(errno));
continue;
}
if (ns_rr_type(record) != ns_t_srv) {
pr_trace_msg(trace_channel, 4,
"found non-SRV DNS resource record #%u, skipping", i + 1);
continue;
}
record_ttl = ns_rr_ttl(record);
offset = 0;
priority = ns_get16(ns_rr_rdata(record) + offset);
offset += NS_INT16SZ;
weight = ns_get16(ns_rr_rdata(record) + offset);
/* TODO: Watch out for port 0 values! */
offset += NS_INT16SZ;
port = ns_get16(ns_rr_rdata(record) + offset);
offset += NS_INT16SZ;
/* Ideally, we would assume proper RFC 2782 implementations, and would NOT
* attempt to decompress the target names. For related issues, see:
*
* systemd should not compress target names in SRV records:
* https://github.com/systemd/systemd/issues/9793
*
* net: target domain names in SRV records should not be decompressed
* https://github.com/golang/go/issues/10622
*
* However, we opportunistically attempt to uncompress the target name,
* for now. Behavior subject to change without notice.
*/
expanded_namelen = ns_name_uncompress(ns_msg_base(msgh), ns_msg_end(msgh),
ns_rr_rdata(record) + offset, expanded_name, sizeof(expanded_name));
if (expanded_namelen < 0) {
/* Assume the target name was properly NOT compressed. */
target_len = ns_rr_rdlen(record) - offset;
target_text = pcalloc(srv_pool, target_len + 1);
memcpy(target_text, (unsigned char *) ns_rr_rdata(record) + offset,
target_len);
} else {
target_len = expanded_namelen;
target_text = pcalloc(srv_pool, target_len + 1);
memcpy(target_text, expanded_name, expanded_namelen);
}
pr_trace_msg(trace_channel, 17, "resolved '%s' to SRV record #%u "
"(TTL %lu): priority = %u, weight = %u, port = %u, target = '%s'",
name, i + 1, (unsigned long) record_ttl, priority, weight, port,
target_text);
/* If target is ".", abort (per RFC 2782); this means that this service
* is decidedly not offered for this host/domain.
*/
if (strcmp(target_text, ".") == 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"SRV records for '%s' indicate that the service is explicitly "
"not available", name);
*resp = NULL;
errno = ENOENT;
return -1;
}
srv = palloc(srv_pool, sizeof(struct srv_record));
srv->priority = priority;
srv->weight = weight;
srv->port = port;
srv->target = target_text;
*((struct srv_record **) push_array(srvs)) = srv;
}
/* Sort our SRV records to get the ordered list of target names/ports. */
qsort(srvs->elts, srvs->nelts, sizeof(struct srv_record *), srv_cmp);
res = dns_resolve_srv_targets(p, name, srvs, msgh, resp, ttl);
if (res < 0) {
pr_trace_msg(trace_channel, 3,
"error resolving SRV targets to addresses: %s", strerror(errno));
}
destroy_pool(srv_pool);
return (*resp)->nelts;
}
static int dns_resolve_txt(pool *p, const char *name, array_header **resp,
uint32_t *ttl) {
register unsigned int i;
int answerlen;
unsigned char answer[NS_PACKETSZ * 4];
unsigned int count;
ns_msg msgh;
pr_trace_msg(trace_channel, 17, "querying DNS for TXT records for '%s'",
name);
answerlen = res_query(name, ns_c_in, ns_t_txt, answer, sizeof(answer)-1);
pr_trace_msg(trace_channel, 22, "received answer (%d bytes) of TXT records "
"for '%s'", answerlen, name);
if (answerlen < 0) {
return dns_query_error("TXT", name);
}
if (ns_initparse(answer, answerlen, &msgh) < 0) {
pr_trace_msg(trace_channel, 2, "failed parsing TXT response for '%s'",
name);
errno = EINVAL;
return -1;
}
count = ns_msg_count(msgh, ns_s_an);
pr_trace_msg(trace_channel, 17, "found %u %s in the '%s' TXT answer section",
count, count != 1 ? "records" : "record", name);
*resp = make_array(p, count, sizeof(char *));
for (i = 0; i < count; i++) {
ns_rr record;
uint32_t record_ttl;
size_t record_len;
char *record_text;
pr_signals_handle();
if (ns_parserr(&msgh, ns_s_an, i, &record) < 0) {
pr_trace_msg(trace_channel, 4,
"error parsing DNS resource record #%u, skipping: %s", i + 1,
strerror(errno));
continue;
}
if (ns_rr_type(record) != ns_t_txt) {
pr_trace_msg(trace_channel, 4,
"found non-TXT DNS resource record #%u, skipping", i + 1);
continue;
}
record_ttl = ns_rr_ttl(record);
record_len = ns_rr_rdlen(record) - 1;
record_text = pcalloc(p, record_len + 1);
memcpy(record_text, (unsigned char *) ns_rr_rdata(record) + 1, record_len);
pr_trace_msg(trace_channel, 17,
"resolved '%s' to TXT record #%u: '%s' (TTL %lu)", name, i + 1,
record_text, (unsigned long) record_ttl);
/* It is up to the caller to filter through these TXT records, looking for
* what they want (e.g. URLs).
*/
*((char **) push_array(*resp)) = record_text;
if (ttl != NULL) {
if (record_ttl < *ttl) {
*ttl = record_ttl;
}
}
}
return (*resp)->nelts;
}
/* Note that this is mostly used for resolving SRV, TXT records. */
int proxy_dns_resolve(pool *p, const char *name, proxy_dns_type_e dns_type,
array_header **resp, uint32_t *ttl) {
int res;
if (p == NULL ||
name == NULL ||
resp == NULL) {
errno = EINVAL;
return -1;
}
switch (dns_type) {
case PROXY_DNS_A:
/* Currently not implemented. */
errno = ENOSYS;
res = -1;
break;
#if defined(PR_USE_IPV6)
case PROXY_DNS_AAAA:
/* Currently not implemented. */
errno = ENOSYS;
res = -1;
break;
#endif /* PR_USE_IPV6 */
case PROXY_DNS_SRV:
res = dns_resolve_srv(p, name, resp, ttl);
break;
case PROXY_DNS_TXT:
res = dns_resolve_txt(p, name, resp, ttl);
break;
case PROXY_DNS_UNKNOWN:
default:
errno = EPERM;
res = -1;
}
return res;
}
proftpd-mod_proxy-0.9.5/lib/proxy/forward.c 0000664 0000000 0000000 00000066714 14757370167 0021017 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy forward proxy implementation
* Copyright (c) 2012-2022 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "proxy/conn.h"
#include "proxy/netio.h"
#include "proxy/inet.h"
#include "proxy/forward.h"
#include "proxy/tls.h"
#include "proxy/ftp/ctrl.h"
#include "proxy/ftp/sess.h"
static int proxy_method = PROXY_FORWARD_METHOD_USER_WITH_PROXY_AUTH;
static int forward_retry_count = PROXY_DEFAULT_RETRY_COUNT;
/* handle_user_passthru flags */
#define PROXY_FORWARD_USER_PASSTHRU_FL_PARSE_DSTADDR 0x001
#define PROXY_FORWARD_USER_PASSTHRU_FL_CONNECT_DSTADDR 0x002
#define PROXY_FORWARD_USER_PASSTHRU_FL_SNI_DSTADDR 0x004
static const char *trace_channel = "proxy.forward";
int proxy_forward_use_proxy_auth(void) {
switch (proxy_method) {
case PROXY_FORWARD_METHOD_USER_NO_PROXY_AUTH:
case PROXY_FORWARD_METHOD_USER_SNI_NO_PROXY_AUTH:
return FALSE;
default:
break;
}
return TRUE;
}
int proxy_forward_init(pool *p, const char *tables_dir) {
return 0;
}
int proxy_forward_free(pool *p) {
/* TODO: Implement any necessary cleanup */
return 0;
}
int proxy_forward_sess_free(pool *p, struct proxy_session *proxy_sess) {
/* Reset any state. */
proxy_method = PROXY_FORWARD_METHOD_USER_WITH_PROXY_AUTH;
forward_retry_count = PROXY_DEFAULT_RETRY_COUNT;
return 0;
}
int proxy_forward_sess_init(pool *p, const char *tables_dir,
struct proxy_session *proxy_sess) {
config_rec *c;
int allowed = FALSE;
const void *enabled = NULL;
/* By default, only allow connections from RFC1918 addresses to use
* forward proxying. Otherwise, it must be from an explicitly allowed
* connection class, via the class notes.
*/
if (session.conn_class != NULL) {
enabled = pr_table_get(session.conn_class->cls_notes,
PROXY_FORWARD_ENABLED_NOTE, NULL);
}
if (enabled != NULL) {
allowed = *((int *) enabled);
if (allowed == FALSE) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"forward proxying not allowed from client address %s in "
"(see ProxyForwardEnabled)",
pr_netaddr_get_ipstr(session.c->remote_addr),
session.conn_class->cls_name);
}
} else {
if (pr_netaddr_is_rfc1918(session.c->remote_addr) == TRUE) {
allowed = TRUE;
} else {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"forward proxying not allowed from non-RFC1918 client address %s",
pr_netaddr_get_ipstr(session.c->remote_addr));
}
}
if (allowed == FALSE) {
errno = EPERM;
return -1;
}
c = find_config(main_server->conf, CONF_PARAM, "ProxyForwardMethod", FALSE);
if (c != NULL) {
proxy_method = *((int *) c->argv[0]);
}
c = find_config(main_server->conf, CONF_PARAM, "ProxyRetryCount", FALSE);
if (c != NULL) {
forward_retry_count = *((int *) c->argv[0]);
}
return 0;
}
int proxy_forward_have_authenticated(cmd_rec *cmd) {
int authd = FALSE;
/* Authenticated here means authenticated *to the proxy*, i.e. should we
* allow more commands, or reject them because the client hasn't authenticated
* yet.
*/
switch (proxy_method) {
case PROXY_FORWARD_METHOD_USER_NO_PROXY_AUTH:
authd = TRUE;
break;
case PROXY_FORWARD_METHOD_PROXY_USER_WITH_PROXY_AUTH:
case PROXY_FORWARD_METHOD_USER_WITH_PROXY_AUTH:
if (proxy_sess_state & PROXY_SESS_STATE_PROXY_AUTHENTICATED) {
authd = TRUE;
}
break;
default:
authd = FALSE;
}
if (authd == FALSE) {
pr_response_send(R_530, _("Please login with USER and PASS"));
}
return authd;
}
static int forward_tls_postopen(pool *p, struct proxy_session *proxy_sess,
conn_t *server_conn, pr_response_t **resp) {
int xerrno;
if (proxy_netio_postopen(server_conn->instrm) < 0) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"postopen error for backend control connection input stream: %s",
strerror(xerrno));
proxy_inet_close(session.pool, server_conn);
proxy_sess->backend_ctrl_conn = NULL;
*resp = NULL;
errno = xerrno;
return -1;
}
if (proxy_netio_postopen(server_conn->outstrm) < 0) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"postopen error for backend control connection output stream: %s",
strerror(xerrno));
proxy_inet_close(session.pool, server_conn);
proxy_sess->backend_ctrl_conn = NULL;
*resp = NULL;
errno = xerrno;
return -1;
}
return 0;
}
static int forward_connect(pool *p, struct proxy_session *proxy_sess,
pr_response_t **resp, unsigned int *resp_nlines) {
conn_t *server_conn = NULL;
int banner_ok = TRUE, use_tls, xerrno = 0;
const pr_netaddr_t *dst_addr;
array_header *other_addrs = NULL;
char port_text[32];
dst_addr = proxy_sess->dst_addr;
other_addrs = proxy_sess->other_addrs;
if (proxy_tls_using_tls() == PROXY_TLS_ENGINE_MATCH_CLIENT) {
proxy_tls_match_client_tls();
}
/* If the destination port is 990, assume implicit FTPS. */
if (ntohs(pr_netaddr_get_port(dst_addr)) == PROXY_TLS_IMPLICIT_FTPS_PORT) {
pr_trace_msg(trace_channel, 9, "%s#%u requesting, using implicit FTPS",
pr_netaddr_get_ipstr(dst_addr),
(unsigned int) ntohs(pr_netaddr_get_port(dst_addr)));
proxy_tls_set_tls(PROXY_TLS_ENGINE_IMPLICIT);
}
server_conn = proxy_conn_get_server_conn(p, proxy_sess, dst_addr);
if (server_conn == NULL) {
xerrno = errno;
if (other_addrs != NULL) {
register unsigned int i;
/* Try the other IP addresses for the requested name (if any) as well. */
for (i = 0; i < other_addrs->nelts; i++) {
dst_addr = ((pr_netaddr_t **) other_addrs->elts)[i];
pr_trace_msg(trace_channel, 8,
"attempting to connect to other address #%u (%s) for requested "
"URI '%.100s'", i+1, pr_netaddr_get_ipstr(dst_addr),
proxy_conn_get_uri(proxy_sess->dst_pconn));
server_conn = proxy_conn_get_server_conn(p, proxy_sess, dst_addr);
if (server_conn != NULL) {
proxy_sess->dst_addr = dst_addr;
break;
}
}
}
if (server_conn == NULL) {
xerrno = errno;
/* EINVALs lead to strange-looking error responses; change them to
* EPERM.
*/
if (xerrno == EINVAL) {
xerrno = EPERM;
}
}
errno = xerrno;
return -1;
}
proxy_sess->frontend_ctrl_conn = session.c;
proxy_sess->backend_ctrl_conn = server_conn;
use_tls = proxy_tls_using_tls();
/* Handle implicit FTPS connects. */
if (use_tls == PROXY_TLS_ENGINE_IMPLICIT) {
if (forward_tls_postopen(p, proxy_sess, server_conn, resp) < 0) {
return -1;
}
}
/* XXX Support/send a CLNT command of our own? Configurable via e.g.
* "UserAgent" string?
*/
/* Read the response from the backend server. */
*resp = proxy_ftp_ctrl_recv_resp(p, proxy_sess->backend_ctrl_conn,
resp_nlines, 0);
if (*resp == NULL) {
xerrno = errno;
pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to read banner from server %s:%u: %s",
pr_netaddr_get_ipstr(proxy_sess->backend_ctrl_conn->remote_addr),
ntohs(pr_netaddr_get_port(proxy_sess->backend_ctrl_conn->remote_addr)),
strerror(xerrno));
errno = EPERM;
return -1;
}
if ((*resp)->num[0] != '2') {
banner_ok = FALSE;
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"received banner from backend %s:%u%s: %s %s",
pr_netaddr_get_ipstr(proxy_sess->backend_ctrl_conn->remote_addr),
ntohs(pr_netaddr_get_port(proxy_sess->backend_ctrl_conn->remote_addr)),
banner_ok ? "" : ", DISCONNECTING", (*resp)->num, (*resp)->msg);
if (banner_ok == FALSE) {
pr_inet_close(p, proxy_sess->backend_ctrl_conn);
proxy_sess->backend_ctrl_conn = NULL;
errno = EPERM;
return -1;
}
/* Get the features supported by the backend server */
if (proxy_ftp_sess_get_feat(p, proxy_sess) < 0) {
if (errno != EPERM) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to determine features of backend server: %s", strerror(errno));
}
}
use_tls = proxy_tls_using_tls();
if (use_tls != PROXY_TLS_ENGINE_OFF &&
use_tls != PROXY_TLS_ENGINE_IMPLICIT) {
if (proxy_ftp_sess_send_auth_tls(p, proxy_sess) < 0 &&
errno != ENOSYS) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error enabling TLS on control connection to backend server: %s",
strerror(xerrno));
pr_inet_close(p, proxy_sess->backend_ctrl_conn);
proxy_sess->backend_ctrl_conn = NULL;
*resp = NULL;
errno = xerrno;
return -1;
}
use_tls = proxy_tls_using_tls();
}
if (use_tls != PROXY_TLS_ENGINE_OFF &&
use_tls != PROXY_TLS_ENGINE_IMPLICIT) {
if (forward_tls_postopen(p, proxy_sess, server_conn, resp) < 0) {
return -1;
}
}
if (use_tls != PROXY_TLS_ENGINE_OFF) {
if (proxy_sess_state & PROXY_SESS_STATE_BACKEND_HAS_CTRL_TLS) {
/* NOTE: should this be a fatal error? */
(void) proxy_ftp_sess_send_pbsz_prot(p, proxy_sess);
}
}
(void) proxy_ftp_sess_send_host(p, proxy_sess);
/* Populate the session notes about this connection. */
memset(port_text, '\0', sizeof(port_text));
pr_snprintf(port_text, sizeof(port_text)-1, "%d",
proxy_conn_get_port(proxy_sess->dst_pconn));
(void) pr_table_add_dup(session.notes, "mod_proxy.backend-ip",
pr_netaddr_get_ipstr(dst_addr), 0);
(void) pr_table_remove(session.notes, "mod_proxy.backend-port", NULL);
(void) pr_table_add_dup(session.notes, "mod_proxy.backend-port",
port_text, 0);
(void) pr_table_add_dup(session.notes, "mod_proxy.backend-url",
proxy_conn_get_uri(proxy_sess->dst_pconn), 0);
proxy_sess_state |= PROXY_SESS_STATE_CONNECTED;
return 0;
}
static int forward_dst_filter(pool *p, const char *hostport) {
#ifdef PR_USE_REGEX
config_rec *c;
pr_regex_t *pre;
int negated = FALSE, res;
c = find_config(main_server->conf, CONF_PARAM, "ProxyForwardTo", FALSE);
if (c == NULL) {
return 0;
}
pre = c->argv[0];
negated = *((int *) c->argv[1]);
res = pr_regexp_exec(pre, hostport, 0, NULL, 0, 0, 0);
if (res == 0) {
/* Pattern matched */
if (negated == TRUE) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"host/port '%.100s' matched ProxyForwardTo !%s, rejecting",
hostport, pr_regexp_get_pattern(pre));
errno = EPERM;
return -1;
}
} else {
/* Pattern NOT matched */
if (negated == FALSE) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"host/port '%.100s' did not match ProxyForwardTo %s, rejecting",
hostport, pr_regexp_get_pattern(pre));
errno = EPERM;
return -1;
}
}
#endif /* PR_USE_REGEX */
return 0;
}
static int forward_cmd_parse_dst(pool *p, const char *arg, char **name,
const struct proxy_conn **pconn) {
const char *default_proto = NULL, *default_port = NULL, *proto = NULL,
*port, *uri = NULL;
char *host = NULL, *hostport = NULL, *host_ptr = NULL, *port_ptr = NULL;
/* TODO: Revisit these defaults once we start supporting other protocols. */
default_proto = "ftp";
default_port = "21";
/* First, look for the optional port. */
port_ptr = strrchr(arg, ':');
if (port_ptr == NULL) {
port = default_port;
} else {
char *tmp2 = NULL;
long num;
num = strtol(port_ptr+1, &tmp2, 10);
if (tmp2 && *tmp2) {
/* Trailing garbage found in port number. */
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"malformed port number '%s' found in USER '%s', rejecting",
port_ptr+1, arg);
errno = EINVAL;
return -1;
}
if (num < 0 ||
num > 65535) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"invalid port number %ld found in USER '%s', rejecting", num, arg);
errno = EINVAL;
return -1;
}
port = pstrdup(p, port_ptr + 1);
}
/* Find the required '@' delimiter. */
host_ptr = strrchr(arg, '@');
if (host_ptr == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"missing required '@' delimiter in USER '%s', rejecting", arg);
errno = EINVAL;
return -1;
}
if (port_ptr == NULL) {
host = pstrdup(p, host_ptr + 1);
} else {
host = pstrndup(p, host_ptr + 1, (port_ptr - host_ptr - 1));
}
*name = pstrndup(p, arg, (host_ptr - arg));
proto = default_proto;
hostport = pstrcat(p, host, ":", port, NULL);
if (forward_dst_filter(p, hostport) < 0) {
return -1;
}
uri = pstrcat(p, proto, "://", hostport, NULL);
/* Note: We deliberately use proxy_pool, rather than the given pool, here
* so that the created structure (especially the pr_netaddr_t) are
* longer-lived.
*/
*pconn = proxy_conn_create(proxy_pool, uri, 0);
if (*pconn == NULL) {
int xerrno = errno;
pr_trace_msg(trace_channel, 1,
"error handling URI '%.100s': %s", uri, strerror(xerrno));
errno = xerrno;
return -1;
}
return 0;
}
static int forward_cmd_parse_sni(pool *p, const struct proxy_conn **pconn) {
const char *sni = NULL;
char *hostport = NULL, *port_ptr = NULL, *uri = NULL;
sni = pr_table_get(session.notes, "mod_tls.sni", NULL);
if (sni == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to use ProxyForwardMethod 'user@sni' due to missing TLS SNI");
errno = EPERM;
return -1;
}
port_ptr = strrchr(sni, ':');
if (port_ptr == NULL) {
hostport = pstrcat(p, sni, ":21", NULL);
} else {
/* In this case, the SNI already includes a port; no need to add one. */
hostport = pstrdup(p, sni);
}
if (forward_dst_filter(p, hostport) < 0) {
return -1;
}
uri = pstrcat(p, "ftp://", hostport, NULL);
/* Note: We deliberately use proxy_pool, rather than the given pool, here
* so that the created structure (especially the pr_netaddr_t) are
* longer-lived.
*/
*pconn = proxy_conn_create(proxy_pool, uri, 0);
if (*pconn == NULL) {
int xerrno = errno;
pr_trace_msg(trace_channel, 1,
"error handling URI '%.100s': %s", uri, strerror(xerrno));
errno = xerrno;
return -1;
}
return 0;
}
static int forward_handle_user_passthru(cmd_rec *cmd,
struct proxy_session *proxy_sess, int *successful, int flags) {
int res, xerrno;
char *user = NULL;
cmd_rec *user_cmd = NULL;
pr_response_t *resp = NULL;
unsigned int resp_nlines = 0;
if ((flags & PROXY_FORWARD_USER_PASSTHRU_FL_PARSE_DSTADDR) ||
(flags & PROXY_FORWARD_USER_PASSTHRU_FL_SNI_DSTADDR)) {
const struct proxy_conn *pconn = NULL;
const pr_netaddr_t *remote_addr = NULL;
array_header *other_addrs = NULL;
if (flags & PROXY_FORWARD_USER_PASSTHRU_FL_PARSE_DSTADDR) {
res = forward_cmd_parse_dst(cmd->tmp_pool, cmd->arg, &user, &pconn);
} else {
res = forward_cmd_parse_sni(cmd->tmp_pool, &pconn);
}
if (res < 0) {
errno = EINVAL;
return -1;
}
remote_addr = proxy_conn_get_addr(pconn, &other_addrs);
/* Ensure that the requested remote address is NOT (blatantly) ourselves,
* i.e. the proxy itself. This prevents easy-to-detect proxy loops.
*/
if (pr_netaddr_cmp(remote_addr, session.c->local_addr) == 0 &&
pr_netaddr_get_port(remote_addr) == pr_netaddr_get_port(session.c->local_addr)) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"requested destination %s#%u is local address %s#%u, rejecting",
pr_netaddr_get_ipstr(remote_addr),
ntohs(pr_netaddr_get_port(remote_addr)),
pr_netaddr_get_ipstr(session.c->local_addr),
ntohs(pr_netaddr_get_port(session.c->local_addr)));
pr_response_send(R_530, _("Unable to connect to %s: %s"),
proxy_conn_get_hostport(pconn), strerror(EPERM));
return 1;
}
proxy_sess->dst_addr = remote_addr;
proxy_sess->other_addrs = other_addrs;
proxy_sess->dst_pconn = pconn;
if (flags & PROXY_FORWARD_USER_PASSTHRU_FL_PARSE_DSTADDR) {
/* Change the command so that it no longer includes the proxy info. */
user_cmd = pr_cmd_alloc(cmd->pool, 2, C_USER, user);
user_cmd->arg = user;
} else {
user_cmd = cmd;
}
} else {
user_cmd = cmd;
}
if (flags & PROXY_FORWARD_USER_PASSTHRU_FL_CONNECT_DSTADDR) {
pr_response_t *banner = NULL;
unsigned int banner_nlines = 0;
res = forward_connect(proxy_pool, proxy_sess, &banner, &banner_nlines);
if (res < 0) {
xerrno = errno;
*successful = FALSE;
/* Send a failed USER response to our waiting frontend client, but do
* not necessarily close the frontend connection.
*/
resp = pcalloc(cmd->tmp_pool, sizeof(pr_response_t));
resp->num = R_530;
if (banner != NULL) {
resp->msg = banner->msg;
resp_nlines = banner_nlines;
} else {
resp->msg = pstrcat(cmd->tmp_pool, "Unable to connect to ",
proxy_conn_get_hostport(proxy_sess->dst_pconn), ": ",
strerror(xerrno), NULL);
resp_nlines = 1;
}
res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool,
proxy_sess->frontend_ctrl_conn, resp, resp_nlines);
if (res < 0) {
xerrno = errno;
pr_response_block(TRUE);
errno = xerrno;
return -1;
}
errno = EINVAL;
return 1;
}
}
res = proxy_ftp_ctrl_send_cmd(cmd->tmp_pool, proxy_sess->backend_ctrl_conn,
user_cmd);
if (res < 0) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error sending %s to backend: %s", (char *) user_cmd->argv[0],
strerror(xerrno));
errno = xerrno;
return -1;
}
resp = proxy_ftp_ctrl_recv_resp(cmd->tmp_pool, proxy_sess->backend_ctrl_conn,
&resp_nlines, 0);
if (resp == NULL) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error receiving %s response from backend: %s", (char *) cmd->argv[0],
strerror(xerrno));
errno = xerrno;
return -1;
}
if (resp->num[0] == '2' ||
resp->num[0] == '3') {
*successful = TRUE;
if (strcmp(resp->num, R_232) == 0) {
proxy_sess_state |= PROXY_SESS_STATE_BACKEND_AUTHENTICATED;
pr_timer_remove(PR_TIMER_LOGIN, ANY_MODULE);
}
}
/* XXX TODO: Concatenate the banner from the connect with the USER response
* message here, and send the entire kit to the frontend client, e.g.:
*
* Name (gatekeeper:you): anonymous@ftp.uu.net
* 331-(----GATEWAY CONNECTED TO ftp.uu.net----)
* 331-(220 ftp.uu.net FTP server (SunOS 4.1) ready.
* 331 Guest login ok, send ident as password.
* Password: ######
* 230 Guest login ok, access restrictions apply.
* ftp> dir
*/
res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool, proxy_sess->frontend_ctrl_conn,
resp, resp_nlines);
if (res < 0) {
xerrno = errno;
pr_response_block(TRUE);
errno = xerrno;
return -1;
}
return 1;
}
static int forward_handle_user_proxyuserwithproxyauth(cmd_rec *cmd,
struct proxy_session *proxy_sess, int *successful, int *block_responses) {
int flags = 0, res;
if (!(proxy_sess_state & PROXY_SESS_STATE_PROXY_AUTHENTICATED)) {
char *user = NULL;
const struct proxy_conn *pconn = NULL;
const pr_netaddr_t *remote_addr = NULL;
array_header *other_addrs = NULL;
res = forward_cmd_parse_dst(cmd->pool, cmd->arg, &user, &pconn);
if (res < 0) {
errno = EINVAL;
return -1;
}
remote_addr = proxy_conn_get_addr(pconn, &other_addrs);
proxy_sess->dst_addr = remote_addr;
proxy_sess->other_addrs = other_addrs;
proxy_sess->dst_pconn = pconn;
/* Rewrite the USER command here with the trimmed/truncated name. */
pr_cmd_clear_cache(cmd);
cmd->arg = cmd->argv[1] = pstrdup(cmd->pool, user);
/* By returning zero here, we let the rest of the proftpd internals
* deal with the USER command locally, leading to proxy auth.
*/
*block_responses = FALSE;
return 0;
}
flags = PROXY_FORWARD_USER_PASSTHRU_FL_CONNECT_DSTADDR;
res = forward_handle_user_passthru(cmd, proxy_sess, successful, flags);
return res;
}
static int forward_handle_user_userwithproxyauth(cmd_rec *cmd,
struct proxy_session *proxy_sess, int *successful, int *block_responses) {
int flags = 0, res;
if (!(proxy_sess_state & PROXY_SESS_STATE_PROXY_AUTHENTICATED)) {
/* By returning zero here, we let the rest of the proftpd internals
* deal with the USER command locally, leading to proxy auth.
*/
*block_responses = FALSE;
return 0;
}
flags = PROXY_FORWARD_USER_PASSTHRU_FL_PARSE_DSTADDR|PROXY_FORWARD_USER_PASSTHRU_FL_CONNECT_DSTADDR;
res = forward_handle_user_passthru(cmd, proxy_sess, successful, flags);
return res;
}
int proxy_forward_handle_user(cmd_rec *cmd, struct proxy_session *proxy_sess,
int *successful, int *block_responses) {
int res = -1;
/* Look at our proxy method to see what we should do here. */
switch (proxy_method) {
case PROXY_FORWARD_METHOD_USER_NO_PROXY_AUTH: {
int flags = PROXY_FORWARD_USER_PASSTHRU_FL_PARSE_DSTADDR|PROXY_FORWARD_USER_PASSTHRU_FL_CONNECT_DSTADDR;
res = forward_handle_user_passthru(cmd, proxy_sess, successful, flags);
break;
}
case PROXY_FORWARD_METHOD_USER_SNI_NO_PROXY_AUTH: {
int flags = PROXY_FORWARD_USER_PASSTHRU_FL_SNI_DSTADDR|PROXY_FORWARD_USER_PASSTHRU_FL_CONNECT_DSTADDR;
/* This method requires use of TLS with SNI; make sure that that is
* actually the case for this session.
*/
if (session.rfc2228_mech != NULL &&
strcmp(session.rfc2228_mech, "TLS") == 0) {
res = forward_handle_user_passthru(cmd, proxy_sess, successful, flags);
} else {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to use ProxyForwardMethod 'user@sni' due to lack of TLS");
errno = EINVAL;
res = -1;
}
break;
}
case PROXY_FORWARD_METHOD_USER_WITH_PROXY_AUTH:
res = forward_handle_user_userwithproxyauth(cmd, proxy_sess,
successful, block_responses);
break;
case PROXY_FORWARD_METHOD_PROXY_USER_WITH_PROXY_AUTH:
res = forward_handle_user_proxyuserwithproxyauth(cmd, proxy_sess,
successful, block_responses);
break;
default:
errno = ENOSYS;
res = -1;
}
return res;
}
static int forward_handle_pass_passthru(cmd_rec *cmd,
struct proxy_session *proxy_sess, int *successful) {
int res, xerrno;
pr_response_t *resp;
unsigned int resp_nlines = 0;
res = proxy_ftp_ctrl_send_cmd(cmd->tmp_pool, proxy_sess->backend_ctrl_conn,
cmd);
if (res < 0) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error sending %s to backend: %s", (char *) cmd->argv[0],
strerror(xerrno));
errno = xerrno;
return -1;
}
resp = proxy_ftp_ctrl_recv_resp(cmd->tmp_pool, proxy_sess->backend_ctrl_conn,
&resp_nlines, 0);
if (resp == NULL) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error receiving %s response from backend: %s", (char *) cmd->argv[0],
strerror(xerrno));
/* If we receive an EPERM here, it is probably because the backend
* closed its control connection, yielding an EOF. To better indicate
* this situation, propagate the error using EPIPE.
*/
if (xerrno == EPERM) {
xerrno = EPIPE;
}
errno = xerrno;
return -1;
}
/* XXX What about other response codes for PASS? */
if (resp->num[0] == '2') {
*successful = TRUE;
proxy_sess_state |= PROXY_SESS_STATE_BACKEND_AUTHENTICATED;
}
res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool, proxy_sess->frontend_ctrl_conn,
resp, resp_nlines);
if (res < 0) {
xerrno = errno;
pr_response_block(TRUE);
errno = xerrno;
return -1;
}
return 1;
}
static int forward_handle_pass_userwithproxyauth(cmd_rec *cmd,
struct proxy_session *proxy_sess, int *successful, int *block_responses) {
if (!(proxy_sess_state & PROXY_SESS_STATE_PROXY_AUTHENTICATED)) {
int res;
const char *user;
user = pr_table_get(session.notes, "mod_auth.orig-user", NULL);
res = proxy_session_check_password(cmd->pool, user, cmd->arg);
if (res < 0) {
errno = EINVAL;
return -1;
}
res = proxy_session_setup_env(proxy_pool, user,
PROXY_SESSION_FL_CHECK_LOGIN_ACL);
if (res < 0) {
errno = EINVAL;
return -1;
}
if (session.auth_mech) {
pr_log_debug(DEBUG2, "user '%s' authenticated by %s", user,
session.auth_mech);
}
pr_response_send(R_230, _("User %s logged in"), user);
return 1;
}
return forward_handle_pass_passthru(cmd, proxy_sess, successful);
}
static int forward_handle_pass_proxyuserwithproxyauth(cmd_rec *cmd,
struct proxy_session *proxy_sess, int *successful, int *block_responses) {
/* The functionality is identical to that of handle_pass_userwithproxyauth. */
return forward_handle_pass_userwithproxyauth(cmd, proxy_sess, successful,
block_responses);
}
int proxy_forward_handle_pass(cmd_rec *cmd, struct proxy_session *proxy_sess,
int *successful, int *block_responses) {
int res = -1, xerrno = 0;
/* Look at our proxy method to see what we should do here. */
switch (proxy_method) {
case PROXY_FORWARD_METHOD_USER_NO_PROXY_AUTH:
case PROXY_FORWARD_METHOD_USER_SNI_NO_PROXY_AUTH:
res = forward_handle_pass_passthru(cmd, proxy_sess, successful);
xerrno = errno;
if (res == 1) {
pr_timer_remove(PR_TIMER_LOGIN, ANY_MODULE);
}
break;
case PROXY_FORWARD_METHOD_USER_WITH_PROXY_AUTH:
res = forward_handle_pass_userwithproxyauth(cmd, proxy_sess,
successful, block_responses);
xerrno = errno;
if (res == 1) {
pr_timer_remove(PR_TIMER_LOGIN, ANY_MODULE);
}
break;
case PROXY_FORWARD_METHOD_PROXY_USER_WITH_PROXY_AUTH:
res = forward_handle_pass_proxyuserwithproxyauth(cmd, proxy_sess,
successful, block_responses);
xerrno = errno;
if (res == 1) {
pr_timer_remove(PR_TIMER_LOGIN, ANY_MODULE);
}
break;
default:
xerrno = ENOSYS;
res = -1;
}
errno = xerrno;
return res;
}
int proxy_forward_get_method(const char *method) {
if (method == NULL) {
errno = EINVAL;
return -1;
}
if (strcasecmp(method, "proxyuser,user@host") == 0) {
return PROXY_FORWARD_METHOD_USER_WITH_PROXY_AUTH;
} else if (strcasecmp(method, "user@host") == 0) {
return PROXY_FORWARD_METHOD_USER_NO_PROXY_AUTH;
} else if (strcasecmp(method, "proxyuser@host,user") == 0) {
return PROXY_FORWARD_METHOD_PROXY_USER_WITH_PROXY_AUTH;
} else if (strcasecmp(method, "user@sni") == 0) {
return PROXY_FORWARD_METHOD_USER_SNI_NO_PROXY_AUTH;
}
errno = ENOENT;
return -1;
}
proftpd-mod_proxy-0.9.5/lib/proxy/ftp/ 0000775 0000000 0000000 00000000000 14757370167 0017762 5 ustar 00root root 0000000 0000000 proftpd-mod_proxy-0.9.5/lib/proxy/ftp/conn.c 0000664 0000000 0000000 00000024514 14757370167 0021071 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy FTP connection routines
* Copyright (c) 2013-2025 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "include/proxy/inet.h"
#include "include/proxy/netio.h"
#include "include/proxy/ftp/conn.h"
static const char *trace_channel = "proxy.ftp.conn";
static int set_conn_socket_opts(pool *p, conn_t *conn, int rcvbufsz,
int sndbufsz, struct tcp_keepalive *keepalive, int reuse_port) {
int res;
#if PROFTPD_VERSION_NUMBER >= 0x0001030801
res = pr_inet_set_socket_opts2(p, conn, rcvbufsz, sndbufsz, keepalive,
reuse_port);
#else
res = pr_inet_set_socket_opts(p, conn, rcvbufsz, sndbufsz, keepalive);
/* Earlier versions of ProFTPD did not support setting the SO_REUSEPORT
* socket option via pr_inet_set_socket_opts(), so we do it ourselves.
*
* For active data transfers, enabling SO_REUSEPORT can be very useful,
* since the number/range of available source ports may be small.
*/
# if defined(SO_REUSEPORT)
if (setsockopt(conn->listen_fd, SOL_SOCKET, SO_REUSEPORT,
(void *) &reuse_port, sizeof(reuse_port)) < 0) {
pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error setting SO_REUSEPORT on fd %d: %s", conn->listen_fd,
strerror(errno));
} else {
pr_trace_msg(trace_channel, 8,
"set socket fd %d reuseport = %d", conn->listen_fd, reuse_port);
}
# endif /* SO_REUSEPORT */
#endif /* ProFTPD 1.3.8rc1 or later */
return res;
}
conn_t *proxy_ftp_conn_accept(pool *p, conn_t *data_conn, conn_t *ctrl_conn,
int frontend_data) {
conn_t *conn;
int reverse_dns;
if (p == NULL ||
data_conn == NULL ||
ctrl_conn == NULL) {
errno = EINVAL;
return NULL;
}
reverse_dns = pr_netaddr_set_reverse_dns(ServerUseReverseDNS);
if (session.xfer.direction == PR_NETIO_IO_RD) {
set_conn_socket_opts(data_conn->pool, data_conn,
(main_server->tcp_rcvbuf_override ? main_server->tcp_rcvbuf_len : 0), 0,
main_server->tcp_keepalive, 0);
} else {
set_conn_socket_opts(data_conn->pool, data_conn,
0, (main_server->tcp_sndbuf_override ? main_server->tcp_sndbuf_len : 0),
main_server->tcp_keepalive, 0);
}
if (frontend_data) {
conn = pr_inet_accept(session.pool, data_conn, ctrl_conn, -1, -1, TRUE);
} else {
conn = proxy_inet_accept(session.pool, data_conn, ctrl_conn, -1, -1, TRUE);
}
pr_netaddr_set_reverse_dns(reverse_dns);
if (conn == NULL) {
int xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error accepting backend data connection: %s", strerror(xerrno));
errno = xerrno;
return NULL;
}
/* Check for error conditions. */
if (conn->mode == CM_ERROR) {
int xerrno = conn->xerrno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error accepting backend data connection: %s", strerror(xerrno));
destroy_pool(conn->pool);
errno = xerrno;
return NULL;
}
if (frontend_data) {
pr_pool_tag(conn->pool, "proxy frontend data accept conn pool");
} else {
pr_pool_tag(conn->pool, "proxy backend data accept conn pool");
}
pr_trace_msg(trace_channel, 9,
"accepted connection from server '%s'", conn->remote_name);
return conn;
}
conn_t *proxy_ftp_conn_connect(pool *p, const pr_netaddr_t *bind_addr,
const pr_netaddr_t *remote_addr, int frontend_data) {
conn_t *conn, *opened = NULL;
int default_inet_family = 0, remote_family, res, reverse_dns, xerrno;
if (p == NULL ||
remote_addr == NULL) {
errno = EINVAL;
return NULL;
}
remote_family = pr_netaddr_get_family(remote_addr);
pr_trace_msg(trace_channel, 9,
"using %s family for backend socket address %s",
remote_family == AF_INET ? "IPv4" : "IPv6",
pr_netaddr_get_ipstr(remote_addr));
default_inet_family = pr_inet_set_default_family(p, remote_family);
conn = pr_inet_create_conn(session.pool, -1, bind_addr, INPORT_ANY, TRUE);
xerrno = errno;
if (conn == NULL) {
pr_inet_set_default_family(p, default_inet_family);
errno = xerrno;
return NULL;
}
reverse_dns = pr_netaddr_set_reverse_dns(ServerUseReverseDNS);
if (session.xfer.direction == PR_NETIO_IO_RD) {
set_conn_socket_opts(conn->pool, conn,
(main_server->tcp_rcvbuf_override ? main_server->tcp_rcvbuf_len : 0), 0,
main_server->tcp_keepalive, 1);
} else {
set_conn_socket_opts(conn->pool, conn,
0, (main_server->tcp_sndbuf_override ? main_server->tcp_sndbuf_len : 0),
main_server->tcp_keepalive, 1);
}
pr_inet_set_proto_opts(session.pool, conn,
main_server->tcp_mss_len, 1, IPTOS_THROUGHPUT, 1);
pr_inet_generate_socket_event("proxy.data-connect", main_server,
conn->local_addr, conn->listen_fd);
pr_trace_msg(trace_channel, 9, "connecting to %s#%u from %s#%u",
pr_netaddr_get_ipstr(remote_addr), ntohs(pr_netaddr_get_port(remote_addr)),
pr_netaddr_get_ipstr(bind_addr), ntohs(pr_netaddr_get_port(bind_addr)));
if (frontend_data == TRUE) {
res = pr_inet_connect(p, conn, remote_addr,
ntohs(pr_netaddr_get_port(remote_addr)));
} else {
res = proxy_inet_connect(p, conn, remote_addr,
ntohs(pr_netaddr_get_port(remote_addr)));
}
if (res < 0) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to connect to %s#%u: %s\n", pr_netaddr_get_ipstr(remote_addr),
ntohs(pr_netaddr_get_port(remote_addr)), strerror(xerrno));
if (frontend_data == FALSE) {
proxy_inet_close(session.pool, conn);
}
pr_inet_close(session.pool, conn);
errno = xerrno;
return NULL;
}
/* XXX Will it always be STRM_DATA? */
if (frontend_data == TRUE) {
opened = pr_inet_openrw(session.pool, conn, NULL, PR_NETIO_STRM_DATA,
conn->listen_fd, -1, -1, TRUE);
} else {
opened = proxy_inet_openrw(session.pool, conn, NULL, PR_NETIO_STRM_DATA,
conn->listen_fd, -1, -1, TRUE);
}
pr_netaddr_set_reverse_dns(reverse_dns);
if (opened == NULL) {
xerrno = errno;
if (frontend_data == FALSE) {
proxy_inet_close(session.pool, conn);
}
pr_inet_close(session.pool, conn);
errno = xerrno;
return NULL;
}
/* The conn returned by pr_inet_openrw() is a copy of the input conn;
* we no longer need the input conn at this point.
*/
if (frontend_data == TRUE) {
pr_inet_close(session.pool, conn);
pr_pool_tag(opened->pool, "proxy frontend data connect conn pool");
} else {
proxy_inet_close(session.pool, conn);
pr_inet_close(session.pool, conn);
pr_pool_tag(opened->pool, "proxy backend data connect conn pool");
}
pr_inet_set_nonblock(session.pool, opened);
pr_trace_msg(trace_channel, 9,
"connected to server '%s'", opened->remote_name);
return opened;
}
conn_t *proxy_ftp_conn_listen(pool *p, const pr_netaddr_t *bind_addr,
int frontend_data) {
int res;
conn_t *conn = NULL;
config_rec *c;
if (p == NULL ||
bind_addr == NULL) {
errno = EINVAL;
return NULL;
}
c = find_config(main_server->conf, CONF_PARAM, "PassivePorts", FALSE);
if (c != NULL) {
int pasv_min_port = *((int *) c->argv[0]);
int pasv_max_port = *((int *) c->argv[1]);
conn = pr_inet_create_conn_portrange(session.pool, bind_addr,
pasv_min_port, pasv_max_port);
if (conn == NULL) {
/* If not able to open a passive port in the given range, default to
* normal behavior (using INPORT_ANY), and log the failure. This
* indicates a too-small range configuration.
*/
pr_log_pri(PR_LOG_WARNING,
"unable to find open port in PassivePorts range %d-%d: "
"defaulting to INPORT_ANY (consider defining a larger PassivePorts "
"range)", pasv_min_port, pasv_max_port);
}
}
if (conn == NULL) {
conn = pr_inet_create_conn(session.pool, -1, bind_addr, INPORT_ANY, FALSE);
}
if (conn == NULL) {
int xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error creating socket: %s", strerror(xerrno));
errno = EINVAL;
return NULL;
}
/* Make sure that necessary socket options are set on the socket prior
* to the call to listen(2).
*/
pr_inet_set_proto_opts(session.pool, conn, main_server->tcp_mss_len, 1,
IPTOS_THROUGHPUT, 1);
pr_inet_generate_socket_event("proxy.data-listen", main_server,
conn->local_addr, conn->listen_fd);
pr_inet_set_block(session.pool, conn);
if (frontend_data) {
res = pr_inet_listen(session.pool, conn, 1, 0);
} else {
res = proxy_inet_listen(session.pool, conn, 1, 0);
}
if (res < 0) {
int xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to listen on %s#%u: %s", pr_netaddr_get_ipstr(bind_addr),
ntohs(pr_netaddr_get_port(bind_addr)), strerror(xerrno));
if (!frontend_data) {
proxy_inet_close(session.pool, conn);
}
pr_inet_close(session.pool, conn);
errno = xerrno;
return NULL;
}
if (frontend_data) {
pr_pool_tag(conn->pool, "proxy frontend data listen conn pool");
conn->instrm = pr_netio_open(session.pool, PR_NETIO_STRM_DATA,
conn->listen_fd, PR_NETIO_IO_RD);
conn->outstrm = pr_netio_open(session.pool, PR_NETIO_STRM_DATA,
conn->listen_fd, PR_NETIO_IO_WR);
} else {
pr_pool_tag(conn->pool, "proxy backend data listen conn pool");
conn->instrm = proxy_netio_open(session.pool, PR_NETIO_STRM_DATA,
conn->listen_fd, PR_NETIO_IO_RD);
conn->outstrm = proxy_netio_open(session.pool, PR_NETIO_STRM_DATA,
conn->listen_fd, PR_NETIO_IO_WR);
}
return conn;
}
proftpd-mod_proxy-0.9.5/lib/proxy/ftp/ctrl.c 0000664 0000000 0000000 00000040003 14757370167 0021067 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy FTP control conn routines
* Copyright (c) 2012-2023 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "proxy/netio.h"
#include "proxy/ftp/ctrl.h"
#include "proxy/tls.h"
static const char *trace_channel = "proxy.ftp.ctrl";
static char *ftp_telnet_gets(char *buf, size_t buflen,
pr_netio_stream_t *nstrm, conn_t *conn) {
char *buf_ptr = buf;
unsigned char cp;
int nread, saw_newline = FALSE;
pr_buffer_t *pbuf = NULL;
if (buflen == 0 ||
nstrm == NULL ||
conn == NULL) {
errno = EINVAL;
return NULL;
}
buflen--;
if (nstrm->strm_buf != NULL) {
pbuf = nstrm->strm_buf;
} else {
pbuf = pr_netio_buffer_alloc(nstrm);
}
while (buflen > 0) {
/* Is the buffer empty? */
if (pbuf->current == NULL ||
pbuf->remaining == pbuf->buflen) {
nread = proxy_netio_read(nstrm, pbuf->buf,
(buflen < pbuf->buflen ? buflen : pbuf->buflen), 4);
if (nread <= 0) {
if (buf_ptr != buf) {
*buf_ptr = '\0';
return buf;
}
if (nread == 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"read EOF from %s", conn->remote_name);
errno = EPERM;
}
return NULL;
}
pbuf->remaining = pbuf->buflen - nread;
pbuf->current = pbuf->buf;
pr_event_generate("mod_proxy.ctrl-read", pbuf);
}
nread = pbuf->buflen - pbuf->remaining;
/* Expensive copying of bytes while we look for the trailing LF. */
while (buflen > 0 &&
nread > 0 &&
*pbuf->current != '\n' &&
nread--) {
pr_signals_handle();
cp = *pbuf->current++;
pbuf->remaining++;
*buf_ptr++ = cp;
buflen--;
}
if (buflen > 0 &&
nread > 0 &&
*pbuf->current == '\n') {
buflen--;
nread--;
*buf_ptr++ = *pbuf->current++;
pbuf->remaining++;
saw_newline = TRUE;
break;
}
if (nread == 0) {
pbuf->current = NULL;
}
}
if (saw_newline == FALSE) {
/* If we haven't seen a newline, then assume the server is deliberately
* sending a too-long response, trying to exploit buffer sizes and make
* the proxy make some possibly bad assumptions.
*/
errno = E2BIG;
return NULL;
}
*buf_ptr = '\0';
return buf;
}
pr_response_t *proxy_ftp_ctrl_recv_resp(pool *p, conn_t *ctrl_conn,
unsigned int *nlines, int flags) {
char buf[PR_TUNABLE_BUFFER_SIZE];
pr_response_t *resp = NULL;
int multi_line = FALSE;
unsigned int count = 0;
if (p == NULL ||
ctrl_conn == NULL ||
nlines == NULL) {
errno = EINVAL;
return NULL;
}
while (TRUE) {
char c, *ptr;
int resp_code;
size_t buflen;
pr_signals_handle();
memset(buf, '\0', sizeof(buf));
if (ftp_telnet_gets(buf, sizeof(buf)-1, ctrl_conn->instrm,
ctrl_conn) == NULL) {
int xerrno = errno;
pr_trace_msg(trace_channel, 9,
"error reading telnet data: %s", strerror(xerrno));
errno = xerrno;
return NULL;
}
buflen = strlen(buf);
/* TODO: What if the given buffer does not end in a CR/LF? What if the
* backend server is spewing response lines longer than our buffer?
*/
/* Remove any trailing CRs, LFs. */
while (buflen > 0 &&
(buf[buflen-1] == '\r' || buf[buflen-1] == '\n')) {
pr_signals_handle();
buf[buflen-1] = '\0';
buflen--;
}
if (buflen == 0 &&
(flags & PROXY_FTP_CTRL_FL_IGNORE_BLANK_RESP)) {
pr_trace_msg(trace_channel, 19, "%s",
"skipping blank response line from backend server");
continue;
}
/* If we are the first line of the response, the first three characters
* MUST be numeric, followed by a hypen. Anything else is nonconformant
* with RFC 959.
*
* If we are NOT the first line of the response, then we are probably
* handling a multi-line response. If the first character is a space, then
* this is a continuation line. Otherwise, the first three characters
* MUST be numeric, AND MUST match the numeric code from the first line.
* This indicates the last line in the multi-line response -- and the
* character after the numerics MUST be a space.
*
* Unfortunately, some FTP servers (IIS, for instance) will use multi-line
* responses whose continuation lines do NOT start with the mandated
* space (as for a multi-line STAT response on a file, for example). Sigh.
*/
if (resp == NULL) {
/* First line of a possibly multi-line response (or just the only
* line).
*/
if (buflen < 4) {
pr_trace_msg(trace_channel, 12,
"read %lu characters of response, needed at least %d",
(unsigned long) buflen, 4);
errno = EINVAL;
return NULL;
}
if (!PR_ISDIGIT((int) buf[0]) ||
!PR_ISDIGIT((int) buf[1]) ||
!PR_ISDIGIT((int) buf[2])) {
pr_trace_msg(trace_channel, 1,
"non-numeric characters in start of response data: '%c%c%c'",
buf[0], buf[1], buf[2]);
errno = EINVAL;
return NULL;
}
/* If this is a space, then we have a single line response. If it
* is a hyphen, then this is the first line of a multi-line response.
*/
if (buf[3] != ' ' &&
buf[3] != '-') {
pr_trace_msg(trace_channel, 1,
"unexpected character '%c' following numeric response code", buf[3]);
errno = EINVAL;
return NULL;
}
if (buf[3] == '-') {
multi_line = TRUE;
}
count++;
resp = (pr_response_t *) pcalloc(p, sizeof(pr_response_t));
} else {
if (buflen >= 1) {
/* TODO: We should have a limit for how large of a buffered response
* we will tolerate. Consider a malicious/buggy backend server whose
* multi-line response is in the GB?
*
* One way to avoid the buffering would be to relay each individual
* response line, as we read them, to the frontend client. But if
* we do so, then we will not be properly acting as an FTP protocol
* sanitizer, either. Hrm.
*/
if (buf[0] == ' ') {
/* Continuation line; append it the existing response. */
if (buflen > 1) {
resp->msg = pstrcat(p, resp->msg, "\r\n", buf, NULL);
}
count++;
continue;
} else {
/* Possible ending line of multi-line response. */
if (buflen < 4) {
errno = EINVAL;
return NULL;
}
if (!PR_ISDIGIT((int) buf[0]) ||
!PR_ISDIGIT((int) buf[1]) ||
!PR_ISDIGIT((int) buf[2])) {
pr_trace_msg(trace_channel, 1,
"non-numeric characters in end of response data: '%c%c%c'",
buf[0], buf[1], buf[2]);
/* NOTE: We could/should be strict here, and require conformant
* responses only. For now, though, we'll proxy through the
* backend's response to the frontend client, to let it decide
* how it wants to handle this response data.
*/
resp->msg = pstrcat(p, resp->msg, "\r\n", buf, NULL);
count++;
continue;
}
if (buf[3] != ' ') {
/* NOTE: We could/should be strict here, and require conformant
* responses only. For now, though, we'll proxy through the
* backend's response to the frontend client, to let it decide
* how it wants to handle this response data.
*/
resp->msg = pstrcat(p, resp->msg, "\r\n", buf, NULL);
count++;
continue;
}
count++;
}
}
}
ptr = &(buf[3]);
c = *ptr;
*ptr = '\0';
resp_code = atoi(buf);
if (resp_code < 100 ||
resp_code >= 700) {
/* Outside of the expected/defined FTP response code range. */
pr_trace_msg(trace_channel, 1,
"invalid FTP response code %d received", resp_code);
errno = EINVAL;
return NULL;
}
if (resp->num == NULL) {
resp->num = pstrdup(p, buf);
} else {
/* Make sure the last line of the multi-line response uses the same
* response code.
*/
if (strncmp(resp->num, buf, 3) != 0) {
pr_trace_msg(trace_channel, 1,
"invalid multi-line FTP response: mismatched starting response "
"code (%s) and ending response code (%s)", resp->num, buf);
errno = EINVAL;
return NULL;
}
}
if (resp->msg == NULL) {
if (buflen > 4) {
if (multi_line == TRUE) {
*ptr = c;
resp->msg = pstrdup(p, ptr);
*ptr = '\0';
} else {
resp->msg = pstrdup(p, ptr + 1);
}
} else {
resp->msg = "";
}
/* If the character after the response code was a space, then this is
* a single line response; we can be done now.
*/
if (c == ' ') {
break;
}
} else {
if (buflen > 4) {
if (multi_line == TRUE) {
*ptr = c;
/* This the last line of a multi-line response, which means we
* need the ENTIRE line, including the response code.
*/
resp->msg = pstrcat(p, resp->msg, "\r\n", buf, NULL);
} else {
resp->msg = pstrcat(p, resp->msg, "\r\n", ptr + 1, NULL);
}
}
break;
}
}
*nlines = count;
pr_trace_msg(trace_channel, 9,
"received '%s%s%s' response from backend to frontend",
resp->num, multi_line ? "-" : " ", resp->msg);
return resp;
}
#ifndef TELNET_DM
# define TELNET_DM 242
#endif /* TELNET_DM */
#ifndef TELNET_IAC
# define TELNET_IAC 255
#endif /* TELNET_IAC */
#ifndef TELNET_IP
# define TELNET_IP 244
#endif /* TELNET_IP */
int proxy_ftp_ctrl_send_abort(pool *p, conn_t *ctrl_conn, cmd_rec *cmd) {
int fd, res, use_tls, xerrno;
unsigned char buf[7];
if (p == NULL ||
ctrl_conn == NULL ||
cmd == NULL) {
errno = EINVAL;
return -1;
}
/* If we are proxying the ABOR command, preface it with the Telnet "Sync"
* mechanism, using OOB data. If the receiving server supports this, it can
* generate a signal to interrupt any IO occurring on the backend server
* (such as when sendfile(2) is used).
*
* Note that such Telnet codes can only be used if we are NOT using TLS
* on the backend control connection.
*/
use_tls = proxy_tls_using_tls();
if (use_tls != PROXY_TLS_ENGINE_OFF) {
return proxy_ftp_ctrl_send_cmd(p, ctrl_conn, cmd);
}
fd = PR_NETIO_FD(ctrl_conn->outstrm);
buf[0] = TELNET_IAC;
buf[1] = TELNET_IP;
buf[2] = TELNET_IAC;
pr_trace_msg(trace_channel, 9,
"sending Telnet abort code out-of-band to backend");
res = send(fd, &buf, 3, MSG_OOB);
xerrno = errno;
if (res < 0) {
pr_trace_msg(trace_channel, 1,
"error sending Telnet abort code out-of-band to backend: %s",
strerror(xerrno));
errno = xerrno;
return -1;
}
buf[0] = TELNET_DM;
buf[1] = 'A';
buf[2] = 'B';
buf[3] = 'O';
buf[4] = 'R';
buf[5] = '\r';
buf[6] = '\n';
pr_trace_msg(trace_channel, 9,
"proxied %s command from frontend to backend", (char *) cmd->argv[0]);
res = send(fd, &buf, 7, 0);
xerrno = errno;
if (res < 0) {
pr_trace_msg(trace_channel, 1,
"error sending Telnet DM code to backend: %s", strerror(xerrno));
errno = xerrno;
return -1;
}
return 0;
}
int proxy_ftp_ctrl_send_cmd(pool *p, conn_t *ctrl_conn, cmd_rec *cmd) {
int res;
if (p == NULL ||
ctrl_conn == NULL ||
cmd == NULL) {
errno = EINVAL;
return -1;
}
if (cmd->argc > 1) {
const char *display_str;
size_t display_len = 0;
display_str = pr_cmd_get_displayable_str(cmd, &display_len);
pr_trace_msg(trace_channel, 9,
"proxied command '%s' from frontend to backend", display_str);
res = proxy_netio_printf(ctrl_conn->outstrm, "%s %s\r\n",
(char *) cmd->argv[0], cmd->arg);
} else {
pr_trace_msg(trace_channel, 9,
"proxied %s command from frontend to backend", (char *) cmd->argv[0]);
res = proxy_netio_printf(ctrl_conn->outstrm, "%s\r\n",
(char *) cmd->argv[0]);
}
return res;
}
int proxy_ftp_ctrl_send_resp(pool *p, conn_t *ctrl_conn, pr_response_t *resp,
unsigned int resp_nlines) {
pool *curr_pool;
(void) ctrl_conn;
if (p == NULL ||
resp == NULL) {
errno = EINVAL;
return -1;
}
pr_trace_msg(trace_channel, 9,
"backend->frontend response: %s%s%s", resp->num,
resp_nlines <= 1 ? " " : "", resp->msg);
curr_pool = pr_response_get_pool();
if (curr_pool == NULL) {
pr_response_set_pool(p);
}
if (resp_nlines > 1) {
pr_response_send_raw("%s-%s", resp->num, resp->msg);
} else {
pr_response_send(resp->num, "%s", resp->msg);
}
pr_response_set_pool(curr_pool);
return 0;
}
int proxy_ftp_ctrl_handle_async(pool *p, conn_t *backend_conn,
conn_t *frontend_conn, int flags) {
if (p == NULL ||
backend_conn == NULL ||
backend_conn->instrm == NULL ||
frontend_conn == NULL) {
errno = EINVAL;
return -1;
}
if (!(proxy_sess_state & PROXY_SESS_STATE_CONNECTED)) {
/* Nothing to do if we're not yet connected to the backend server. */
return 0;
}
while (TRUE) {
fd_set rfds;
struct timeval tv;
int ctrlfd, res, xerrno = 0;
/* By using a timeout of zero, we effect a poll on the fd. */
tv.tv_sec = 0;
tv.tv_usec = 0;
pr_signals_handle();
FD_ZERO(&rfds);
ctrlfd = PR_NETIO_FD(backend_conn->instrm);
FD_SET(ctrlfd, &rfds);
res = select(ctrlfd + 1, &rfds, NULL, NULL, &tv);
if (res < 0) {
xerrno = errno;
if (xerrno == EINTR) {
pr_signals_handle();
continue;
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error calling select(2) on backend control connection (fd %d): %s",
ctrlfd, strerror(xerrno));
return 0;
}
if (res == 0) {
/* Nothing there. */
break;
}
pr_trace_msg(trace_channel, 19,
"select(2) reported %d for backend %s (fd %d)", res,
backend_conn->remote_name, ctrlfd);
if (FD_ISSET(ctrlfd, &rfds)) {
unsigned int resp_nlines = 0;
pr_response_t *resp;
pr_timer_reset(PR_TIMER_IDLE, ANY_MODULE);
pr_trace_msg(trace_channel, 9, "reading async response from backend %s",
backend_conn->remote_name);
resp = proxy_ftp_ctrl_recv_resp(p, backend_conn, &resp_nlines, flags);
if (resp == NULL) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error receiving response from backend control connection: %s",
strerror(xerrno));
errno = xerrno;
return -1;
}
res = proxy_ftp_ctrl_send_resp(p, frontend_conn, resp, resp_nlines);
if (res < 0) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error sending response to frontend control connection: %s",
strerror(xerrno));
errno = xerrno;
return -1;
}
}
break;
}
return 0;
}
proftpd-mod_proxy-0.9.5/lib/proxy/ftp/data.c 0000664 0000000 0000000 00000011170 14757370167 0021037 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy FTP data conn routines
* Copyright (c) 2012-2020 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "proxy/netio.h"
#include "proxy/ftp/data.h"
static const char *trace_channel = "proxy.ftp.data";
pr_buffer_t *proxy_ftp_data_recv(pool *p, conn_t *data_conn,
int frontend_data) {
int nread;
pr_buffer_t *pbuf = NULL;
if (p == NULL ||
data_conn == NULL ||
data_conn->instrm == NULL) {
errno = EINVAL;
return NULL;
}
if (data_conn->instrm->strm_buf != NULL) {
pbuf = data_conn->instrm->strm_buf;
} else {
pbuf = pr_netio_buffer_alloc(data_conn->instrm);
}
pbuf->current = pbuf->buf;
pbuf->remaining = pbuf->buflen;
while (TRUE) {
size_t avail_len;
if (frontend_data) {
nread = pr_netio_read(data_conn->instrm, pbuf->current,
pbuf->remaining, 1);
} else {
nread = proxy_netio_read(data_conn->instrm, pbuf->current,
pbuf->remaining, 1);
}
if (nread < 0) {
return NULL;
}
if (nread == 0) {
/* We might have had data left over in the buffer from a previous
* iteration of the loop, thus we return it as is.
*/
return pbuf;
}
pr_timer_reset(PR_TIMER_NOXFER, ANY_MODULE);
pr_timer_reset(PR_TIMER_STALLED, ANY_MODULE);
pr_timer_reset(PR_TIMER_IDLE, ANY_MODULE);
pr_trace_msg(trace_channel, 15, "received %d bytes of data", nread);
pbuf->current += nread;
pbuf->remaining -= nread;
pr_event_generate("mod_proxy.data-read", pbuf);
/* How much data is available in the buffer? It is possible that
* event listeners consumed that data entirely. If there is no available
* data left, we need to read more.
*/
avail_len = pbuf->current - pbuf->buf;
if (avail_len > 0) {
break;
}
}
return pbuf;
}
int proxy_ftp_data_send(pool *p, conn_t *data_conn, pr_buffer_t *pbuf,
int frontend_data) {
int nwrote;
char *buf;
size_t buflen;
if (p == NULL ||
data_conn == NULL ||
data_conn->outstrm == NULL ||
pbuf == NULL) {
errno = EINVAL;
return -1;
}
pr_event_generate("mod_proxy.data-write", pbuf);
/* Currently, we make the conn_t nonblocking (via pr_inet_set_nonblocking),
* BUT that does NOT set the nonblocking flag on the contained stream.
* Thus this write is actually a BLOCKING write -- which means that we will
* not need to worry about short writes here.
*
* In the future, we may want to make the streams nonblocking, but that
* makes mod_proxy a little more sensitive to the slow producer/consumer
* problem.
*/
buf = pbuf->buf;
buflen = pbuf->current - pbuf->buf;
pr_trace_msg(trace_channel, 25, "writing %lu bytes of data to %s",
(unsigned long) buflen,
frontend_data ? "frontend client" : "backend server");
if (frontend_data) {
nwrote = pr_netio_write(data_conn->outstrm, buf, buflen);
} else {
nwrote = proxy_netio_write(data_conn->outstrm, buf, buflen);
}
while (nwrote < 0) {
int xerrno = errno;
if (xerrno == EAGAIN) {
/* Since our socket is in non-blocking mode, write(2) can return
* EAGAIN if there is not enough from for our data yet. Handle
* this by delaying temporarily, then trying again.
*/
errno = EINTR;
pr_signals_handle();
if (frontend_data) {
nwrote = pr_netio_write(data_conn->outstrm, buf, buflen);
} else {
nwrote = proxy_netio_write(data_conn->outstrm, buf, buflen);
}
continue;
}
errno = xerrno;
return -1;
}
pr_timer_reset(PR_TIMER_NOXFER, ANY_MODULE);
pr_timer_reset(PR_TIMER_STALLED, ANY_MODULE);
pr_timer_reset(PR_TIMER_IDLE, ANY_MODULE);
return nwrote;
}
proftpd-mod_proxy-0.9.5/lib/proxy/ftp/dirlist.c 0000664 0000000 0000000 00000104212 14757370167 0021600 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy FTP dirlist routines
* Copyright (c) 2020-2025 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "proxy/str.h"
#include "proxy/ftp/dirlist.h"
#include "proxy/ftp/facts.h"
static unsigned long facts_opts = 0UL;
/* Tracks all of the state/context for parsing a single directory listing. */
struct dirlist_ctx {
pool *pool;
unsigned long opts;
/* Style/format of directory listing: Unix, Windows/DOS, etc. */
int list_style;
/* Skip the "total NNN" leadling line in some listings? */
unsigned char skip_total;
/* Accumulated unprocessed input data. In theory, this should never be
* more than a single line of text, minus the terminating LF.
*/
char *input_ptr, *input_text;
size_t input_textsz, input_textlen;
/* Accumulated output data. */
char *output_ptr, *output_text;
size_t output_textsz, output_textlen;
};
#define DIRLIST_LIST_STYLE_UNKNOWN 0
#define DIRLIST_LIST_STYLE_UNIX 1
#define DIRLIST_LIST_STYLE_WINDOWS 2
static const char *trace_channel = "proxy.ftp.dirlist";
int proxy_ftp_dirlist_init(pool *p, struct proxy_session *proxy_sess) {
struct dirlist_ctx *ctx;
pool *ctx_pool;
if (p == NULL ||
proxy_sess == NULL) {
errno = EINVAL;
return -1;
}
facts_opts = proxy_ftp_facts_get_opts();
ctx_pool = make_sub_pool(p);
pr_pool_tag(ctx_pool, "Proxy Dirlist Context Pool");
ctx = pcalloc(ctx_pool, sizeof(struct dirlist_ctx));
ctx->pool = ctx_pool;
ctx->opts = proxy_sess->dirlist_opts;
ctx->list_style = DIRLIST_LIST_STYLE_UNKNOWN;
ctx->skip_total = TRUE;
/* This is the maximum size of one line, per mod_ls. Be aware, however, that
* we may be talking to non-ProFTPD servers, whose behaviors will be different.
*/
ctx->input_textsz = (PR_TUNABLE_PATH_MAX * 2) + 256;
ctx->input_ptr = ctx->input_text = palloc(ctx_pool, ctx->input_textsz);
ctx->output_textsz = (pr_config_get_server_xfer_bufsz(PR_NETIO_IO_WR) * 64);
ctx->output_ptr = ctx->output_text = palloc(ctx_pool, ctx->output_textsz);
proxy_sess->dirlist_ctx = (void *) ctx;
return 0;
}
int proxy_ftp_dirlist_finish(struct proxy_session *proxy_sess) {
if (proxy_sess == NULL) {
errno = EINVAL;
return -1;
}
facts_opts = 0UL;
if (proxy_sess->dirlist_ctx != NULL) {
struct dirlist_ctx *ctx;
ctx = proxy_sess->dirlist_ctx;
destroy_pool(ctx->pool);
proxy_sess->dirlist_ctx = NULL;
}
return 0;
}
struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_dos(pool *p,
const char *text, size_t textlen, unsigned long opts) {
struct proxy_dirlist_fileinfo *pdf;
char *buf, *ptr;
size_t buflen, windows_ts_fmtlen = 17;
const char *windows_ts_fmt = "%m-%d-%y %I:%M%p";
if (p == NULL ||
text == NULL ||
textlen == 0) {
errno = EINVAL;
return NULL;
}
pr_trace_msg(trace_channel, 19, "parsing Windows text: '%.*s'",
(int) textlen, text);
/* 24 is the minimum length of a well-formatted Windows directory listing
* line.
*/
if (textlen < 24) {
pr_trace_msg(trace_channel, 3,
"error parsing Windows text (too short, need at least 24 bytes): '%.*s'",
(int) textlen, text);
errno = EINVAL;
return NULL;
}
pdf = pcalloc(p, sizeof(struct proxy_dirlist_fileinfo));
ptr = (char *) text;
buflen = 8;
buf = pstrndup(p, ptr, buflen);
if (strpbrk(buf, "0123456789-") == NULL) {
pr_trace_msg(trace_channel, 3,
"unexpected Windows date format: '%.*s'", (int) buflen, buf);
errno = EINVAL;
return NULL;
}
ptr += buflen;
if (strncmp(ptr, " ", 2) != 0) {
pr_trace_msg(trace_channel, 3,
"malformed Windows text (expected 2 spaces after date): '%.*s'",
(int) textlen, text); errno = EINVAL;
return NULL;
}
ptr += 2;
buflen = 7;
buf = pstrndup(p, ptr, buflen);
if (strpbrk(buf, "AMP0123456789:") == NULL) {
pr_trace_msg(trace_channel, 3, "unexpected Windows time format: '%.*s'",
(int) buflen, buf);
errno = EINVAL;
return NULL;
}
/* Some servers might mistakenly omit the AM/PM markers; try to handle these
* cases gracefully.
*/
if (strpbrk(buf, "AMP") == NULL) {
pr_trace_msg(trace_channel, 3,
"Windows time format lacks AM/PM marker, adjusting expectations");
windows_ts_fmt = "%m-%d-%y %I:%M";
windows_ts_fmtlen = 15;
}
pdf->tm = pcalloc(p, sizeof(struct tm));
buflen = windows_ts_fmtlen;
buf = pstrndup(p, text, buflen);
pr_trace_msg(trace_channel, 19,
"parsing Windows-style timestamp: '%.*s'", (int) buflen, buf);
if (strptime(buf, windows_ts_fmt, pdf->tm) == NULL) {
pr_trace_msg(trace_channel, 3,
"unexpected Windows timestamp format: '%.*s'", (int) buflen, buf);
errno = EINVAL;
return NULL;
}
ptr = (char *) text + buflen;
/* We now expect at least 7 spaces. */
if (strncmp(ptr, " ", 7) != 0) {
pr_trace_msg(trace_channel, 3,
"malformed Windows text (expected 7 spaces after timestamp): '%.*s'",
(int) textlen, text);
errno = EINVAL;
return NULL;
}
ptr += 7;
pdf->st = pcalloc(p, sizeof(struct stat));
if (strncmp(ptr, "", 5) == 0) {
pdf->st->st_mode |= S_IFDIR;
pdf->type = pstrdup(p, "dir");
/* For a directory, we expect the next 10 characters to be spaces. */
ptr += 5;
if (strncmp(ptr, " ", 10) != 0) {
pr_trace_msg(trace_channel, 3,
"malformed Windows text (expected 10 spaces after dir): '%.*s'",
(int) textlen, text);
errno = EINVAL;
return NULL;
}
ptr += 10;
} else if (strncmp(ptr, " ", 5) == 0) {
char *size_ptr;
off_t filesz;
pdf->st->st_mode |= S_IFREG;
pdf->type = pstrdup(p, "file");
/* For a file, we expect to see the file size within 9 characters or
* less.
*/
ptr += 5;
buflen = 9;
buf = pstrndup(p, ptr, buflen);
if (strpbrk(buf, "0123456789 ") == NULL) {
pr_trace_msg(trace_channel, 3,
"malformed Windows text (expected filesize with '%.*s'): '%.*s'",
(int) buflen, buf, (int) textlen, text);
errno = EINVAL;
return NULL;
}
size_ptr = strpbrk(buf, "0123456789");
if (size_ptr == NULL) {
pr_trace_msg(trace_channel, 3,
"malformed Windows text (expected filesize not found): '%.*s'",
(int) textlen, text);
errno = EINVAL;
return NULL;
}
pr_trace_msg(trace_channel, 19,
"parsing Windows-style filesize from '%s'", size_ptr);
if (pr_str_get_nbytes(size_ptr, NULL, &filesz) < 0) {
pr_trace_msg(trace_channel, 3,
"malformed Windows text (unable to parse filesize: %s): '%.*s'",
strerror(errno), (int) textlen, text);
errno = EINVAL;
return NULL;
}
pdf->st->st_size = filesz;
ptr += 9;
if (strncmp(ptr, " ", 1) != 0) {
pr_trace_msg(trace_channel, 3,
"malformed Windows text (missing space after filesize): '%.*s'",
(int) textlen, text);
errno = EINVAL;
return NULL;
}
ptr += 1;
} else {
pr_trace_msg(trace_channel, 3,
"malformed Windows text (unexpected spaces after timestamp): '%.*s'",
(int) textlen, text);
errno = EINVAL;
return NULL;
}
pdf->path = pstrdup(p, ptr);
return pdf;
}
static mode_t get_unix_mode(const char *text) {
mode_t perms = 0;
/* User permissions */
switch (text[0]) {
/* S_IRUSR only */
case 'r':
perms |= S_IRUSR;
break;
case '-':
break;
}
switch (text[1]) {
/* S_IWUSR only */
case 'w':
perms |= S_IWUSR;
break;
case '-':
break;
}
switch (text[2]) {
case 'S':
#if defined(S_ISUID)
perms |= S_ISUID;
#endif /* S_ISUID */
break;
/* S_ISUID + S_IXUSR */
case 's':
#if defined(S_ISUID)
perms |= S_ISUID;
perms |= S_IXUSR;
#endif /* S_ISUID */
break;
/* S_IXUSR only */
case 'x':
perms |= S_IXUSR;
break;
case '-':
break;
}
/* Group permissions */
switch (text[3]) {
case 'r':
perms |= S_IRGRP;
break;
case '-':
break;
}
switch (text[4]) {
case 'w':
perms |= S_IWGRP;
break;
case '-':
break;
}
switch (text[5]) {
case 'S':
#if defined(S_ISGID)
perms |= S_ISGID;
#endif /* S_ISGID */
break;
/* S_ISGID + S_IXGRP */
case 's':
#if defined(S_ISGID)
perms |= S_ISGID;
perms |= S_IXGRP;
#endif /* S_ISGID */
break;
/* S_IXGRP only */
case 'x':
perms |= S_IXGRP;
break;
case '-':
break;
}
/* World/other permissions */
switch (text[6]) {
case 'r':
perms |= S_IROTH;
break;
case '-':
break;
}
switch (text[7]) {
case 'w':
perms |= S_IWOTH;
break;
case '-':
break;
}
switch (text[8]) {
/* S_ISVTX only */
case 'T':
#if defined(S_ISVTX)
perms |= S_ISVTX;
#endif /* S_ISVTX */
break;
/* S_ISVTX + S_IXOTH */
case 't':
#if defined(S_ISVTX)
perms |= S_ISVTX;
perms |= S_IXOTH;
#endif /* S_ISVTX */
break;
/* S_IXOTH only */
case 'x':
perms |= S_IXOTH;
break;
case '-':
break;
}
return perms;
}
/* See RFC 3659, Section 7.5.5: "The perm Fact" */
static char *get_perm_fact(pool *p, mode_t mode) {
char *perm = "";
if (!S_ISDIR(mode)) {
if (mode & (S_IWUSR|S_IWGRP|S_IWOTH)) {
perm = pstrcat(p, perm, "a", NULL);
}
perm = pstrcat(p, perm, "d", NULL);
if (mode & (S_IWUSR|S_IWGRP|S_IWOTH)) {
perm = pstrcat(p, perm, "f", NULL);
}
if (mode & (S_IRUSR|S_IRGRP|S_IROTH)) {
perm = pstrcat(p, perm, "r", NULL);
}
if (mode & (S_IWUSR|S_IWGRP|S_IWOTH)) {
perm = pstrcat(p, perm, "w", NULL);
}
} else if (S_ISDIR(mode)) {
if (mode & (S_IWUSR|S_IWGRP|S_IWOTH)) {
perm = pstrcat(p, perm, "c", NULL);
}
perm = pstrcat(p, perm, "d", NULL);
if (mode & (S_IXUSR|S_IXGRP|S_IXOTH)) {
perm = pstrcat(p, perm, "e", NULL);
}
if (mode & (S_IWUSR|S_IWGRP|S_IWOTH)) {
perm = pstrcat(p, perm, "f", NULL);
}
if (mode & (S_IXUSR|S_IXGRP|S_IXOTH)) {
perm = pstrcat(p, perm, "l", NULL);
}
if (mode & (S_IWUSR|S_IWGRP|S_IWOTH)) {
perm = pstrcat(p, perm, "mp", NULL);
}
}
return perm;
}
static int get_unix_nlink(pool *p, char *buf, size_t buflen, struct stat *st) {
char *nlinks_ptr;
if (strpbrk(buf, "0123456789 ") == NULL) {
errno = EINVAL;
return -1;
}
nlinks_ptr = strpbrk(buf, "0123456789");
if (nlinks_ptr == NULL) {
errno = EINVAL;
return -1;
}
st->st_nlink = atoi(nlinks_ptr);
return 0;
}
static int get_unix_user(pool *p, char *buf, size_t buflen,
struct proxy_dirlist_fileinfo *pdf) {
int res;
char user[32];
uid_t uid;
memset(user, '\0', sizeof(user));
while (PR_ISSPACE(*buf) &&
*buf) {
buf += 1;
buflen -= 1;
}
res = sscanf(buf, "%s", user);
if (res != 1) {
pr_trace_msg(trace_channel, 3,
"malformed Unix text (unable to parse user): '%.*s'", (int) buflen, buf);
errno = EINVAL;
return -1;
}
if (pr_str2uid(user, &uid) == 0) {
pdf->st->st_uid = uid;
pdf->have_uid = TRUE;
} else {
pdf->user = pstrdup(p, user);
}
return 0;
}
static int get_unix_group(pool *p, char *buf, size_t buflen,
struct proxy_dirlist_fileinfo *pdf) {
int res;
char group[32];
gid_t gid;
memset(group, '\0', sizeof(group));
while (PR_ISSPACE(*buf) &&
*buf) {
buf += 1;
buflen -= 1;
}
res = sscanf(buf, "%s", group);
if (res != 1) {
pr_trace_msg(trace_channel, 3,
"malformed Unix text (unable to parse group): '%.*s'", (int) buflen, buf);
errno = EINVAL;
return -1;
}
if (pr_str2gid(group, &gid) == 0) {
pdf->st->st_gid = gid;
pdf->have_gid = TRUE;
} else {
pdf->group = pstrdup(p, group);
}
return 0;
}
static int get_unix_filesize(pool *p, char *buf, size_t buflen,
struct stat *st) {
off_t filesz;
if (pr_str_get_nbytes(buf, NULL, &filesz) < 0) {
pr_trace_msg(trace_channel, 3,
"malformed Unix text (unable to parse filesize: %s): '%.*s'",
strerror(errno), (int) buflen, buf);
errno = EINVAL;
return -1;
}
st->st_size = filesz;
return 0;
}
static const char *months[13] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
NULL
};
/* Either:
*
* "Jul 21 04:53"
* "Apr 9 2015"
*/
static int get_unix_timestamp(pool *p, char *buf, size_t buflen,
struct tm *tm, int current_year) {
register unsigned int i;
int found_month = FALSE, mday, year, hour, min, res;
for (i = 0; months[i]; i++) {
if (strncmp(buf, months[i], 3) == 0) {
tm->tm_mon = (int) i;
found_month = TRUE;
break;
}
}
if (found_month == FALSE) {
pr_trace_msg(trace_channel, 3,
"malformed Unix text (unable to month in '%.*s')", (int) buflen, buf);
errno = EINVAL;
return -1;
}
buf += 3;
buflen -= 3;
if (strncmp(buf, " ", 1) != 0) {
pr_trace_msg(trace_channel, 3,
"malformed Unix text (expected space after month): '%.*s'",
(int) buflen, buf);
errno = EINVAL;
return -1;
}
buf += 1;
buflen -= 1;
res = sscanf(buf, "%2d", &mday);
if (res != 1) {
pr_trace_msg(trace_channel, 3,
"malformed Unix text (expected mday after month): '%.*s'",
(int) buflen, buf);
errno = EINVAL;
return -1;
}
tm->tm_mday = mday;
buf += 2;
buflen -= 2;
res = sscanf(buf, "%02d:%02d", &hour, &min);
if (res == 2) {
tm->tm_year = current_year;
tm->tm_hour = hour;
tm->tm_min = min;
} else {
/* We have text of 5 characters, but years are only 4 characters.
* Advance past the space character.
*/
buf += 1;
buflen -= 1;
res = sscanf(buf, "%4d", &year);
if (res == 1) {
tm->tm_year = year;
if (tm->tm_year > 1900) {
tm->tm_year -= 1900;
}
} else {
pr_trace_msg(trace_channel, 3,
"malformed Unix text (expected year/hour/min after mday): '%.*s'",
(int) buflen, buf);
errno = EINVAL;
return -1;
}
}
return 0;
}
struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_unix(pool *p,
const char *text, size_t textlen, struct tm *tm, unsigned long opts) {
struct proxy_dirlist_fileinfo *pdf;
char *buf, *perm, *ptr, *ptr2;
size_t buflen;
mode_t mode;
if (p == NULL ||
text == NULL ||
textlen == 0 ||
tm == NULL) {
errno = EINVAL;
return NULL;
}
pr_trace_msg(trace_channel, 19, "parsing Unix text: '%.*s'",
(int) textlen, text);
/* 43 is the minimum length of a well-formatted Unix directory listing
* line.
*/
if (textlen < 43) {
pr_trace_msg(trace_channel, 3,
"error parsing Unix text (too short, need at least 43 bytes): '%.*s'",
(int) textlen, text);
errno = EINVAL;
return NULL;
}
pdf = pcalloc(p, sizeof(struct proxy_dirlist_fileinfo));
pdf->st = pcalloc(p, sizeof(struct stat));
switch (text[0]) {
case '-':
pdf->st->st_mode |= S_IFREG;
pdf->type = pstrdup(p, "file");
break;
case 'd':
pdf->st->st_mode |= S_IFDIR;
pdf->type = pstrdup(p, "dir");
break;
case 'l':
#if defined(S_IFLNK)
pdf->st->st_mode |= S_IFLNK;
#endif /* S_IFLNK */
/* If the USE_SLINK option is set, then pdf->type will be filled in
* later, once we know the symlink target.
*/
if (!(opts & PROXY_FTP_DIRLIST_OPT_USE_SLINK)) {
pdf->type = pstrdup(p, "OS.unix=symlink");
}
break;
case 'p':
#if defined(S_IFIFO)
pdf->st->st_mode |= S_IFIFO;
#else
pdf->st->st_mode |= S_IFREG;
#endif /* S_IFIFO */
pdf->type = pstrdup(p, "OS.unix=pipe");
break;
case 's':
#if defined(S_IFSOCK)
pdf->st->st_mode |= S_IFSOCK;
#else
pdf->st->st_mode |= S_IFREG;
#endif /* S_IFSOCK */
pdf->type = pstrdup(p, "OS.unix=socket");
break;
case 'c':
#if defined(S_IFCHR)
pdf->st->st_mode |= S_IFCHR;
#else
pdf->st->st_mode |= S_IFREG;
#endif /* S_IFCHR */
pdf->type = pstrdup(p, "OS.unix=chardev");
break;
case 'b':
#if defined(S_IFBLK)
pdf->st->st_mode |= S_IFBLK;
#else
pdf->st->st_mode |= S_IFREG;
#endif /* S_IFBLK */
pdf->type = pstrdup(p, "OS.unix=blockdev");
break;
case 'D':
#if defined(S_IFIFO)
pdf->st->st_mode |= S_IFIFO;
#else
pdf->st->st_mode |= S_IFREG;
#endif /* S_IFIFO */
pdf->type = pstrdup(p, "OS.solaris=door");
break;
default:
pr_trace_msg(trace_channel, 3, "unknown Unix file type: '%.*s'", 1, text);
errno = EINVAL;
return NULL;
}
ptr = (char *) text + 1;
buflen = 9;
buf = pstrndup(p, ptr, buflen);
if (strpbrk(buf, "rwx-tTsS") == NULL) {
pr_trace_msg(trace_channel, 3,
"malformed Unix text (expected permissions): '%.*s'", (int) buflen, buf);
errno = EINVAL;
return NULL;
}
mode = get_unix_mode(buf);
pdf->st->st_mode |= mode;
perm = get_perm_fact(p, pdf->st->st_mode);
pdf->perm = perm;
ptr += buflen;
if (strncmp(ptr, " ", 1) != 0) {
pr_trace_msg(trace_channel, 3,
"malformed Unix text (expected space after permissions): '%.*s'",
(int) textlen, text);
errno = EINVAL;
return NULL;
}
ptr += 1;
if (*ptr == ' ') {
buflen = 3;
} else {
ptr2 = strchr(ptr, ' ');
if (ptr2 == NULL) {
pr_trace_msg(trace_channel, 3,
"malformed Unix text (expected space after nlink): '%.*s'",
(int) textlen, text);
errno = EINVAL;
return NULL;
}
buflen = ptr2 - ptr;
}
buf = pstrndup(p, ptr, buflen);
if (get_unix_nlink(p, buf, buflen, pdf->st) < 0) {
int xerrno = errno;
pr_trace_msg(trace_channel, 3,
"malformed Unix text (expected nlink with '%.*s'): '%.*s'", (int) buflen,
buf, (int) textlen, text);
errno = xerrno;
return NULL;
}
ptr += buflen;
if (strncmp(ptr, " ", 1) != 0) {
pr_trace_msg(trace_channel, 3,
"malformed Unix text (expected space after nlink): '%.*s'",
(int) textlen, text);
errno = EINVAL;
return NULL;
}
ptr += 1;
buflen = 8;
buf = pstrndup(p, ptr, buflen);
if (get_unix_user(p, buf, buflen, pdf) < 0) {
int xerrno = errno;
pr_trace_msg(trace_channel, 3,
"malformed Unix text (expected user with '%.*s'): '%.*s'", (int) buflen,
buf, (int) textlen, text);
errno = xerrno;
return NULL;
}
ptr += buflen;
if (strncmp(ptr, " ", 1) != 0) {
pr_trace_msg(trace_channel, 3,
"malformed Unix text (expected space after user): '%.*s'",
(int) textlen, text);
errno = EINVAL;
return NULL;
}
ptr += 1;
buflen = 8;
buf = pstrndup(p, ptr, buflen);
if (get_unix_group(p, buf, buflen, pdf) < 0) {
int xerrno = errno;
pr_trace_msg(trace_channel, 3,
"malformed Unix text (expected group with '%.*s'): '%.*s'", (int) buflen,
buf, (int) textlen, text);
errno = xerrno;
return NULL;
}
ptr += buflen;
if (strncmp(ptr, " ", 1) != 0) {
pr_trace_msg(trace_channel, 3,
"malformed Unix text (expected space after group): '%.*s'",
(int) textlen, text);
errno = EINVAL;
return NULL;
}
ptr += 1;
while (PR_ISSPACE(*ptr) &&
*ptr) {
pr_signals_handle();
ptr++;
}
ptr2 = strchr(ptr, ' ');
if (ptr2 == NULL) {
pr_trace_msg(trace_channel, 3,
"malformed Unix text (expected space after filesize): '%.*s'",
(int) textlen, text);
errno = EINVAL;
return NULL;
}
buflen = ptr2 - ptr;
buf = pstrndup(p, ptr, buflen);
if (get_unix_filesize(p, buf, buflen, pdf->st) < 0) {
int xerrno = errno;
pr_trace_msg(trace_channel, 3,
"malformed Unix text (expected filesize with '%.*s'): '%.*s'",
(int) buflen, buf, (int) textlen, text);
errno = xerrno;
return NULL;
}
ptr += buflen;
if (strncmp(ptr, " ", 1) != 0) {
pr_trace_msg(trace_channel, 3,
"malformed Unix text (expected space after filesize): '%.*s'",
(int) textlen, text);
errno = EINVAL;
return NULL;
}
ptr += 1;
pdf->tm = pcalloc(p, sizeof(struct tm));
buflen = 12;
buf = pstrndup(p, ptr, buflen);
if (get_unix_timestamp(p, buf, buflen, pdf->tm, tm->tm_year) < 0) {
int xerrno = errno;
pr_trace_msg(trace_channel, 3,
"malformed Unix text (expected timestamp with '%.*s'): '%.*s'",
(int) buflen, buf, (int) textlen, text);
errno = xerrno;
return NULL;
}
ptr += buflen;
if (strncmp(ptr, " ", 1) != 0) {
pr_trace_msg(trace_channel, 3,
"malformed Unix text (expected space after timestamp): '%.*s'",
(int) textlen, text);
errno = EINVAL;
return NULL;
}
ptr += 1;
if (S_ISLNK(pdf->st->st_mode)) {
ptr2 = strchr(ptr, ' ');
if (ptr2 == NULL) {
pr_trace_msg(trace_channel, 3,
"malformed Unix text (expected space after symlink source): '%.*s'",
(int) textlen, text);
errno = EINVAL;
return NULL;
}
buflen = ptr2 - ptr;
pdf->path = pstrndup(p, ptr, buflen);
ptr = ptr2 + 1;
if (strncmp(ptr, "-> ", 3) != 0) {
pr_trace_msg(trace_channel, 3,
"malformed Unix text (expected arrow after symlink source): '%.*s'",
(int) textlen, text);
errno = EINVAL;
return NULL;
}
ptr += 3;
if (opts & PROXY_FTP_DIRLIST_OPT_USE_SLINK) {
char *target_path;
target_path = pstrdup(p, ptr);
pdf->type = pstrcat(p, "OS.unix=slink:", target_path, NULL);
}
} else {
pdf->path = pstrdup(p, ptr);
}
if (strcmp(pdf->path, ".") == 0) {
pdf->type = pstrdup(p, "cdir");
} else if (strcmp(pdf->path, "..") == 0) {
pdf->type = pstrdup(p, "pdir");
}
return pdf;
}
struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_text(pool *p,
const char *text, size_t textlen, struct tm *tm, void *user_data,
unsigned long opts) {
struct proxy_session *proxy_sess;
struct dirlist_ctx *ctx;
struct proxy_dirlist_fileinfo *pdf = NULL;
if (p == NULL ||
text == NULL ||
textlen == 0 ||
user_data == NULL) {
errno = EINVAL;
return NULL;
}
proxy_sess = user_data;
if (proxy_sess->dirlist_ctx == NULL) {
errno = EINVAL;
return NULL;
}
ctx = proxy_sess->dirlist_ctx;
if (ctx->list_style == DIRLIST_LIST_STYLE_UNKNOWN) {
/* We don't know yet what style of listing we have, so we use some
* heuristics to guess.
*
* A Windows-style listing always starts with a timestamp, e.g.:
*
* 01-29-97 11:32PM prog
*
* Thus if the first character is '0' or '1', we treat it as Windows,
* otherwise Unix.
*/
if (text[0] == '0' ||
text[1] == '1') {
ctx->list_style = DIRLIST_LIST_STYLE_WINDOWS;
pr_trace_msg(trace_channel, 19,
"assuming Windows-style directory listing data");
} else {
ctx->list_style = DIRLIST_LIST_STYLE_UNIX;
pr_trace_msg(trace_channel, 19,
"assuming Unix-style directory listing data");
}
}
switch (ctx->list_style) {
case DIRLIST_LIST_STYLE_UNIX:
pdf = proxy_ftp_dirlist_fileinfo_from_unix(p, text, textlen, tm, opts);
break;
case DIRLIST_LIST_STYLE_WINDOWS:
pdf = proxy_ftp_dirlist_fileinfo_from_dos(p, text, textlen, opts);
break;
default:
pr_trace_msg(trace_channel, 3,
"unable to determine directory listing style");
errno = EPERM;
pdf = NULL;
break;
}
return pdf;
}
static size_t facts_fmt(const struct proxy_dirlist_fileinfo *pdf, char *buf,
size_t bufsz) {
int len;
char *ptr;
size_t buflen = 0;
memset(buf, '\0', bufsz);
ptr = buf;
if (pdf->tm != NULL &&
(facts_opts & PROXY_FTP_FACTS_OPT_SHOW_MODIFY)) {
len = pr_snprintf(ptr, bufsz, "modify=%04d%02d%02d%02d%02d%02d;",
pdf->tm->tm_year+1900, pdf->tm->tm_mon+1, pdf->tm->tm_mday,
pdf->tm->tm_hour, pdf->tm->tm_min, pdf->tm->tm_sec);
buflen += len;
ptr = buf + buflen;
}
if (pdf->perm != NULL &&
(facts_opts & PROXY_FTP_FACTS_OPT_SHOW_PERM)) {
len = pr_snprintf(ptr, bufsz - buflen, "perm=%s;", pdf->perm);
buflen += len;
ptr = buf + buflen;
}
if (pdf->st != NULL &&
!S_ISDIR(pdf->st->st_mode) &&
(facts_opts & PROXY_FTP_FACTS_OPT_SHOW_SIZE)) {
len = pr_snprintf(ptr, bufsz - buflen, "size=%" PR_LU ";",
(pr_off_t) pdf->st->st_size);
buflen += len;
ptr = buf + buflen;
}
if (pdf->type != NULL &&
(facts_opts & PROXY_FTP_FACTS_OPT_SHOW_TYPE)) {
len = pr_snprintf(ptr, bufsz - buflen, "type=%s;", pdf->type);
buflen += len;
ptr = buf + buflen;
}
if (pdf->st != NULL) {
if (facts_opts & PROXY_FTP_FACTS_OPT_SHOW_UNIQUE) {
len = pr_snprintf(ptr, bufsz - buflen, "unique=%lXU%lX;",
(unsigned long) pdf->st->st_dev, (unsigned long) pdf->st->st_ino);
buflen += len;
ptr = buf + buflen;
}
if (pdf->have_gid == TRUE &&
(facts_opts & PROXY_FTP_FACTS_OPT_SHOW_UNIX_GROUP)) {
len = pr_snprintf(ptr, bufsz - buflen, "UNIX.group=%s;",
pr_gid2str(NULL, pdf->st->st_gid));
buflen += len;
ptr = buf + buflen;
}
}
if (pdf->group != NULL &&
(facts_opts & PROXY_FTP_FACTS_OPT_SHOW_UNIX_GROUP_NAME)) {
len = pr_snprintf(ptr, bufsz - buflen, "UNIX.groupname=%s;", pdf->group);
buflen += len;
ptr = buf + buflen;
}
if (pdf->st != NULL) {
if (facts_opts & PROXY_FTP_FACTS_OPT_SHOW_UNIX_MODE) {
len = pr_snprintf(ptr, bufsz - buflen, "UNIX.mode=0%o;",
(unsigned int) pdf->st->st_mode & 07777);
buflen += len;
ptr = buf + buflen;
}
if (pdf->have_uid == TRUE &&
(facts_opts & PROXY_FTP_FACTS_OPT_SHOW_UNIX_OWNER)) {
len = pr_snprintf(ptr, bufsz - buflen, "UNIX.owner=%s;",
pr_uid2str(NULL, pdf->st->st_uid));
buflen += len;
ptr = buf + buflen;
}
}
if (pdf->user != NULL &&
(facts_opts & PROXY_FTP_FACTS_OPT_SHOW_UNIX_OWNER_NAME)) {
len = pr_snprintf(ptr, bufsz - buflen, "UNIX.ownername=%s;", pdf->user);
buflen += len;
ptr = buf + buflen;
}
/* Make sure we terminate each line with CRLF; this text will be sent to
* the requesting client as is.
*/
len = pr_snprintf(ptr, bufsz - buflen, " %s\r\n", pdf->path);
buf[bufsz-1] = '\0';
buflen += len;
return buflen;
}
const char *proxy_ftp_dirlist_fileinfo_to_facts(pool *p,
const struct proxy_dirlist_fileinfo *pdf, size_t *textlen) {
char buf[PR_TUNABLE_BUFFER_SIZE];
size_t buflen;
if (p == NULL ||
pdf == NULL ||
textlen == NULL) {
errno = EINVAL;
return NULL;
}
buflen = facts_fmt(pdf, buf, sizeof(buf));
*textlen = buflen;
return pstrndup(p, buf, buflen);
}
static array_header *text_to_lines(pool *p, const char *text, size_t textlen) {
char *ptr;
array_header *text_lines;
text_lines = make_array(p, 1, sizeof(char *));
ptr = proxy_strnstr(text, "\r\n", textlen);
while (ptr != NULL) {
size_t linelen;
pr_signals_handle();
linelen = ptr - text;
if (linelen > 0) {
char *line;
line = palloc(p, linelen + 1);
memcpy(line, text, linelen);
line[linelen] = '\0';
*((char **) push_array(text_lines)) = line;
}
text = ptr + 2;
textlen = textlen - linelen - 2;
if (textlen == 0) {
break;
}
ptr = proxy_strnstr(text, "\r\n", textlen);
}
if (textlen > 0) {
*((char **) push_array(text_lines)) = pstrdup(p, text);
}
return text_lines;
}
int proxy_ftp_dirlist_to_text(pool *p, char *buf, size_t buflen,
size_t max_textsz, char **output_text, size_t *output_textlen,
void *user_data) {
register unsigned int i;
pool *tmp_pool;
struct proxy_session *proxy_sess;
struct dirlist_ctx *ctx;
char *text, **lines;
size_t textlen;
array_header *text_lines;
unsigned long current_facts_opts;
time_t now;
struct tm *tm;
if (p == NULL ||
buf == NULL ||
buflen == 0 ||
max_textsz == 0 ||
output_text == NULL ||
output_textlen == NULL ||
user_data == NULL) {
errno = EINVAL;
return -1;
}
proxy_sess = user_data;
if (proxy_sess->dirlist_ctx == NULL) {
errno = EINVAL;
return -1;
}
ctx = proxy_sess->dirlist_ctx;
tmp_pool = make_sub_pool(p);
pr_pool_tag(tmp_pool, "Proxy Dirlist Text Pool");
/* Our text to process is comprised of any previous buffered input text,
* and our given input buffer.
*/
if (ctx->input_textlen == 0) {
text = buf;
textlen = buflen;
} else {
/* We cannot use pstrcat() here because the buffers are not
* NUL-terminated.
*/
textlen = ctx->input_textlen + buflen;
text = palloc(tmp_pool, textlen + 1);
memcpy(text, ctx->input_ptr, ctx->input_textlen);
memcpy(text + ctx->input_textlen, buf, buflen);
text[textlen] = '\0';
ctx->input_text = ctx->input_ptr;
ctx->input_textlen = 0;
}
if (textlen < 3) {
/* Not enough; keep accumulating. */
memcpy(ctx->input_text, text, textlen);
ctx->input_text += textlen;
ctx->input_textlen += textlen;
return 0;
}
/* Check for a terminating CRLF. If present, we can process the entire
* text. Otherwise, trim off the unterminated line, and save it for the
* next pass.
*/
if (text[textlen-2] != '\r' ||
text[textlen-1] != '\n') {
char *ptr = NULL;
size_t len = 0;
/* Too bad there is no `memrchr(3)` library function. */
for (i = textlen-1; i != 0; i--) {
if (text[i] == '\n') {
ptr = &(text[i]);
break;
}
}
if (ptr == NULL) {
memcpy(ctx->input_text, text, textlen);
ctx->input_text += textlen;
ctx->input_textlen += textlen;
return 0;
}
ptr++;
len = textlen - (ptr - text);
if (len > ctx->input_textsz) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unterminated directory list data length (%lu bytes) exceeds "
"capacity (%lu bytes), rejecting", (unsigned long) len,
(unsigned long) ctx->input_textsz);
errno = EPERM;
return -1;
}
memcpy(ctx->input_text, ptr, len);
ctx->input_text += len;
ctx->input_textlen += len;
pr_trace_msg(trace_channel, 25,
"given text (%lu bytes) is not CRLF-terminated, "
"trimming %lu bytes for later", (unsigned long) textlen,
(unsigned long) len);
textlen -= len;
}
text_lines = text_to_lines(tmp_pool, text, textlen);
current_facts_opts = facts_opts;
/* We get the current time, for filling in defaults. */
now = time(NULL);
tm = pr_gmtime(tmp_pool, &now);
lines = text_lines->elts;
for (i = 0; i < text_lines->nelts; i++) {
const char *input_line, *output_line;
size_t input_linelen, output_linelen = 0;
struct proxy_dirlist_fileinfo *pdf;
pr_signals_handle();
input_line = lines[i];
input_linelen = strlen(input_line);
/* Skip any possible "total NNN" lines, as from /bin/ls. */
if (ctx->skip_total == TRUE) {
ctx->skip_total = FALSE;
if (strncmp(input_line, "total ", 6) == 0) {
continue;
}
}
pdf = proxy_ftp_dirlist_fileinfo_from_text(tmp_pool, input_line,
input_linelen, tm, user_data, proxy_sess->dirlist_opts);
if (pdf == NULL) {
pr_trace_msg(trace_channel, 3, "error parsing text '%.*s': %s",
(int) input_linelen, input_line, strerror(errno));
continue;
}
if (ctx->list_style == DIRLIST_LIST_STYLE_WINDOWS) {
/* Once we know that we are parsing a Windows-style directory listing,
* we can toggle off the RFC 3649 facts that we KNOW will not be provided
* by the listing data.
*/
facts_opts &= ~PROXY_FTP_FACTS_OPT_SHOW_PERM;
facts_opts &= ~PROXY_FTP_FACTS_OPT_SHOW_UNIQUE;
facts_opts &= ~PROXY_FTP_FACTS_OPT_SHOW_UNIX_MODE;
facts_opts &= ~PROXY_FTP_FACTS_OPT_SHOW_UNIX_GROUP;
facts_opts &= ~PROXY_FTP_FACTS_OPT_SHOW_UNIX_GROUP_NAME;
facts_opts &= ~PROXY_FTP_FACTS_OPT_SHOW_UNIX_OWNER;
facts_opts &= ~PROXY_FTP_FACTS_OPT_SHOW_UNIX_OWNER_NAME;
} else {
/* Once we know that we are parsing a Unix-style directory listing,
* we can toggle off the RFC 3649 facts that we KNOW will not be provided
* by the listing data.
*/
facts_opts &= ~PROXY_FTP_FACTS_OPT_SHOW_UNIQUE;
}
output_line = proxy_ftp_dirlist_fileinfo_to_facts(tmp_pool, pdf,
&output_linelen);
pr_trace_msg(trace_channel, 19, "emitting line: '%.*s'",
(int) output_linelen, output_line);
/* XXX What to do if this will exceed capacity of output buffer? */
/* TODO: Watch for output_linelen > (ctx->output_textsz - ctx->output_textlen),
* and rejigger this function to handle the case of "no more input to
* accumulate, but have unprocess input".
*/
sstrcat(ctx->output_text, output_line,
ctx->output_textsz - ctx->output_textlen);
ctx->output_text += output_linelen;
ctx->output_textlen += output_linelen;
}
facts_opts = current_facts_opts;
*output_textlen = ctx->output_textlen;
if (*output_textlen > max_textsz) {
*output_textlen = max_textsz;
}
pr_trace_msg(trace_channel, 29,
"emitting %lu bytes of output text (max %lu), for %lu bytes of input text",
(unsigned long) *output_textlen, (unsigned long) max_textsz, textlen);
*output_text = palloc(p, *output_textlen);
memcpy(*output_text, ctx->output_ptr, *output_textlen);
memmove(ctx->output_ptr, ctx->output_ptr + *output_textlen,
ctx->output_textsz - *output_textlen);
ctx->output_text = ctx->output_ptr;
ctx->output_textlen -= *output_textlen;
destroy_pool(tmp_pool);
return 0;
}
proftpd-mod_proxy-0.9.5/lib/proxy/ftp/facts.c 0000664 0000000 0000000 00000006602 14757370167 0021232 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy FTP Facts routines
* Copyright (c) 2020 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "proxy/ftp/facts.h"
/* Similar to those of mod_facts. */
/* NOTE: We only want to parse/handle any OPTS MLST commands from the frontend
* client IFF the DirectoryListPolicy is "LIST". Otherwise, let the backend
* handle them. But...what if the backend doesn't support OPTS MLST? Do
* we watch for that error, and handle it ourselves (e.g. show our defaults)?
*/
static unsigned long facts_opts = PROXY_FTP_FACTS_OPT_SHOW_MODIFY|
PROXY_FTP_FACTS_OPT_SHOW_PERM|
PROXY_FTP_FACTS_OPT_SHOW_SIZE|
PROXY_FTP_FACTS_OPT_SHOW_TYPE|
PROXY_FTP_FACTS_OPT_SHOW_UNIQUE|
PROXY_FTP_FACTS_OPT_SHOW_UNIX_GROUP|
PROXY_FTP_FACTS_OPT_SHOW_UNIX_GROUP_NAME|
PROXY_FTP_FACTS_OPT_SHOW_UNIX_MODE|
PROXY_FTP_FACTS_OPT_SHOW_UNIX_OWNER|
PROXY_FTP_FACTS_OPT_SHOW_UNIX_OWNER_NAME;
static const char *trace_channel = "proxy.ftp.facts";
unsigned long proxy_ftp_facts_get_opts(void) {
return facts_opts;
}
void proxy_ftp_facts_parse_opts(char *facts) {
unsigned long opts = 0UL;
char *ptr;
if (facts == NULL) {
return;
}
ptr = strchr(facts, ';');
while (ptr != NULL) {
pr_signals_handle();
*ptr = '\0';
if (strcasecmp(facts, "modify") == 0) {
opts |= PROXY_FTP_FACTS_OPT_SHOW_MODIFY;
} else if (strcasecmp(facts, "perm") == 0) {
opts |= PROXY_FTP_FACTS_OPT_SHOW_PERM;
} else if (strcasecmp(facts, "size") == 0) {
opts |= PROXY_FTP_FACTS_OPT_SHOW_SIZE;
} else if (strcasecmp(facts, "type") == 0) {
opts |= PROXY_FTP_FACTS_OPT_SHOW_TYPE;
} else if (strcasecmp(facts, "unique") == 0) {
opts |= PROXY_FTP_FACTS_OPT_SHOW_UNIQUE;
} else if (strcasecmp(facts, "UNIX.group") == 0) {
opts |= PROXY_FTP_FACTS_OPT_SHOW_UNIX_GROUP;
} else if (strcasecmp(facts, "UNIX.groupname") == 0) {
opts |= PROXY_FTP_FACTS_OPT_SHOW_UNIX_GROUP_NAME;
} else if (strcasecmp(facts, "UNIX.mode") == 0) {
opts |= PROXY_FTP_FACTS_OPT_SHOW_UNIX_MODE;
} else if (strcasecmp(facts, "UNIX.owner") == 0) {
opts |= PROXY_FTP_FACTS_OPT_SHOW_UNIX_OWNER;
} else if (strcasecmp(facts, "UNIX.ownername") == 0) {
opts |= PROXY_FTP_FACTS_OPT_SHOW_UNIX_OWNER_NAME;
} else {
pr_trace_msg(trace_channel, 7,
"client requested unsupported fact '%s'", facts);
}
*ptr = ';';
facts = ptr + 1;
ptr = strchr(facts, ';');
}
facts_opts = opts;
}
proftpd-mod_proxy-0.9.5/lib/proxy/ftp/msg.c 0000664 0000000 0000000 00000027274 14757370167 0020730 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy FTP message routines
* Copyright (c) 2013-2021 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "include/proxy/ftp/msg.h"
static const char *trace_channel = "proxy.ftp.msg";
const char *proxy_ftp_msg_fmt_addr(pool *p, const pr_netaddr_t *addr,
unsigned short port, int use_masqaddr) {
char *addr_str, *msg, *ptr;
size_t msglen;
if (p == NULL ||
addr == NULL) {
errno = EINVAL;
return NULL;
}
if (use_masqaddr) {
config_rec *c;
/* TODO What about TLSMasqueradeAddress? */
/* Handle MasqueradeAddress. */
c = find_config(main_server->conf, CONF_PARAM, "MasqueradeAddress", FALSE);
if (c != NULL) {
addr = c->argv[0];
}
}
addr_str = pstrdup(p, pr_netaddr_get_ipstr(addr));
/* Fixup the address string for use in PORT commands/PASV responses. */
ptr = strrchr(addr_str, ':');
if (ptr != NULL) {
addr_str = ptr + 1;
}
for (ptr = addr_str; *ptr; ptr++) {
if (*ptr == '.') {
*ptr = ',';
}
}
/* Allocate enough room for 6 numbers (3 digits max each), 5 separators,
* and a trailing NUL.
*/
msglen = (6 * 3) + (5 * 1) + 1;
msg = pcalloc(p, msglen);
snprintf(msg, msglen, "%s,%u,%u", addr_str, (port >> 8) & 255, port & 255);
return msg;
}
const char *proxy_ftp_msg_fmt_ext_addr(pool *p, const pr_netaddr_t *addr,
unsigned short port, int cmd_id, int use_masqaddr) {
const char *addr_str;
char delim = '|', *msg;
int family = 0;
size_t addr_strlen, msglen;
if (p == NULL ||
addr == NULL) {
errno = EINVAL;
return NULL;
}
if (use_masqaddr) {
config_rec *c;
/* Handle MasqueradeAddress. */
c = find_config(main_server->conf, CONF_PARAM, "MasqueradeAddress", FALSE);
if (c != NULL) {
addr = c->argv[0];
}
}
/* Format is protoip addressport (ASCII in network order),
* where is an arbitrary delimiter character.
*/
switch (pr_netaddr_get_family(addr)) {
case AF_INET:
family = 1;
break;
#ifdef PR_USE_IPV6
case AF_INET6:
family = 2;
break;
#endif /* PR_USE_IPV6 */
default:
/* Unlikely to happen. */
errno = EINVAL;
return NULL;
}
addr_str = pr_netaddr_get_ipstr(addr);
addr_strlen = strlen(addr_str);
/* 4 delimiters, the network protocol, the IP address, the port, and a NUL. */
msglen = (4 * 1) + addr_strlen + 6 + 1;
msg = pcalloc(p, msglen);
switch (cmd_id) {
case PR_CMD_EPRT_ID:
snprintf(msg, msglen, "%c%d%c%s%c%hu%c", delim, family, delim,
addr_str, delim, port, delim);
break;
case PR_CMD_EPSV_ID:
snprintf(msg, msglen-1, "%c%c%c%u%c", delim, delim, delim, port, delim);
break;
default:
pr_trace_msg(trace_channel, 3, "invalid/unsupported command ID: %d",
cmd_id);
errno = EINVAL;
return NULL;
}
return msg;
}
const pr_netaddr_t *proxy_ftp_msg_parse_addr(pool *p, const char *msg,
int addr_family) {
int valid_fmt = FALSE;
const char *ptr;
char *addr_buf;
unsigned int h1, h2, h3, h4, p1, p2;
unsigned short port;
size_t addrlen;
pr_netaddr_t *addr;
if (p == NULL ||
msg == NULL) {
errno = EINVAL;
return NULL;
}
/* Have to scan the message for the encoded address/port. Note that we may
* see some strange formats for PASV responses from FTP servers here.
*
* We can't predict where the expected address/port numbers start in the
* string, so start from the beginning.
*/
h1 = h2 = h3 = h4 = p1 = p2 = 0;
for (ptr = msg; *ptr; ptr++) {
pr_signals_handle();
h1 = h2 = h3 = h4 = p1 = p2 = 0;
if (sscanf(ptr, "%u,%u,%u,%u,%u,%u", &h1, &h2, &h3, &h4, &p1, &p2) == 6) {
valid_fmt = TRUE;
break;
}
}
if (valid_fmt == FALSE) {
pr_trace_msg(trace_channel, 12,
"unable to find PORT/PASV address/port format in '%s'", msg);
errno = EPERM;
return NULL;
}
if (h1 > 255 || h2 > 255 || h3 > 255 || h4 > 255 ||
p1 > 255 || p2 > 255 ||
(h1|h2|h3|h4) == 0 ||
(p1|p2) == 0) {
pr_trace_msg(trace_channel, 9,
"message '%s' has invalid address/port value(s)", msg);
errno = EINVAL;
return NULL;
}
/* A dotted quad address has a maximum size of 16 bytes: 4 numbers of 3 digits
* (max), 3 periods, and 1 terminating NUL.
*/
addrlen = 16;
#ifdef PR_USE_IPV6
/* Allow extra room for any necessary "::ffff:" prefix, for IPv6 sessions. */
addrlen += 7;
#endif /* PR_USE_IPV6 */
addr_buf = pcalloc(p, addrlen);
#ifdef PR_USE_IPV6
if (pr_netaddr_use_ipv6()) {
if (addr_family == AF_INET6) {
snprintf(addr_buf, addrlen, "::ffff:%u.%u.%u.%u", h1, h2, h3, h4);
} else {
snprintf(addr_buf, addrlen, "%u.%u.%u.%u", h1, h2, h3, h4);
}
} else {
snprintf(addr_buf, addrlen, "%u.%u.%u.%u", h1, h2, h3, h4);
}
#else
snprintf(addr_buf, addrlen, "%u.%u.%u.%u", h1, h2, h3, h4);
#endif /* PR_USE_IPV6 */
addr = (pr_netaddr_t *) pr_netaddr_get_addr(p, addr_buf, NULL);
if (addr == NULL) {
int xerrno = errno;
pr_trace_msg(trace_channel, 7,
"unable to resolve '%s' from message '%s': %s", addr_buf, msg,
strerror(xerrno));
errno = xerrno;
return NULL;
}
port = (p1 << 8) + p2;
pr_netaddr_set_port2(addr, port);
pr_trace_msg(trace_channel, 9, "parsed '%s' into %s %s#%u", msg,
pr_netaddr_get_family(addr) == AF_INET ? "IPv4" : "IPv6",
pr_netaddr_get_ipstr(addr), ntohs(pr_netaddr_get_port(addr)));
return addr;
}
const pr_netaddr_t *proxy_ftp_msg_parse_ext_addr(pool *p, const char *msg,
const pr_netaddr_t *addr, int cmd_id, const char *net_proto) {
pr_netaddr_t *res = NULL, na;
int family = 0;
unsigned short port = 0;
char delim, *msg_str, *ptr;
size_t msglen;
if (p == NULL ||
msg == NULL ||
addr == NULL) {
errno = EINVAL;
return NULL;
}
if (cmd_id == PR_CMD_EPSV_ID) {
int decr = 0;
/* First, find the opening '(' character. */
ptr = strchr(msg, '(');
if (ptr == NULL) {
pr_trace_msg(trace_channel, 12,
"missing starting '(' character for extended address in '%s'", msg);
errno = EINVAL;
return NULL;
}
/* Make sure that one of the last characters is a closing ')'. Note that
* some servers may have a trailing '.' as well.
*/
msglen = strlen(ptr);
if (ptr[msglen-1] == ')') {
decr = 1;
} else if (ptr[msglen-2] == ')') {
decr = 2;
} else {
pr_trace_msg(trace_channel, 12,
"missing ending ')' character for extended address in '%s'", msg);
errno = EINVAL;
return NULL;
}
msg_str = pstrndup(p, ptr+1, msglen-decr-1);
} else {
msg_str = pstrdup(p, msg);
}
/* Format is protoip addressport (ASCII in network order),
* where is an arbitrary delimiter character.
*/
delim = *msg_str++;
/* If the network protocol string (e.g. sent by client in EPSV command) is
* null, then determine the protocol family from the address family we were
* given.
*/
/* XXX Hack to skip "all", e.g. "EPSV ALL" commands. */
if (net_proto != NULL) {
if (strncasecmp(net_proto, "all", 4) == 0) {
net_proto = NULL;
}
}
if (net_proto == NULL) {
if (*msg_str == delim) {
switch (pr_netaddr_get_family(addr)) {
case AF_INET:
family = 1;
break;
#ifdef PR_USE_IPV6
case AF_INET6:
if (pr_netaddr_use_ipv6()) {
family = 2;
break;
}
#endif /* PR_USE_IPV6 */
default:
break;
}
} else {
family = atoi(msg_str);
}
} else {
family = atoi(net_proto);
}
switch (family) {
case 1:
pr_trace_msg(trace_channel, 19, "parsed IPv4 address from '%s'", msg);
break;
#ifdef PR_USE_IPV6
case 2:
pr_trace_msg(trace_channel, 19, "parsed IPv6 address from '%s'", msg);
if (pr_netaddr_use_ipv6()) {
break;
}
#endif /* PR_USE_IPV6 */
default:
pr_trace_msg(trace_channel, 12,
"unsupported network protocol %d", family);
errno = EPROTOTYPE;
return NULL;
}
/* Now, skip past those numeric characters that atoi() used. */
while (PR_ISDIGIT(*msg_str)) {
msg_str++;
}
/* If the next character is not the delimiter, it's a badly formatted
* parameter.
*/
if (*msg_str == delim) {
msg_str++;
} else {
pr_trace_msg(trace_channel, 17, "rejecting badly formatted message '%s'",
msg_str);
errno = EPERM;
return NULL;
}
pr_netaddr_clear(&na);
/* If the next character IS the delimiter, then the address portion is
* omitted (which is permissible).
*/
if (*msg_str == delim) {
pr_netaddr_set_family(&na, pr_netaddr_get_family(addr));
pr_netaddr_set_sockaddr(&na, pr_netaddr_get_sockaddr(addr));
msg_str++;
} else {
ptr = strchr(msg_str, delim);
if (ptr == NULL) {
/* Badly formatted message. */
errno = EINVAL;
return NULL;
}
/* Twiddle the string so that just the address portion will be processed
* by pr_inet_pton().
*/
*ptr = '\0';
/* Use pr_inet_pton() to translate the address string into the address
* value.
*/
switch (family) {
case 1: {
struct sockaddr *sa = NULL;
pr_netaddr_set_family(&na, AF_INET);
sa = pr_netaddr_get_sockaddr(&na);
if (sa) {
sa->sa_family = AF_INET;
}
if (pr_inet_pton(AF_INET, msg_str, pr_netaddr_get_inaddr(&na)) <= 0) {
pr_trace_msg(trace_channel, 2,
"error converting IPv4 address '%s': %s", msg_str, strerror(errno));
errno = EPERM;
return NULL;
}
break;
}
case 2: {
struct sockaddr *sa = NULL;
pr_netaddr_set_family(&na, AF_INET6);
sa = pr_netaddr_get_sockaddr(&na);
if (sa) {
sa->sa_family = AF_INET6;
}
if (pr_inet_pton(AF_INET6, msg_str, pr_netaddr_get_inaddr(&na)) <= 0) {
pr_trace_msg(trace_channel, 2,
"error converting IPv6 address '%s': %s", msg_str, strerror(errno));
errno = EPERM;
return NULL;
}
break;
}
}
/* Advance past the address portion of the argument. */
msg_str = ++ptr;
}
port = atoi(msg_str);
while (PR_ISDIGIT(*msg_str)) {
msg_str++;
}
/* If the next character is not the delimiter, it's a badly formatted
* parameter.
*/
if (*msg_str != delim) {
pr_trace_msg(trace_channel, 17, "rejecting badly formatted message '%s'",
msg_str);
errno = EPERM;
return NULL;
}
res = pr_netaddr_dup(p, &na);
pr_netaddr_set_port(res, htons(port));
pr_trace_msg(trace_channel, 9, "parsed '%s' into %s %s#%u", msg,
pr_netaddr_get_family(res) == AF_INET ? "IPv4" : "IPv6",
pr_netaddr_get_ipstr(res), ntohs(pr_netaddr_get_port(res)));
return res;
}
proftpd-mod_proxy-0.9.5/lib/proxy/ftp/sess.c 0000664 0000000 0000000 00000041456 14757370167 0021115 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy FTP session routines
* Copyright (c) 2013-2023 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "proxy/conn.h"
#include "proxy/netio.h"
#include "proxy/tls.h"
#include "proxy/ftp/sess.h"
#include "proxy/ftp/ctrl.h"
static const char *feat_crlf = "\r\n";
static int tls_xfer_prot_policy = 1;
static const char *trace_channel = "proxy.ftp.sess";
/* Many FTP servers (e.g. IIS) use the semicolon delimiter syntax, as used
* for listing the MLSD/MLST facts, for other FEAT values (e.g. AUTH, PROT,
* etc).
*
* NOTE: Should this return a table rather than an array, for easier lookup
* of parsed values by callers?
*/
static int parse_feat(pool *p, const char *feat, array_header **res) {
char *ptr, *ptr2 = NULL;
array_header *vals;
size_t len;
if (feat == NULL) {
return 0;
}
vals = make_array(p, 1, sizeof(char *));
/* No semicolons in this value? No work to do...*/
ptr = strchr(feat, ';');
if (ptr == NULL) {
*((char **) push_array(vals)) = pstrdup(p, feat);
*res = vals;
return vals->nelts;
}
len = ptr - feat;
if (len > 0) {
*((char **) push_array(vals)) = pstrndup(p, feat, len);
}
/* Watch for any sequences of just semicolons. */
while (*ptr == ';') {
pr_signals_handle();
ptr++;
}
ptr2 = strchr(ptr, ';');
while (ptr2 != NULL) {
pr_signals_handle();
len = ptr2 - ptr;
if (len > 0) {
*((char **) push_array(vals)) = pstrndup(p, ptr, len);
}
ptr = ptr2;
while (*ptr == ';') {
pr_signals_handle();
ptr++;
}
ptr2 = strchr(ptr, ';');
}
/* Since the semicolon delimiter syntax uses a trailing semicolon,
* we shouldn't need to worry about something like "...;FOO". Right?
*/
*res = vals;
return vals->nelts;
}
int proxy_ftp_sess_get_feat(pool *p, const struct proxy_session *proxy_sess) {
pool *tmp_pool;
int flags, res, xerrno = 0;
cmd_rec *cmd;
pr_response_t *resp;
unsigned int resp_nlines = 0;
char *feats, *feats_start, *token;
size_t feats_len = 0, token_len = 0;
if (p == NULL ||
proxy_sess == NULL) {
errno = EINVAL;
return -1;
}
tmp_pool = make_sub_pool(p);
cmd = pr_cmd_alloc(tmp_pool, 1, C_FEAT);
res = proxy_ftp_ctrl_send_cmd(tmp_pool, proxy_sess->backend_ctrl_conn, cmd);
if (res < 0) {
xerrno = errno;
pr_trace_msg(trace_channel, 4,
"error sending %s to backend: %s", (char *) cmd->argv[0],
strerror(xerrno));
destroy_pool(tmp_pool);
errno = xerrno;
return -1;
}
/* Some broken FTP servers actually send blank lines in responses, such
* as in a FEAT response. Ugh. (See Issue #251.)
*
* TODO: Should this use some sort of "enable compatibility with broken/
* non-conformat servrs" ProxyOption/flag?
*/
flags = PROXY_FTP_CTRL_FL_IGNORE_BLANK_RESP;
resp = proxy_ftp_ctrl_recv_resp(tmp_pool, proxy_sess->backend_ctrl_conn,
&resp_nlines, flags);
if (resp == NULL) {
xerrno = errno;
pr_trace_msg(trace_channel, 4,
"error receiving %s response from backend: %s", (char *) cmd->argv[0],
strerror(xerrno));
destroy_pool(tmp_pool);
errno = xerrno;
return -1;
}
if (resp->num[0] != '2') {
pr_trace_msg(trace_channel, 4,
"received unexpected %s response code %s from backend",
(char *) cmd->argv[0], resp->num);
/* Note: If the UseProxyProtocol ProxyOption is enabled, AND if the
* response message mentions a "PROXY" command, we might read an
* error response here that is NOT actually for the FEAT command we just
* sent.
*
* A backend FTP server which does not understand the PROXY protocol
* will treat it as a normal FTP command, and respond. And that will
* put us, the client, out of lockstep with the server, for how do we know
* that we need to read that error response FIRST, then send another
* command?
*/
destroy_pool(tmp_pool);
errno = EPERM;
return -1;
}
((struct proxy_session *) proxy_sess)->backend_features = pr_table_nalloc(p, 0, 4);
feats_start = feats = (char *) resp->msg;
feats_len = strlen(feats);
token = pr_str_get_token2(&feats, (char *) feat_crlf, &token_len);
while (token != NULL) {
pr_signals_handle();
if (token_len > 0) {
/* The FEAT response lines in which we are interested all start with
* a single space, per RFC spec. Ignore any other lines.
*/
if (token[0] == ' ') {
char *key, *val, *ptr;
/* Find the next space in the string, to delimit our key/value pairs. */
ptr = strchr(token + 1, ' ');
if (ptr != NULL) {
key = pstrndup(p, token + 1, ptr - token - 1);
val = pstrdup(p, ptr + 1);
} else {
key = pstrdup(p, token + 1);
val = pstrdup(p, "");
}
pr_table_add(proxy_sess->backend_features, key, val, 0);
}
}
feats = token + token_len + 1;
/* Don't advance past the end of our FEAT response. */
if (feats > feats_start + feats_len) {
break;
}
token = pr_str_get_token2(&feats, (char *) feat_crlf, &token_len);
}
destroy_pool(tmp_pool);
return 0;
}
static pr_response_t *send_recv(pool *p, conn_t *conn, cmd_rec *cmd,
unsigned int *resp_nlines) {
int res, xerrno;
pr_response_t *resp;
res = proxy_ftp_ctrl_send_cmd(p, conn, cmd);
if (res < 0) {
xerrno = errno;
pr_trace_msg(trace_channel, 4,
"error sending '%s %s' to backend: %s", (char *) cmd->argv[0], cmd->arg,
strerror(xerrno));
errno = xerrno;
return NULL;
}
resp = proxy_ftp_ctrl_recv_resp(p, conn, resp_nlines, 0);
if (resp == NULL) {
xerrno = errno;
pr_trace_msg(trace_channel, 4,
"error receiving %s response from backend: %s", (char *) cmd->argv[0],
strerror(xerrno));
errno = xerrno;
return NULL;
}
return resp;
}
int proxy_ftp_sess_send_host(pool *p, const struct proxy_session *proxy_sess) {
pool *tmp_pool;
int xerrno = 0;
cmd_rec *cmd;
pr_response_t *resp;
unsigned int resp_nlines = 0;
const char *host;
if (p == NULL ||
proxy_sess == NULL) {
errno = EINVAL;
return -1;
}
if (pr_table_get(proxy_sess->backend_features, C_HOST, NULL) == NULL) {
pr_trace_msg(trace_channel, 9,
"HOST not supported by backend server, ignoring");
return 0;
}
tmp_pool = make_sub_pool(p);
host = proxy_conn_get_host(proxy_sess->dst_pconn);
/* Make sure we format the HOST parameter properly for an IPv6 address. */
if (pr_netaddr_is_v6(host) == TRUE) {
host = pstrcat(tmp_pool, "[", host, "]", NULL);
}
cmd = pr_cmd_alloc(tmp_pool, 2, C_HOST, host);
cmd->arg = pstrdup(tmp_pool, host);
resp = send_recv(tmp_pool, proxy_sess->backend_ctrl_conn, cmd, &resp_nlines);
if (resp == NULL) {
xerrno = errno;
destroy_pool(tmp_pool);
errno = xerrno;
return -1;
}
if (resp->num[0] != '2') {
pr_trace_msg(trace_channel, 4,
"received unexpected %s response code %s from backend",
(char *) cmd->argv[0], resp->num);
destroy_pool(tmp_pool);
errno = EPERM;
return -1;
}
destroy_pool(tmp_pool);
return 0;
}
int proxy_ftp_sess_send_auth_tls(pool *p,
const struct proxy_session *proxy_sess) {
int uri_tls, use_tls, xerrno;
const char *auth_feat;
array_header *auth_feats = NULL;
pool *tmp_pool;
cmd_rec *cmd;
pr_response_t *resp;
unsigned int resp_nlines = 0;
config_rec *c;
if (p == NULL ||
proxy_sess == NULL) {
errno = EINVAL;
return -1;
}
use_tls = proxy_tls_using_tls();
/* Note: In theory, use_tls should never be MATCH_CLIENT here; that should
* have been handled earlier, in the connect routines of the forward/reverse
* APIs.
*/
if (use_tls == PROXY_TLS_ENGINE_MATCH_CLIENT) {
proxy_tls_match_client_tls();
use_tls = proxy_tls_using_tls();
}
if (use_tls == PROXY_TLS_ENGINE_OFF) {
pr_trace_msg(trace_channel, 19,
"TLS support not enabled/desired, skipping 'AUTH TLS' command");
return 0;
}
if (use_tls == PROXY_TLS_ENGINE_IMPLICIT) {
pr_trace_msg(trace_channel, 19,
"implicit FTPS support requested, skipping 'AUTH TLS' command");
return 0;
}
/* Check for any per-URI scheme-based TLS requirements. */
uri_tls = proxy_conn_get_tls(proxy_sess->dst_pconn);
auth_feat = pr_table_get(proxy_sess->backend_features, C_AUTH, NULL);
if (auth_feat == NULL) {
/* Backend server does not indicate that it supports AUTH via FEAT.
*
* Even though this is the case, we will still try to send the AUTH
* command. A malicious attacker could be modifying the plaintext
* FEAT listing, to make us think that TLS is not supported, and thus
* prevent us from encrypting the session (a la "SSL stripping").
*/
/* If TLS is required, then complain loudly. */
if (uri_tls == PROXY_TLS_ENGINE_ON ||
use_tls == PROXY_TLS_ENGINE_ON) {
const char *ip_str;
ip_str = pr_netaddr_get_ipstr(proxy_sess->backend_ctrl_conn->remote_addr);
if (uri_tls == PROXY_TLS_ENGINE_ON) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"backend server %s does not support AUTH TLS (see FEAT response) but "
"URI '%.100s' requires TLS, attempting anyway", ip_str,
proxy_conn_get_uri(proxy_sess->dst_pconn));
} else if (use_tls == PROXY_TLS_ENGINE_ON) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"backend server %s does not support AUTH TLS (see FEAT response) but "
"ProxyTLSEngine requires TLS, attempting anyway", ip_str);
}
}
pr_trace_msg(trace_channel, 9,
"backend server does not support AUTH TLS (via FEAT)");
}
tmp_pool = make_sub_pool(p);
/* Note: the FEAT response against IIS servers shows e.g.:
*
* 211-Extended features supported:
* LANG EN*
* UTF8
* AUTH TLS;TLS-C;SSL;TLS-P;
* PBSZ
* PROT C;P;
* CCC
* HOST
* SIZE
* MDTM
* REST STREAM
* 211 END
*
* Note how the AUTH and PROT values are not exactly as specified
* in RFC 4217. This means we'll need to deal with it as is. There will
* be other servers with other FEAT response formats, too.
*/
if (parse_feat(tmp_pool, auth_feat, &auth_feats) > 0) {
register unsigned int i;
pr_trace_msg(trace_channel, 9, "parsed FEAT value '%s' into %d %s",
auth_feat, auth_feats->nelts,
auth_feats->nelts != 1 ? "values" : "value");
for (i = 0; i < auth_feats->nelts; i++) {
char *val;
val = ((char **) auth_feats->elts)[i];
pr_trace_msg(trace_channel, 9, " %s", val);
}
}
/* XXX How should we interoperate with servers that support/want the
* older formats, e.g.:
*
* AUTH SSL (which automatically assumes PBSZ 0, PROT P)
* AUTH TLS-P (synonym for AUTH SSL)
* AUTH TLS-C (synonym for AUTH TLS)
*/
cmd = pr_cmd_alloc(tmp_pool, 2, C_AUTH, "TLS");
cmd->arg = pstrdup(tmp_pool, "TLS");
resp = send_recv(tmp_pool, proxy_sess->backend_ctrl_conn, cmd, &resp_nlines);
if (resp == NULL) {
xerrno = errno;
proxy_netio_use(PR_NETIO_STRM_CTRL, NULL);
destroy_pool(tmp_pool);
errno = xerrno;
return -1;
}
if (resp->num[0] != '2') {
if (uri_tls != PROXY_TLS_ENGINE_ON &&
use_tls != PROXY_TLS_ENGINE_ON) {
proxy_tls_set_tls(PROXY_TLS_ENGINE_OFF);
errno = ENOSYS;
return -1;
}
/* XXX Some older servers might respond with a 334 response code, per
* RFC 2228. See, for example:
* http://serverfault.com/questions/640978/ftp-alter-server-response-in-transit
*/
pr_trace_msg(trace_channel, 4,
"received unexpected %s response code %s from backend",
(char *) cmd->argv[0], resp->num);
proxy_netio_use(PR_NETIO_STRM_CTRL, NULL);
destroy_pool(tmp_pool);
errno = EPERM;
return -1;
}
/* Now that we have our AUTH TLS, check for TLS-related configs. */
c = find_config(main_server->conf, CONF_PARAM,
"ProxyTLSTransferProtectionPolicy", FALSE);
if (c != NULL) {
tls_xfer_prot_policy = *((int *) c->argv[0]);
switch (tls_xfer_prot_policy) {
case PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_CLIENT:
case PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_REQUIRED:
proxy_tls_set_data_prot(TRUE);
break;
case PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_CLEAR:
proxy_tls_set_data_prot(FALSE);
break;
default:
/* ignore */
break;
}
}
destroy_pool(tmp_pool);
return 0;
}
int proxy_ftp_sess_send_pbsz_prot(pool *p,
const struct proxy_session *proxy_sess) {
int have_feat_pbsz = FALSE, have_feat_prot = FALSE, res, send_prot = FALSE,
use_tls, xerrno;
pool *tmp_pool;
cmd_rec *cmd;
pr_response_t *resp;
unsigned int resp_nlines = 0;
if (p == NULL ||
proxy_sess == NULL) {
errno = EINVAL;
return -1;
}
use_tls = proxy_tls_using_tls();
if (use_tls == PROXY_TLS_ENGINE_OFF) {
pr_trace_msg(trace_channel, 19,
"TLS support not enabled/desired, skipping");
return 0;
}
/* Some FTPS servers don't properly list PBSZ, PROT in their FEAT
* responses. If we are to use TLS, though, we will need to send PBSZ,
* PROT commands in order to comply with RFC 4217.
*
* Thus we will opportunistically send PBSZ, PROT commands anyway.
* If these are listed in the server's FEAT response, and the commands
* fail, then we will return an error; otherwise, we log the response
* and move on.
*/
/* PBSZ */
if (pr_table_get(proxy_sess->backend_features, C_PBSZ, NULL) != NULL) {
have_feat_pbsz = TRUE;
}
tmp_pool = make_sub_pool(p);
cmd = pr_cmd_alloc(tmp_pool, 2, C_PBSZ, "0");
cmd->arg = pstrdup(tmp_pool, "0");
res = 0;
resp = send_recv(tmp_pool, proxy_sess->backend_ctrl_conn, cmd, &resp_nlines);
xerrno = errno;
if (resp == NULL) {
res = -1;
} else {
if (resp->num[0] != '2') {
pr_trace_msg(trace_channel, 4,
"received unexpected %s response code %s from backend",
(char *) cmd->argv[0], resp->num);
xerrno = EPERM;
res = -1;
}
}
destroy_pool(tmp_pool);
if (have_feat_pbsz == TRUE &&
res < 0) {
errno = xerrno;
return -1;
}
/* PROT */
if (pr_table_get(proxy_sess->backend_features, C_PROT, NULL) != NULL) {
have_feat_prot = TRUE;
}
/* If our protection policy overrides that of the client, then we will
* send our own PROT command. Otherwise, we don't send PROT, because the
* frontend client will.
*
* However, what if the frontend client is NOT using FTPS? In that case,
* we should send PROT, even for the "client" policy.
*/
switch (tls_xfer_prot_policy) {
case PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_CLEAR:
case PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_REQUIRED:
send_prot = TRUE;
break;
case PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_CLIENT:
send_prot = FALSE;
if (session.rfc2228_mech == NULL) {
send_prot = TRUE;
}
break;
}
if (send_prot == TRUE) {
const char *prot;
resp_nlines = 0;
tmp_pool = make_sub_pool(p);
switch (tls_xfer_prot_policy) {
case PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_CLEAR:
prot = "C";
break;
case PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_CLIENT:
case PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_REQUIRED:
default:
prot = "P";
break;
}
cmd = pr_cmd_alloc(tmp_pool, 2, C_PROT, prot);
cmd->arg = pstrdup(tmp_pool, prot);
res = 0;
resp = send_recv(tmp_pool, proxy_sess->backend_ctrl_conn, cmd,
&resp_nlines);
xerrno = errno;
if (resp == NULL) {
res = -1;
} else {
if (resp->num[0] != '2') {
pr_trace_msg(trace_channel, 4,
"received unexpected %s response code %s from backend",
(char *) cmd->argv[0], resp->num);
xerrno = EPERM;
res = -1;
}
}
destroy_pool(tmp_pool);
if (have_feat_prot == TRUE &&
res < 0) {
errno = xerrno;
return -1;
}
}
return 0;
}
proftpd-mod_proxy-0.9.5/lib/proxy/ftp/xfer.c 0000664 0000000 0000000 00000047057 14757370167 0021107 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy FTP data transfer routines
* Copyright (c) 2013-2025 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "include/proxy/conn.h"
#include "include/proxy/inet.h"
#include "include/proxy/ftp/conn.h"
#include "include/proxy/ftp/ctrl.h"
#include "include/proxy/ftp/msg.h"
#include "include/proxy/ftp/xfer.h"
/* From response.c */
extern pr_response_t *resp_list, *resp_err_list;
static const char *trace_channel = "proxy.ftp.xfer";
int proxy_ftp_xfer_prepare_active(int policy_id, cmd_rec *cmd,
const char *error_code, struct proxy_session *proxy_sess, int flags) {
int backend_family, bind_family, ipv6_backend, res, xerrno = 0;
cmd_rec *actv_cmd;
const pr_netaddr_t *backend_addr, *bind_addr = NULL;
pr_response_t *resp;
unsigned int resp_nlines = 0;
conn_t *data_conn = NULL;
char *active_cmd;
const char *resp_msg = NULL;
if (cmd == NULL ||
error_code == NULL ||
proxy_sess == NULL ||
proxy_sess->backend_ctrl_conn == NULL) {
errno = EINVAL;
return -1;
}
ipv6_backend = FALSE;
backend_addr = proxy_conn_get_addr(proxy_sess->dst_pconn, NULL);
if (pr_netaddr_get_family(backend_addr) != AF_INET) {
ipv6_backend = TRUE;
}
switch (policy_id) {
case PR_CMD_PORT_ID:
/* If we have an IPv6 address for the backend server, automatically switch
* to using EPRT by falling through to the EPRT case.
*/
if (ipv6_backend == FALSE) {
active_cmd = C_PORT;
break;
}
case PR_CMD_EPRT_ID:
/* If the remote host does not mention EPRT in its features, fall back
* to using PORT.
*/
active_cmd = C_EPRT;
if (pr_table_get(proxy_sess->backend_features, C_EPRT, NULL) == NULL) {
pr_trace_msg(trace_channel, 19,
"EPRT not supported by backend server (via FEAT), using PORT");
if (proxy_sess->dataxfer_policy == PR_CMD_EPRT_ID) {
proxy_sess->dataxfer_policy = PR_CMD_PORT_ID;
}
active_cmd = C_PORT;
policy_id = PR_CMD_PORT_ID;
}
break;
default:
/* In this case, the cmd we were given is the one we should send to
* the backend server -- but only if it is either EPRT or PORT.
*/
if (pr_cmd_cmp(cmd, PR_CMD_EPRT_ID) != 0 &&
pr_cmd_cmp(cmd, PR_CMD_PORT_ID) != 0) {
pr_trace_msg(trace_channel, 9,
"illegal FTP active transfer command '%s'", (char *) cmd->argv[0]);
errno = EINVAL;
return -1;
}
active_cmd = cmd->argv[0];
/* If we have an IPv6 address for the backend server, automatically switch
* to using EPRT.
*/
if (pr_cmd_cmp(cmd, PR_CMD_PORT_ID) == 0 &&
ipv6_backend == TRUE) {
pr_trace_msg(trace_channel, 19,
"automatically switching from %s to %s for IPv6 backend server",
active_cmd, C_EPRT);
active_cmd = C_EPRT;
policy_id = PR_CMD_EPRT_ID;
}
if (pr_cmd_cmp(cmd, PR_CMD_EPRT_ID) == 0) {
/* If the remote host does not mention EPRT in its features, fall back
* to using PORT.
*/
if (pr_table_get(proxy_sess->backend_features, C_EPRT, NULL) == NULL) {
pr_trace_msg(trace_channel, 19,
"EPRT not supported by backend server (via FEAT), using PORT");
if (proxy_sess->dataxfer_policy == PR_CMD_EPRT_ID) {
proxy_sess->dataxfer_policy = PR_CMD_PORT_ID;
}
active_cmd = C_PORT;
policy_id = PR_CMD_PORT_ID;
}
}
break;
}
bind_addr = proxy_sess->src_addr;
if (bind_addr == NULL) {
bind_addr = session.c->local_addr;
}
if (pr_netaddr_is_loopback(bind_addr) == TRUE &&
pr_netaddr_is_loopback(proxy_sess->backend_ctrl_conn->remote_addr) != TRUE) {
const char *local_name;
const pr_netaddr_t *local_addr;
local_name = pr_netaddr_get_localaddr_str(cmd->pool);
local_addr = pr_netaddr_get_addr(cmd->pool, local_name, NULL);
if (local_addr != NULL) {
pr_trace_msg(trace_channel, 14,
"%s is a loopback address, using %s instead",
pr_netaddr_get_ipstr(bind_addr), pr_netaddr_get_ipstr(local_addr));
bind_addr = local_addr;
}
}
/* Need to check the family of the bind addr against the family of the
* backend server.
*/
backend_family = pr_netaddr_get_family(proxy_sess->backend_ctrl_conn->remote_addr);
bind_family = pr_netaddr_get_family(bind_addr);
if (bind_family == backend_family) {
#ifdef PR_USE_IPV6
if (pr_netaddr_use_ipv6()) {
/* Make sure that the family is NOT IPv6, even though the local and
* remote families match. The PORT command cannot be used for IPv6
* addresses -- but EPRT CAN be used for IPv6 addresses.
*/
if (bind_family == AF_INET6 &&
policy_id == PR_CMD_PORT_ID) {
pr_netaddr_t *mapped_addr;
mapped_addr = pr_netaddr_v6tov4(cmd->pool, bind_addr);
if (mapped_addr != NULL) {
pr_trace_msg(trace_channel, 9,
"converting local IPv6 address '%s' to IPv4 address '%s' for "
"active transfer using PORT", pr_netaddr_get_ipstr(bind_addr),
pr_netaddr_get_ipstr(mapped_addr));
bind_addr = mapped_addr;
} else {
pr_trace_msg(trace_channel, 3,
"unable to convert IPv6 address '%s' to IPv4: %s",
pr_netaddr_get_ipstr(bind_addr), strerror(errno));
}
}
}
#endif /* PR_USE_IPV6 */
} else {
if (backend_family == AF_INET) {
pr_netaddr_t *mapped_addr;
/* In this scenario, the remote peer is an IPv4 (or IPv4-mapped IPv6)
* peer, so make sure we use an IPv4 local address.
*/
mapped_addr = pr_netaddr_v6tov4(cmd->pool, bind_addr);
if (mapped_addr != NULL) {
pr_trace_msg(trace_channel, 9,
"converting local IPv6 address '%s' to IPv4 address '%s' for "
"active transfer with IPv4 peer", pr_netaddr_get_ipstr(bind_addr),
pr_netaddr_get_ipstr(mapped_addr));
bind_addr = mapped_addr;
} else {
pr_trace_msg(trace_channel, 3,
"unable to convert IPv6 address '%s' to IPv4: %s",
pr_netaddr_get_ipstr(bind_addr), strerror(errno));
}
}
}
if (proxy_sess->backend_data_conn != NULL) {
/* Make sure that we only have one backend data connection. */
proxy_inet_close(session.pool, proxy_sess->backend_data_conn);
pr_inet_close(session.pool, proxy_sess->backend_data_conn);
proxy_sess->backend_data_conn = NULL;
}
data_conn = proxy_ftp_conn_listen(cmd->tmp_pool, bind_addr, FALSE);
if (data_conn == NULL) {
xerrno = errno;
pr_response_add_err(error_code,
_("Unable to build data connection: Internal error"));
pr_response_flush(&resp_err_list);
errno = xerrno;
return -1;
}
proxy_sess->backend_data_conn = data_conn;
switch (pr_cmd_get_id(active_cmd)) {
case PR_CMD_PORT_ID:
resp_msg = proxy_ftp_msg_fmt_addr(cmd->tmp_pool, data_conn->local_addr,
data_conn->local_port, FALSE);
break;
case PR_CMD_EPRT_ID:
resp_msg = proxy_ftp_msg_fmt_ext_addr(cmd->tmp_pool,
data_conn->local_addr, data_conn->local_port, PR_CMD_EPRT_ID, FALSE);
break;
}
actv_cmd = pr_cmd_alloc(cmd->tmp_pool, 2, active_cmd, resp_msg);
actv_cmd->arg = (char *) resp_msg;
pr_cmd_clear_cache(actv_cmd);
res = proxy_ftp_ctrl_send_cmd(cmd->tmp_pool, proxy_sess->backend_ctrl_conn,
actv_cmd);
if (res < 0) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error sending %s to backend: %s", (char *) actv_cmd->argv[0],
strerror(xerrno));
proxy_inet_close(session.pool, proxy_sess->backend_data_conn);
pr_inet_close(session.pool, proxy_sess->backend_data_conn);
proxy_sess->backend_data_conn = NULL;
pr_response_add_err(error_code, "%s: %s", (char *) cmd->argv[0],
strerror(xerrno));
pr_response_flush(&resp_err_list);
errno = xerrno;
return -1;
}
resp = proxy_ftp_ctrl_recv_resp(cmd->tmp_pool, proxy_sess->backend_ctrl_conn,
&resp_nlines, flags);
if (resp == NULL) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error receiving %s response from backend: %s",
(char *) actv_cmd->argv[0], strerror(xerrno));
proxy_inet_close(session.pool, proxy_sess->backend_data_conn);
pr_inet_close(session.pool, proxy_sess->backend_data_conn);
proxy_sess->backend_data_conn = NULL;
pr_response_add_err(error_code, "%s: %s", (char *) cmd->argv[0],
strerror(xerrno));
pr_response_flush(&resp_err_list);
errno = xerrno;
return -1;
}
if (resp->num[0] != '2') {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"received non-2xx response from backend for %s: %s %s",
(char *) actv_cmd->argv[0], resp->num, resp->msg);
proxy_inet_close(session.pool, proxy_sess->backend_data_conn);
pr_inet_close(session.pool, proxy_sess->backend_data_conn);
proxy_sess->backend_data_conn = NULL;
if (policy_id == PR_CMD_EPRT_ID) {
/* If using EPRT failed, try again using PORT, and switch the
* DataTransferPolicy (if EPRT) to be PORT, for future attempts.
*/
if (proxy_sess->dataxfer_policy == PR_CMD_EPRT_ID) {
pr_trace_msg(trace_channel, 15,
"falling back from EPRT to PORT DataTransferPolicy");
proxy_sess->dataxfer_policy = PR_CMD_PORT_ID;
}
return proxy_ftp_xfer_prepare_active(PR_CMD_PORT_ID, cmd,
error_code, proxy_sess, flags);
}
pr_response_add_err(error_code, "%s", resp->msg);
pr_response_flush(&resp_err_list);
errno = EINVAL;
return -1;
}
return 0;
}
const pr_netaddr_t *proxy_ftp_xfer_prepare_passive(int policy_id, cmd_rec *cmd,
const char *error_code, struct proxy_session *proxy_sess, int flags) {
int ipv6_backend, res, xerrno = 0;
cmd_rec *pasv_cmd;
const pr_netaddr_t *backend_addr, *remote_addr = NULL;
pr_response_t *resp;
unsigned int resp_nlines = 0;
unsigned short remote_port;
char *passive_cmd, *passive_respcode = NULL;
if (cmd == NULL ||
error_code == NULL ||
proxy_sess == NULL ||
proxy_sess->backend_ctrl_conn == NULL) {
errno = EINVAL;
return NULL;
}
/* Whether we send a PASV (and expect 227) or an EPSV (and expect 229)
* needs to depend on the policy_id, AND on the backend address.
*/
ipv6_backend = FALSE;
backend_addr = proxy_conn_get_addr(proxy_sess->dst_pconn, NULL);
if (pr_netaddr_get_family(backend_addr) != AF_INET) {
ipv6_backend = TRUE;
}
switch (policy_id) {
case PR_CMD_PASV_ID:
/* If we have an IPv6 address for the backend server, automatically switch
* to using EPSV by falling through to the EPSV case.
*/
if (ipv6_backend == FALSE) {
passive_cmd = C_PASV;
break;
}
case PR_CMD_EPSV_ID: {
int epsv_supported = TRUE;
passive_cmd = C_EPSV;
if (pr_table_get(proxy_sess->backend_features, C_EPSV, NULL) == NULL) {
epsv_supported = FALSE;
/* If the remote host does not mention EPSV in its features, fall back
* to using PASV. Note, however, that some servers (e.g. pure-ftpd)
* only mention EPRT in their FEAT to cover both EPRT and EPSV.
*/
if (pr_table_get(proxy_sess->backend_features, C_EPRT, NULL) != NULL) {
epsv_supported = TRUE;
}
}
if (epsv_supported == FALSE) {
pr_trace_msg(trace_channel, 19,
"EPSV not supported by backend server (via FEAT), using PASV");
if (proxy_sess->dataxfer_policy == PR_CMD_EPSV_ID) {
proxy_sess->dataxfer_policy = PR_CMD_PASV_ID;
}
passive_cmd = C_PASV;
policy_id = PR_CMD_PASV_ID;
}
break;
}
default:
/* In this case, the cmd we were given is the one we should send to
* the backend server -- but only if it is either EPSV or PASV.
*/
if (pr_cmd_cmp(cmd, PR_CMD_EPSV_ID) != 0 &&
pr_cmd_cmp(cmd, PR_CMD_PASV_ID) != 0) {
pr_trace_msg(trace_channel, 9,
"illegal FTP passive transfer command '%s'", (char *) cmd->argv[0]);
errno = EINVAL;
return NULL;
}
passive_cmd = cmd->argv[0];
/* If we have an IPv6 address for the backend server, automatically switch
* to using EPSV.
*/
if (pr_cmd_cmp(cmd, PR_CMD_PASV_ID) == 0 &&
ipv6_backend == TRUE) {
pr_trace_msg(trace_channel, 19,
"automatically switching from %s to %s for IPv6 backend server",
passive_cmd, C_EPSV);
passive_cmd = C_EPSV;
}
if (pr_cmd_cmp(cmd, PR_CMD_EPSV_ID) == 0) {
int epsv_supported = FALSE;
if (pr_table_get(proxy_sess->backend_features, C_EPSV, NULL) == NULL) {
/* If the remote host does not mention EPSV in its features, fall back
* to using PASV. Note, however, that some servers (e.g. pure-ftpd)
* only mention EPRT in their FEAT to cover both EPRT and EPSV.
*/
if (pr_table_get(proxy_sess->backend_features, C_EPRT, NULL) != NULL) {
epsv_supported = TRUE;
}
}
if (epsv_supported == FALSE) {
pr_trace_msg(trace_channel, 19,
"EPSV not supported by backend server (via FEAT), using PASV");
if (proxy_sess->dataxfer_policy == PR_CMD_EPSV_ID) {
proxy_sess->dataxfer_policy = PR_CMD_PASV_ID;
}
passive_cmd = C_PASV;
policy_id = PR_CMD_PASV_ID;
}
}
break;
}
pasv_cmd = pr_cmd_alloc(cmd->tmp_pool, 1, passive_cmd);
switch (pr_cmd_get_id(pasv_cmd->argv[0])) {
case PR_CMD_PASV_ID:
passive_respcode = R_227;
break;
case PR_CMD_EPSV_ID:
passive_respcode = R_229;
break;
}
res = proxy_ftp_ctrl_send_cmd(cmd->tmp_pool, proxy_sess->backend_ctrl_conn,
pasv_cmd);
if (res < 0) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error sending %s to backend: %s", (char *) pasv_cmd->argv[0],
strerror(xerrno));
pr_response_add_err(error_code, "%s: %s", (char *) cmd->argv[0],
strerror(xerrno));
pr_response_flush(&resp_err_list);
errno = xerrno;
return NULL;
}
resp = proxy_ftp_ctrl_recv_resp(cmd->tmp_pool, proxy_sess->backend_ctrl_conn,
&resp_nlines, flags);
if (resp == NULL) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error receiving %s response from backend: %s",
(char *) pasv_cmd->argv[0], strerror(xerrno));
pr_response_add_err(error_code, "%s: %s", (char *) cmd->argv[0],
strerror(xerrno));
pr_response_flush(&resp_err_list);
errno = xerrno;
return NULL;
}
/* We specifically expect a 227 or 229 response code here; anything else is
* an error. Right?
*/
if (strncmp(resp->num, passive_respcode, 4) != 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"received response code %s, but expected %s for %s command", resp->num,
passive_respcode, (char *) pasv_cmd->argv[0]);
if (policy_id == PR_CMD_EPSV_ID) {
/* If using EPSV failed, try again using PASV, and switch the
* DataTransferPolicy (if EPSV) to be PASV, for future attempts.
*/
if (proxy_sess->dataxfer_policy == PR_CMD_EPSV_ID) {
pr_trace_msg(trace_channel, 15,
"falling back from EPSV to PASV DataTransferPolicy");
proxy_sess->dataxfer_policy = PR_CMD_PASV_ID;
}
return proxy_ftp_xfer_prepare_passive(PR_CMD_PASV_ID, cmd,
error_code, proxy_sess, flags);
}
res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool,
proxy_sess->frontend_ctrl_conn, resp, resp_nlines);
errno = EPERM;
return NULL;
}
switch (pr_cmd_get_id(pasv_cmd->argv[0])) {
case PR_CMD_PASV_ID: {
int remote_family;
remote_family = pr_netaddr_get_family(proxy_sess->backend_ctrl_conn->remote_addr);
remote_addr = proxy_ftp_msg_parse_addr(proxy_sess->dataxfer_pool,
resp->msg, remote_family);
break;
}
case PR_CMD_EPSV_ID:
remote_addr = proxy_ftp_msg_parse_ext_addr(proxy_sess->dataxfer_pool,
resp->msg, backend_addr, PR_CMD_EPSV_ID, NULL);
break;
}
if (remote_addr == NULL) {
xerrno = errno;
pr_trace_msg(trace_channel, 2, "error parsing %s response '%s': %s",
(char *) pasv_cmd->argv[0], resp->msg, strerror(xerrno));
xerrno = EPERM;
pr_response_add_err(error_code, "%s: %s", (char *) cmd->argv[0],
strerror(xerrno));
pr_response_flush(&resp_err_list);
errno = xerrno;
return NULL;
}
remote_port = ntohs(pr_netaddr_get_port(remote_addr));
/* See if the given address matches the address to which we originally
* connected.
*/
if (pr_netaddr_cmp(remote_addr,
proxy_sess->backend_ctrl_conn->remote_addr) != 0) {
pr_trace_msg(trace_channel, 2,
"backend passive transfer address %s does not match backend control "
"connection address %s", pr_netaddr_get_ipstr(remote_addr),
pr_netaddr_get_ipstr(proxy_sess->backend_ctrl_conn->remote_addr));
if (proxy_opts & PROXY_OPT_IGNORE_FOREIGN_ADDRESS) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"Ignoring %s address %s per IgnoreForeignAddress ProxyOption, using %s "
"instead", (char *) pasv_cmd->argv[0],
pr_netaddr_get_ipstr(remote_addr),
pr_netaddr_get_ipstr(proxy_sess->backend_ctrl_conn->remote_addr));
remote_addr = pr_netaddr_dup(proxy_sess->dataxfer_pool,
proxy_sess->backend_ctrl_conn->remote_addr);
pr_netaddr_set_port2((pr_netaddr_t *) remote_addr, remote_port);
} else {
if (!(proxy_opts & PROXY_OPT_ALLOW_FOREIGN_ADDRESS)) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"Refused %s address %s (address mismatch with %s)",
(char *) pasv_cmd->argv[0], pr_netaddr_get_ipstr(remote_addr),
pr_netaddr_get_ipstr(proxy_sess->backend_ctrl_conn->remote_addr));
xerrno = EPERM;
pr_response_add_err(error_code, "%s: %s", (char *) cmd->argv[0],
strerror(xerrno));
pr_response_flush(&resp_err_list);
errno = xerrno;
return NULL;
}
}
}
if (remote_port < 1024) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"Refused %s port %hu (below 1024)",
(char *) pasv_cmd->argv[0], remote_port);
xerrno = EPERM;
pr_response_add_err(error_code, "%s: %s", (char *) cmd->argv[0],
strerror(xerrno));
pr_response_flush(&resp_err_list);
errno = xerrno;
return NULL;
}
pr_trace_msg(trace_channel, 12,
"obtained address %s#%d for passive data transfer",
pr_netaddr_get_ipstr(remote_addr), ntohs(pr_netaddr_get_port(remote_addr)));
return remote_addr;
}
proftpd-mod_proxy-0.9.5/lib/proxy/inet.c 0000664 0000000 0000000 00000011651 14757370167 0020300 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy Inet implementation
* Copyright (c) 2015-2021 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "proxy/netio.h"
#include "proxy/inet.h"
conn_t *proxy_inet_accept(pool *p, conn_t *data_conn, conn_t *ctrl_conn,
int rfd, int wfd, int resolve) {
int xerrno;
conn_t *conn;
pr_netio_t *curr_netio;
curr_netio = proxy_netio_unset(PR_NETIO_STRM_DATA, "inet_accept");
conn = pr_inet_accept(p, data_conn, ctrl_conn, rfd, wfd,
(unsigned char) resolve);
xerrno = errno;
proxy_netio_set(PR_NETIO_STRM_DATA, curr_netio);
errno = xerrno;
return conn;
}
void proxy_inet_close(pool *p, conn_t *conn) {
if (conn != NULL) {
/* Note that we do our own close here, rather than relying on the
* core Inet's close, as that one simply relies on the connection
* cleanup callback -- and we want to use our own Proxy Netio API
* functions for closing, too.
*/
/* Shutdowns first, then closes. */
if (conn->instrm != NULL) {
proxy_netio_shutdown(conn->instrm, 0);
}
if (conn->outstrm != NULL) {
proxy_netio_shutdown(conn->outstrm, 1);
}
if (conn->instrm != NULL) {
proxy_netio_close(conn->instrm);
conn->instrm = NULL;
}
if (conn->outstrm != NULL) {
proxy_netio_close(conn->outstrm);
conn->outstrm = NULL;
}
if (conn->listen_fd != -1) {
(void) close(conn->listen_fd);
conn->listen_fd = -1;
}
if (conn->rfd != -1) {
(void) close(conn->rfd);
conn->rfd = -1;
}
if (conn->wfd != -1) {
(void) close(conn->wfd);
conn->wfd = -1;
}
}
}
int proxy_inet_connect(pool *p, conn_t *conn, const pr_netaddr_t *addr,
int port) {
int instrm_type = -1, outstrm_type = -1, res, xerrno;
pr_netio_t *in_netio = NULL, *out_netio = NULL;
if (conn != NULL) {
if (conn->instrm != NULL) {
instrm_type = conn->instrm->strm_type;
in_netio = proxy_netio_unset(instrm_type, "inet_connect");
}
if (conn->outstrm != NULL) {
outstrm_type = conn->outstrm->strm_type;
if (outstrm_type != instrm_type) {
out_netio = proxy_netio_unset(outstrm_type, "inet_connect");
}
}
}
res = pr_inet_connect(p, conn, addr, port);
xerrno = errno;
if (in_netio != NULL) {
proxy_netio_set(instrm_type, in_netio);
}
if (out_netio != NULL) {
proxy_netio_set(outstrm_type, out_netio);
}
errno = xerrno;
return res;
}
int proxy_inet_listen(pool *p, conn_t *conn, int backlog, int flags) {
int instrm_type = -1, outstrm_type = -1, res, xerrno;
pr_netio_t *in_netio = NULL, *out_netio = NULL;
if (conn != NULL) {
if (conn->instrm != NULL) {
instrm_type = conn->instrm->strm_type;
in_netio = proxy_netio_unset(instrm_type, "inet_listen");
}
if (conn->outstrm != NULL) {
outstrm_type = conn->outstrm->strm_type;
if (outstrm_type != instrm_type) {
out_netio = proxy_netio_unset(outstrm_type, "inet_listen");
}
}
}
res = pr_inet_listen(p, conn, backlog, flags);
xerrno = errno;
if (in_netio != NULL) {
proxy_netio_set(instrm_type, in_netio);
}
if (out_netio != NULL) {
proxy_netio_set(outstrm_type, out_netio);
}
errno = xerrno;
return res;
}
conn_t *proxy_inet_openrw(pool *p, conn_t *conn, const pr_netaddr_t *addr,
int strm_type, int fd, int rfd, int wfd, int resolve) {
int xerrno;
conn_t *new_conn;
pr_netio_t *curr_netio = NULL;
curr_netio = proxy_netio_unset(strm_type, "inet_openrw");
new_conn = pr_inet_openrw(p, conn, addr, strm_type, fd, rfd, wfd, resolve);
xerrno = errno;
proxy_netio_set(strm_type, curr_netio);
if (new_conn != NULL) {
/* Note: pr_inet_openrw() calls pr_inet_copy_conn(), which registers
* a cleanup on the create object. But we clean up our own data,
* so that cleanup, when run, will attempt a double-free. Thus we
* unregister that cleanup here.
*/
unregister_cleanup(new_conn->pool, new_conn, NULL);
}
errno = xerrno;
return new_conn;
}
proftpd-mod_proxy-0.9.5/lib/proxy/netio.c 0000664 0000000 0000000 00000020731 14757370167 0020456 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy NetIO implementation
* Copyright (c) 2015-2021 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "proxy/netio.h"
static const char *trace_channel = "proxy.netio";
static pr_netio_t *ctrl_netio = NULL;
static pr_netio_t *data_netio = NULL;
static const char *netio_strm_typestr(int strm_type) {
const char *typestr = "(unknown)";
switch (strm_type) {
case PR_NETIO_STRM_CTRL:
typestr = "ctrl";
break;
case PR_NETIO_STRM_DATA:
typestr = "data";
break;
case PR_NETIO_STRM_OTHR:
typestr = "othr";
break;
default:
break;
}
return typestr;
}
int proxy_netio_use(int strm_type, pr_netio_t *netio) {
int res;
switch (strm_type) {
case PR_NETIO_STRM_CTRL:
ctrl_netio = netio;
res = 0;
break;
case PR_NETIO_STRM_DATA:
data_netio = netio;
res = 0;
break;
default:
errno = ENOSYS;
res = -1;
}
return res;
}
int proxy_netio_using(int strm_type, pr_netio_t **netio) {
int res;
if (netio == NULL) {
errno = EINVAL;
return -1;
}
switch (strm_type) {
case PR_NETIO_STRM_CTRL:
*netio = ctrl_netio;
res = 0;
break;
case PR_NETIO_STRM_DATA:
*netio = data_netio;
res = 0;
break;
default:
errno = ENOENT;
res = -1;
}
return res;
}
pr_netio_t *proxy_netio_unset(int strm_type, const char *fn) {
pr_netio_t *netio = NULL;
if (fn == NULL) {
errno = EINVAL;
return NULL;
}
netio = pr_get_netio(strm_type);
if (netio != NULL) {
const char *owner_name = "core", *typestr;
if (netio->owner_name != NULL) {
owner_name = netio->owner_name;
}
typestr = netio_strm_typestr(strm_type);
pr_trace_msg(trace_channel, 18, "(%s) found %s %s NetIO", fn, owner_name,
typestr);
if (pr_unregister_netio(strm_type) < 0) {
pr_trace_msg(trace_channel, 3,
"(%s) error unregistering %s NetIO: %s", fn, typestr, strerror(errno));
}
}
/* Regardless of whether we found a previously registered NetIO, make
* sure to use our own NetIO, if any.
*/
switch (strm_type) {
case PR_NETIO_STRM_CTRL:
if (ctrl_netio != NULL) {
if (pr_register_netio(ctrl_netio, strm_type) < 0) {
pr_trace_msg(trace_channel, 3,
"(%s) error registering proxy %s NetIO: %s", fn,
netio_strm_typestr(strm_type), strerror(errno));
} else {
pr_trace_msg(trace_channel, 19,
"(%s) using proxy %s NetIO", fn, netio_strm_typestr(strm_type));
}
}
break;
case PR_NETIO_STRM_DATA:
if (data_netio != NULL) {
if (pr_register_netio(data_netio, strm_type) < 0) {
pr_trace_msg(trace_channel, 3,
"(%s) error registering proxy %s NetIO: %s", fn,
netio_strm_typestr(strm_type), strerror(errno));
} else {
pr_trace_msg(trace_channel, 19,
"(%s) using proxy %s NetIO", fn, netio_strm_typestr(strm_type));
}
}
break;
default:
break;
}
return netio;
}
int proxy_netio_set(int strm_type, pr_netio_t *netio) {
/* Note: we DO want to unregister the registered stream type, assuming we
* have a NetIO of our own to use for that type.
*/
switch (strm_type) {
case PR_NETIO_STRM_CTRL:
if (ctrl_netio != NULL) {
(void) pr_unregister_netio(strm_type);
}
break;
case PR_NETIO_STRM_DATA:
if (data_netio != NULL) {
(void) pr_unregister_netio(strm_type);
}
break;
default:
break;
}
if (netio != NULL) {
if (pr_register_netio(netio, strm_type) < 0) {
pr_trace_msg(trace_channel, 3,
"error registering previous %s NetIO: %s",
netio_strm_typestr(strm_type), strerror(errno));
}
}
return 0;
}
int proxy_netio_close(pr_netio_stream_t *nstrm) {
int strm_type = -1, res, xerrno;
pr_netio_t *curr_netio = NULL;
if (nstrm != NULL) {
strm_type = nstrm->strm_type;
curr_netio = proxy_netio_unset(strm_type, "netio_close");
}
res = pr_netio_close(nstrm);
xerrno = errno;
if (strm_type != -1) {
proxy_netio_set(strm_type, curr_netio);
}
errno = xerrno;
return res;
}
pr_netio_stream_t *proxy_netio_open(pool *p, int strm_type, int fd, int mode) {
int xerrno;
pr_netio_stream_t *nstrm = NULL;
pr_netio_t *curr_netio;
curr_netio = proxy_netio_unset(strm_type, "netio_open");
nstrm = pr_netio_open(p, strm_type, fd, mode);
xerrno = errno;
proxy_netio_set(strm_type, curr_netio);
errno = xerrno;
return nstrm;
}
int proxy_netio_poll(pr_netio_stream_t *nstrm) {
int res, xerrno;
pr_netio_t *curr_netio;
if (nstrm == NULL) {
errno = EINVAL;
return -1;
}
curr_netio = proxy_netio_unset(nstrm->strm_type, "netio_poll");
res = pr_netio_poll(nstrm);
xerrno = errno;
proxy_netio_set(nstrm->strm_type, curr_netio);
errno = xerrno;
return res;
}
int proxy_netio_postopen(pr_netio_stream_t *nstrm) {
int res = 0, xerrno;
pr_netio_t *curr_netio = NULL;
if (nstrm == NULL) {
errno = EINVAL;
return -1;
}
curr_netio = proxy_netio_unset(nstrm->strm_type, "netio_postpopen");
res = pr_netio_postopen(nstrm);
xerrno = errno;
proxy_netio_set(nstrm->strm_type, curr_netio);
errno = xerrno;
return res;
}
int proxy_netio_printf(pr_netio_stream_t *nstrm, const char *fmt, ...) {
int res, xerrno;
va_list msg;
pr_netio_t *curr_netio = NULL;
if (nstrm == NULL) {
errno = EINVAL;
return -1;
}
curr_netio = proxy_netio_unset(nstrm->strm_type, "netio_printf");
va_start(msg, fmt);
res = pr_netio_vprintf(nstrm, fmt, msg);
xerrno = errno;
va_end(msg);
proxy_netio_set(nstrm->strm_type, curr_netio);
errno = xerrno;
return res;
}
int proxy_netio_read(pr_netio_stream_t *nstrm, char *buf, size_t bufsz,
int bufmin) {
int res, xerrno;
pr_netio_t *curr_netio = NULL;
if (nstrm == NULL) {
errno = EINVAL;
return -1;
}
curr_netio = proxy_netio_unset(nstrm->strm_type, "netio_read");
res = pr_netio_read(nstrm, buf, bufsz, bufmin);
xerrno = errno;
proxy_netio_set(nstrm->strm_type, curr_netio);
errno = xerrno;
return res;
}
void proxy_netio_reset_poll_interval(pr_netio_stream_t *nstrm) {
pr_netio_t *curr_netio = NULL;
if (nstrm == NULL) {
return;
}
curr_netio = proxy_netio_unset(nstrm->strm_type, "netio_reset_poll_interval");
pr_netio_reset_poll_interval(nstrm);
proxy_netio_set(nstrm->strm_type, curr_netio);
}
void proxy_netio_set_poll_interval(pr_netio_stream_t *nstrm,
unsigned int secs) {
pr_netio_t *curr_netio = NULL;
if (nstrm == NULL) {
return;
}
curr_netio = proxy_netio_unset(nstrm->strm_type, "netio_set_poll_interval");
pr_netio_set_poll_interval(nstrm, secs);
proxy_netio_set(nstrm->strm_type, curr_netio);
}
int proxy_netio_shutdown(pr_netio_stream_t *nstrm, int how) {
int res, xerrno;
pr_netio_t *curr_netio = NULL;
if (nstrm == NULL) {
errno = EINVAL;
return -1;
}
curr_netio = proxy_netio_unset(nstrm->strm_type, "netio_shutdown");
res = pr_netio_shutdown(nstrm, how);
xerrno = errno;
proxy_netio_set(nstrm->strm_type, curr_netio);
errno = xerrno;
return res;
}
int proxy_netio_write(pr_netio_stream_t *nstrm, char *buf, size_t bufsz) {
int res, xerrno;
pr_netio_t *curr_netio = NULL;
if (nstrm == NULL) {
errno = EINVAL;
return -1;
}
curr_netio = proxy_netio_unset(nstrm->strm_type, "netio_write");
res = pr_netio_write(nstrm, buf, bufsz);
xerrno = errno;
proxy_netio_set(nstrm->strm_type, curr_netio);
errno = xerrno;
return res;
}
proftpd-mod_proxy-0.9.5/lib/proxy/random.c 0000664 0000000 0000000 00000003634 14757370167 0020623 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy random number implementation
* Copyright (c) 2013-2020 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "proxy/random.h"
static const char *trace_channel = "proxy.random";
/* If random(3) is supported on this platform, seed it. rand(3) is already
* seeded by the core proftpd code.
*/
int proxy_random_init(void) {
#ifdef HAVE_RANDOM
struct timeval tv;
gettimeofday(&tv, NULL);
srandom(getpid() ^ tv.tv_usec);
#endif /* HAVE_RANDOM */
return 0;
}
long proxy_random_next(long min, long max) {
long r, scaled;
#if defined(HAVE_RANDOM)
r = random();
pr_trace_msg(trace_channel, 22, "obtained r = %ld from random(3)", r);
#else
r = (long) rand();
pr_trace_msg(trace_channel, 22, "obtained r = %ld from rand(3)", r);
#endif /* HAVE_RANDOM */
scaled = r % (max - min + 1) + min;
pr_trace_msg(trace_channel, 15,
"yielding scaled r = %ld (r = %ld, max = %ld, min = %ld)", scaled,
r, max, min);
return scaled;
}
proftpd-mod_proxy-0.9.5/lib/proxy/reverse.c 0000664 0000000 0000000 00000150220 14757370167 0021010 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy reverse proxy implementation
* Copyright (c) 2012-2022 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "json.h"
#include "proxy/db.h"
#include "proxy/conn.h"
#include "proxy/netio.h"
#include "proxy/inet.h"
#include "proxy/reverse.h"
#include "proxy/reverse/db.h"
#include "proxy/reverse/redis.h"
#include "proxy/random.h"
#include "proxy/tls.h"
#include "proxy/ftp/ctrl.h"
#include "proxy/ftp/sess.h"
#include
extern xaset_t *server_list;
static array_header *default_backends = NULL, *reverse_backends = NULL;
static int reverse_backend_id = -1;
static int reverse_backend_updated = FALSE;
static int reverse_connect_policy = PROXY_REVERSE_CONNECT_POLICY_ROUND_ROBIN;
static unsigned long reverse_flags = 0UL;
static int reverse_retry_count = PROXY_DEFAULT_RETRY_COUNT;
static struct proxy_reverse_datastore reverse_ds;
/* Flag that indicates that we should select/connect to the backend server
* at session init time, i.e. when proxy auth is not required, and we're using
* a balancing policy.
*/
#define PROXY_REVERSE_FL_CONNECT_AT_SESS_INIT 1
/* Flag that indicates that we should select/connect to the backend server
* at USER time, i.e. when proxy auth is not required, and we're using a
* sticky policy.
*/
#define PROXY_REVERSE_FL_CONNECT_AT_USER 2
/* Flag that indicates that we should select/connect to the backend server
* at PASS time, i.e. when proxy auth IS required (balancing/sticky policy
* doesn't really matter).
*/
#define PROXY_REVERSE_FL_CONNECT_AT_PASS 3
/* JSON handling */
#define PROXY_REVERSE_JSON_MAX_FILE_SIZE (1024 * 1024 * 5)
#define PROXY_REVERSE_JSON_MAX_ITEMS 1000
static const char *trace_channel = "proxy.reverse";
static void clear_user_creds(void) {
register unsigned int i;
if (reverse_backends == NULL ||
reverse_backends->nelts == 0) {
/* Nothing to do. */
return;
}
for (i = 0; i < reverse_backends->nelts; i++) {
struct proxy_conn *pconn;
pconn = ((struct proxy_conn **) reverse_backends->elts)[i];
proxy_conn_clear_username(pconn);
proxy_conn_clear_password(pconn);
}
}
static int check_parent_dir_perms(pool *p, const char *path) {
struct stat st;
int res;
char *dir_path, *ptr = NULL;
ptr = strrchr(path, '/');
if (ptr != path) {
dir_path = pstrndup(p, path, ptr - path);
} else {
dir_path = "/";
}
res = stat(dir_path, &st);
if (res < 0) {
int xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to check ProxyReverseServers %s directory '%s': %s",
path, dir_path, strerror(xerrno));
pr_log_debug(DEBUG0, MOD_PROXY_VERSION
": unable to check ProxyReverseServers %s directory '%s': %s",
path, dir_path, strerror(xerrno));
errno = xerrno;
return -1;
}
if (!(proxy_opts & PROXY_OPT_IGNORE_CONFIG_PERMS) &&
(st.st_mode & S_IWOTH)) {
int xerrno = EPERM;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to use ProxyReverseServers '%s' from world-writable "
"directory '%s' (perms %04o): %s", path, dir_path,
st.st_mode & ~S_IFMT, strerror(xerrno));
pr_log_debug(DEBUG0, MOD_PROXY_VERSION
": unable to use ProxyReverseServers '%s' from world-writable "
"directory '%s' (perms %04o): %s", path, dir_path,
st.st_mode & ~S_IFMT, strerror(xerrno));
errno = xerrno;
return -1;
}
return 0;
}
static int check_file_perms(pool *p, const char *path) {
struct stat st;
int res;
const char *orig_path;
orig_path = path;
res = lstat(path, &st);
if (res < 0) {
int xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to check ProxyReverseServers '%s': %s", path, strerror(xerrno));
pr_log_debug(DEBUG0, MOD_PROXY_VERSION
": unable to check ProxyReverseServers '%s': %s", path, strerror(xerrno));
errno = xerrno;
return -1;
}
if (S_ISLNK(st.st_mode)) {
char buf[PR_TUNABLE_PATH_MAX+1];
/* Check the permissions on the parent directory; if they're world-writable,
* then this symlink can be deleted/pointed somewhere else.
*/
res = check_parent_dir_perms(p, path);
if (res < 0) {
return -1;
}
/* Follow the link to the target path; that path will then have its
* parent directory checked.
*/
memset(buf, '\0', sizeof(buf));
res = readlink(path, buf, sizeof(buf)-1);
if (res > 0) {
path = pstrdup(p, buf);
}
res = stat(orig_path, &st);
if (res < 0) {
int xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to check ProxyReverseServers '%s': %s", orig_path,
strerror(xerrno));
pr_log_debug(DEBUG0, MOD_PROXY_VERSION
": unable to check ProxyReverseServers '%s': %s", orig_path,
strerror(xerrno));
errno = xerrno;
return -1;
}
}
if (S_ISDIR(st.st_mode)) {
int xerrno = EISDIR;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to use ProxyReverseServers '%s': %s", orig_path,
strerror(xerrno));
pr_log_debug(DEBUG0, MOD_PROXY_VERSION
": unable to use ProxyReverseServers '%s': %s", orig_path,
strerror(xerrno));
errno = xerrno;
return -1;
}
/* World-writable files are insecure, and are thus not usable/trusted. */
if (st.st_mode & S_IWOTH) {
int xerrno = EPERM;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to use world-writable ProxyReverseServers '%s' "
"(perms %04o): %s", orig_path, st.st_mode & ~S_IFMT, strerror(xerrno));
pr_log_debug(DEBUG0, MOD_PROXY_VERSION
": unable to use world-writable ProxyReverseServers '%s' "
"(perms %04o): %s", orig_path, st.st_mode & ~S_IFMT, strerror(xerrno));
errno = xerrno;
return -1;
}
/* TODO: This will warn about files such as FIFOs, BUT will leave them
* usable. Good idea, or bad idea?
*/
if (!S_ISREG(st.st_mode)) {
pr_log_pri(PR_LOG_WARNING, MOD_PROXY_VERSION
": ProxyReverseServers '%s' is not a regular file", orig_path);
}
/* Check the parent directory of this file. If the parent directory
* is world-writable, that too is insecure.
*/
res = check_parent_dir_perms(p, path);
if (res < 0) {
return -1;
}
return 0;
}
/* Shared/common routines for PerUser and PerGroup. */
static array_header *reverse_db_parse_uris(pool *p, array_header *uris) {
array_header *pconns = NULL;
register unsigned int i;
pconns = make_array(p, 0, sizeof(struct proxy_conn *));
for (i = 0; i < uris->nelts; i++) {
char *uri;
const struct proxy_conn *pconn;
pr_signals_handle();
uri = ((char **) uris->elts)[i];
/* Skip blank/empty URIs. */
if (*uri == '\0') {
continue;
}
pconn = proxy_conn_create(p, uri, 0);
if (pconn == NULL) {
pr_trace_msg(trace_channel, 9, "skipping malformed URL '%s'", uri);
continue;
}
*((const struct proxy_conn **) push_array(pconns)) = pconn;
}
return pconns;
}
/* SQL support routines. */
static cmd_rec *reverse_db_sql_cmd_create(pool *parent_pool,
unsigned int argc, ...) {
pool *cmd_pool = NULL;
cmd_rec *cmd = NULL;
register unsigned int i = 0;
va_list argp;
cmd_pool = make_sub_pool(parent_pool);
cmd = (cmd_rec *) pcalloc(cmd_pool, sizeof(cmd_rec));
cmd->pool = cmd_pool;
cmd->argc = argc;
cmd->argv = pcalloc(cmd->pool, argc * sizeof(void *));
/* Hmmm... */
cmd->tmp_pool = cmd->pool;
va_start(argp, argc);
for (i = 0; i < argc; i++) {
cmd->argv[i] = va_arg(argp, char *);
}
va_end(argp);
return cmd;
}
static const char *reverse_db_sql_quote_str(pool *p, char *str) {
size_t len;
cmdtable *cmdtab;
cmd_rec *cmd;
modret_t *res;
len = strlen(str);
if (len == 0) {
return str;
}
cmdtab = pr_stash_get_symbol2(PR_SYM_HOOK, "sql_escapestr", NULL, NULL, NULL);
if (cmdtab == NULL) {
return str;
}
cmd = reverse_db_sql_cmd_create(p, 1, pr_str_strip(p, str));
res = pr_module_call(cmdtab->m, cmdtab->handler, cmd);
if (MODRET_ISDECLINED(res) ||
MODRET_ISERROR(res)) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing 'sql_escapestr'");
return str;
}
return res->data;
}
/* Look for any user/group-specific ProxyReverseServers and load them, either
* from file or SQL or whatever. Randomly choose from one of those
* backends. If no user/group-specific backends are found, use the existing
* "global" list.
*/
static array_header *reverse_db_pername_sql_parse_uris(pool *p,
cmdtable *sql_cmdtab, const char *name, int per_user,
const char *named_query) {
array_header *backends, *results;
pool *tmp_pool;
cmd_rec *cmd;
modret_t *res;
tmp_pool = make_sub_pool(p);
cmd = reverse_db_sql_cmd_create(tmp_pool, 3, "sql_lookup", named_query, name);
res = pr_module_call(sql_cmdtab->m, sql_cmdtab->handler, cmd);
if (res == NULL ||
MODRET_ISERROR(res)) {
destroy_pool(tmp_pool);
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error processing SQLNamedQuery '%s'", named_query);
errno = EPERM;
return NULL;
}
results = res->data;
if (results->nelts == 0) {
destroy_pool(tmp_pool);
pr_trace_msg(trace_channel, 10,
"SQLNamedQuery '%s' returned zero rows for %s '%s'", named_query,
per_user ? "user" : "group", name);
errno = ENOENT;
return NULL;
}
backends = reverse_db_parse_uris(p, results);
destroy_pool(tmp_pool);
if (backends != NULL) {
if (backends->nelts == 0) {
errno = ENOENT;
return NULL;
}
pr_trace_msg(trace_channel, 10,
"SQLNamedQuery '%s' returned %d %s for %s '%s'", named_query,
backends->nelts, backends->nelts != 1 ? "URLs" : "URL",
per_user ? "user" : "group", name);
}
return backends;
}
static array_header *reverse_db_pername_backends_by_sql(pool *p,
const char *name, int per_user) {
config_rec *c;
array_header *sql_backends = NULL;
const char *quoted_name = NULL;
cmdtable *sql_cmdtab;
sql_cmdtab = pr_stash_get_symbol2(PR_SYM_HOOK, "sql_lookup", NULL, NULL,
NULL);
if (sql_cmdtab == NULL) {
/* No mod_sql backend loaded; no lookups to do. */
pr_trace_msg(trace_channel, 18,
"no 'sql_lookup' symbol found (mod_sql not loaded?), skipping "
"%s SQL lookups", per_user ? "per-user" : "per-group");
errno = EPERM;
return NULL;
}
c = find_config(main_server->conf, CONF_PARAM, "ProxyReverseServers", FALSE);
while (c != NULL) {
const char *named_query, *uri;
array_header *backends = NULL;
pr_signals_handle();
uri = c->argv[1];
if (uri == NULL) {
c = find_config_next(c, c->next, CONF_PARAM, "ProxyReverseServers",
FALSE);
continue;
}
if (strncmp(uri, "sql:/", 5) != 0) {
c = find_config_next(c, c->next, CONF_PARAM, "ProxyReverseServers",
FALSE);
continue;
}
named_query = uri + 5;
if (quoted_name == NULL) {
quoted_name = reverse_db_sql_quote_str(p, (char *) name);
}
pr_trace_msg(trace_channel, 17,
"loading %s-specific ProxyReverseServers SQLNamedQuery '%s'",
per_user ? "user" : "group", named_query);
backends = reverse_db_pername_sql_parse_uris(p, sql_cmdtab, quoted_name,
per_user, named_query);
if (backends == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error reading ProxyReverseServers SQLNamedQuery '%s': %s", named_query,
strerror(errno));
c = find_config_next(c, c->next, CONF_PARAM, "ProxyReverseServers",
FALSE);
continue;
}
if (backends->nelts == 0) {
pr_trace_msg(trace_channel, 3,
"no usable URLs found by ProxyReverseServers SQLNamedQuery '%s', "
"ignoring", named_query);
} else {
if (sql_backends == NULL) {
sql_backends = backends;
} else {
array_cat(sql_backends, backends);
}
}
c = find_config_next(c, c->next, CONF_PARAM, "ProxyReverseServers", FALSE);
}
return sql_backends;
}
static array_header *reverse_db_pername_backends_by_json(pool *p,
const char *name, int per_user) {
config_rec *c;
array_header *file_backends = NULL;
c = find_config(main_server->conf, CONF_PARAM, "ProxyReverseServers", FALSE);
while (c != NULL) {
const char *path, *uri;
int xerrno;
array_header *backends = NULL;
pr_signals_handle();
uri = c->argv[1];
if (uri == NULL) {
c = find_config_next(c, c->next, CONF_PARAM, "ProxyReverseServers",
FALSE);
continue;
}
if (per_user) {
if (strstr(uri, "%U") == NULL) {
c = find_config_next(c, c->next, CONF_PARAM, "ProxyReverseServers",
FALSE);
continue;
}
} else {
if (strstr(uri, "%g") == NULL) {
c = find_config_next(c, c->next, CONF_PARAM, "ProxyReverseServers",
FALSE);
continue;
}
}
if (strncmp(uri, "file:", 5) != 0) {
c = find_config_next(c, c->next, CONF_PARAM, "ProxyReverseServers",
FALSE);
continue;
}
if (per_user) {
path = sreplace(p, (char *) (uri + 5), "%U", name, NULL);
} else {
path = sreplace(p, (char *) (uri + 5), "%g", name, NULL);
}
pr_trace_msg(trace_channel, 17,
"loading %s-specific ProxyReverseServers file '%s'",
per_user ? "user" : "group", path);
PRIVS_ROOT
backends = proxy_reverse_json_parse_uris(p, path, 0);
xerrno = errno;
PRIVS_RELINQUISH
if (backends == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error reading ProxyReverseServers file '%s': %s", path,
strerror(xerrno));
if (xerrno == ENOENT) {
/* No file for this user? We're done looking, then. */
break;
}
c = find_config_next(c, c->next, CONF_PARAM, "ProxyReverseServers",
FALSE);
continue;
}
if (backends->nelts == 0) {
pr_trace_msg(trace_channel, 3,
"no usable URLs found in ProxyReverseServers file '%s', ignoring",
path);
} else {
if (file_backends == NULL) {
file_backends = backends;
} else {
array_cat(file_backends, backends);
}
}
c = find_config_next(c, c->next, CONF_PARAM, "ProxyReverseServers", FALSE);
}
return file_backends;
}
array_header *proxy_reverse_pername_backends(pool *p, const char *name,
int per_user) {
array_header *file_backends, *sql_backends, *backends = NULL;
file_backends = reverse_db_pername_backends_by_json(p, name, per_user);
if (file_backends != NULL) {
backends = file_backends;
}
sql_backends = reverse_db_pername_backends_by_sql(p, name, per_user);
if (sql_backends != NULL) {
if (backends != NULL) {
array_cat(backends, sql_backends);
} else {
backends = sql_backends;
}
}
if (backends == NULL) {
if (default_backends == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"no %s servers found for %s '%s', and no global "
"ProxyReverseServers configured", per_user ? "PerUser" : "PerGroup",
per_user ? "user" : "group", name);
errno = ENOENT;
return NULL;
}
pr_trace_msg(trace_channel, 11,
"using global ProxyReverseServers list for %s '%s'",
per_user ? "user" : "group", name);
backends = default_backends;
}
return backends;
}
int proxy_reverse_policy_is_sticky(int policy_id) {
int sticky = FALSE;
switch (policy_id) {
case PROXY_REVERSE_CONNECT_POLICY_PER_USER:
case PROXY_REVERSE_CONNECT_POLICY_PER_GROUP:
case PROXY_REVERSE_CONNECT_POLICY_PER_HOST:
sticky = TRUE;
break;
default:
break;
}
return sticky;
}
const char *proxy_reverse_policy_name(int policy_id) {
const char *name;
switch (policy_id) {
case PROXY_REVERSE_CONNECT_POLICY_RANDOM:
name = "Random";
break;
case PROXY_REVERSE_CONNECT_POLICY_ROUND_ROBIN:
name = "RoundRobin";
break;
case PROXY_REVERSE_CONNECT_POLICY_SHUFFLE:
name = "Shuffle";
break;
case PROXY_REVERSE_CONNECT_POLICY_PER_USER:
name = "PerUser";
break;
case PROXY_REVERSE_CONNECT_POLICY_PER_GROUP:
name = "PerGroup";
break;
case PROXY_REVERSE_CONNECT_POLICY_LEAST_CONNS:
name = "LeastConns";
break;
case PROXY_REVERSE_CONNECT_POLICY_LEAST_RESPONSE_TIME:
name = "LeastResponseTime";
break;
case PROXY_REVERSE_CONNECT_POLICY_PER_HOST:
name = "PerHost";
break;
default:
name = "unknown/unsupported";
break;
}
return name;
}
static int reverse_connect_index_used(pool *p, unsigned int vhost_id,
int idx, long connect_ms) {
int res;
if (reverse_backends != NULL &&
reverse_backends->nelts == 1) {
return 0;
}
res = (reverse_ds.policy_update_backend)(p, reverse_ds.dsh,
reverse_connect_policy, vhost_id, idx, 1, connect_ms);
if (res < 0) {
int xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error updating database entry for backend ID %d: %s", idx,
strerror(xerrno));
errno = xerrno;
return -1;
}
reverse_backend_updated = TRUE;
res = (reverse_ds.policy_used_backend)(p, reverse_ds.dsh,
reverse_connect_policy, vhost_id, idx);
if (res < 0) {
int xerrno = errno;
errno = xerrno;
return -1;
}
return 0;
}
static const struct proxy_conn *get_reverse_server_conn(pool *p,
struct proxy_session *proxy_sess, int *backend_id,
const void *policy_data) {
const struct proxy_conn *pconn;
pconn = (reverse_ds.policy_next_backend)(p, reverse_ds.dsh,
reverse_connect_policy, main_server->sid, default_backends, policy_data,
backend_id);
if (pconn == NULL) {
int xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error selecting backend server: %s", strerror(xerrno));
errno = xerrno;
return NULL;
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"selected backend server '%s'", proxy_conn_get_uri(pconn));
reverse_backend_id = *backend_id;
return pconn;
}
static int reverse_tls_postopen(pool *p, struct proxy_session *proxy_sess,
conn_t *server_conn) {
int xerrno;
if (proxy_netio_postopen(server_conn->instrm) < 0) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"postopen error for backend control connection input stream: %s",
strerror(xerrno));
proxy_inet_close(session.pool, server_conn);
proxy_sess->backend_ctrl_conn = NULL;
pr_response_block(FALSE);
/* Note that we explicitly return EINVAL here, to indicate to the calling
* code in mod_proxy that it should return e.g. "Login incorrect."
*/
errno = EINVAL;
return -1;
}
if (proxy_netio_postopen(server_conn->outstrm) < 0) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"postopen error for backend control connection output stream: %s",
strerror(xerrno));
proxy_inet_close(session.pool, server_conn);
proxy_sess->backend_ctrl_conn = NULL;
pr_response_block(FALSE);
/* Note that we explicitly return EINVAL here, to indicate to the calling
* code in mod_proxy that it should return e.g. "Login incorrect."
*/
errno = EINVAL;
return -1;
}
return 0;
}
static int reverse_try_connect(pool *p, struct proxy_session *proxy_sess,
const void *connect_data) {
int backend_id = -1, uri_tls, use_tls, xerrno = 0;
conn_t *server_conn = NULL;
pr_response_t *resp = NULL;
unsigned int resp_nlines = 0;
const struct proxy_conn *pconn;
const pr_netaddr_t *dst_addr;
array_header *other_addrs = NULL;
uint64_t connecting_ms, connected_ms;
char port_text[32];
pconn = get_reverse_server_conn(p, proxy_sess, &backend_id, connect_data);
if (pconn == NULL) {
return -1;
}
dst_addr = proxy_conn_get_addr(pconn, &other_addrs);
proxy_sess->dst_addr = dst_addr;
proxy_sess->dst_pconn = pconn;
proxy_sess->other_addrs = other_addrs;
/* Carefully handle FTP-isms here; we may be proxying SSH instead.
*
* NOTE: All of these FTP-specific cases should be refactored into the
* the FTP client API, e.g. proxy_ftp_ctrl_on_connect().
*
* NOTE: Should we consider using the SSH client API to do the version
* exchange there?
*/
if (proxy_sess->use_ftp == TRUE) {
if (proxy_tls_using_tls() == PROXY_TLS_ENGINE_MATCH_CLIENT) {
proxy_tls_match_client_tls();
}
uri_tls = proxy_conn_get_tls(pconn);
if (uri_tls == PROXY_TLS_ENGINE_IMPLICIT) {
pr_trace_msg(trace_channel, 9, "%s#%u requesting, using implicit FTPS",
pr_netaddr_get_ipstr(dst_addr),
(unsigned int) ntohs(pr_netaddr_get_port(dst_addr)));
proxy_tls_set_tls(uri_tls);
}
}
pr_gettimeofday_millis(&connecting_ms);
server_conn = proxy_conn_get_server_conn(p, proxy_sess, dst_addr);
if (server_conn == NULL) {
xerrno = errno;
if (other_addrs != NULL) {
register unsigned int i;
/* Try the other IP addresses for the configured name (if any) as well. */
for (i = 0; i < other_addrs->nelts; i++) {
dst_addr = ((pr_netaddr_t **) other_addrs->elts)[i];
pr_gettimeofday_millis(&connecting_ms);
pr_trace_msg(trace_channel, 8,
"attempting to connect to other address #%u (%s) for requested "
"URI '%.100s'", i+1, pr_netaddr_get_ipstr(dst_addr),
proxy_conn_get_uri(proxy_sess->dst_pconn));
server_conn = proxy_conn_get_server_conn(p, proxy_sess, dst_addr);
if (server_conn != NULL) {
proxy_sess->dst_addr = dst_addr;
break;
}
}
}
if (server_conn == NULL) {
xerrno = errno;
/* TODO: Under what errno values will we mark this backend/idx as
* "unhealthy"? When we do, how will that unhealthy flag be taken into
* account with the existing queries? JOIN the index on the backend table
* to get that unhealthy flag?
*/
if (reverse_connect_index_used(p, main_server->sid, backend_id, -1) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error updating database for backend server index %d: %s", backend_id,
strerror(errno));
}
}
errno = xerrno;
return -1;
}
if (proxy_opts & PROXY_OPT_USE_PROXY_PROTOCOL_V1) {
pr_trace_msg(trace_channel, 17,
"sending PROXY V1 protocol message to %s#%u",
pr_netaddr_get_ipstr(server_conn->remote_addr),
ntohs(pr_netaddr_get_port(server_conn->remote_addr)));
if (proxy_conn_send_proxy_v1(p, server_conn) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error sending PROXY V1 message to %s#%u: %s",
pr_netaddr_get_ipstr(server_conn->remote_addr),
ntohs(pr_netaddr_get_port(server_conn->remote_addr)),
strerror(errno));
}
} else if (proxy_opts & PROXY_OPT_USE_PROXY_PROTOCOL_V2) {
pr_trace_msg(trace_channel, 17,
"sending PROXY V2 protocol message to %s#%u",
pr_netaddr_get_ipstr(server_conn->remote_addr),
ntohs(pr_netaddr_get_port(server_conn->remote_addr)));
if (proxy_conn_send_proxy_v2(p, server_conn) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error sending PROXY V2 message to %s#%u: %s",
pr_netaddr_get_ipstr(server_conn->remote_addr),
ntohs(pr_netaddr_get_port(server_conn->remote_addr)),
strerror(errno));
}
}
proxy_sess->frontend_ctrl_conn = session.c;
proxy_sess->backend_ctrl_conn = server_conn;
if (proxy_sess->use_ftp == TRUE) {
use_tls = proxy_tls_using_tls();
/* Handle implicit FTPS connects. */
if (use_tls == PROXY_TLS_ENGINE_IMPLICIT) {
if (reverse_tls_postopen(p, proxy_sess, server_conn) < 0) {
return -1;
}
}
/* XXX Support/send a CLNT command of our own? Configurable via e.g.
* "UserAgent" string?
*/
resp = proxy_ftp_ctrl_recv_resp(p, server_conn, &resp_nlines, 0);
if (resp == NULL) {
xerrno = errno;
pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to read banner from server %s:%u: %s",
pr_netaddr_get_ipstr(server_conn->remote_addr),
ntohs(pr_netaddr_get_port(server_conn->remote_addr)), strerror(xerrno));
errno = xerrno;
return -1;
} else {
int banner_ok = TRUE;
pr_gettimeofday_millis(&connected_ms);
if (resp->num[0] != '2') {
banner_ok = FALSE;
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"received banner from backend %s:%u%s: %s %s",
pr_netaddr_get_ipstr(server_conn->remote_addr),
ntohs(pr_netaddr_get_port(server_conn->remote_addr)),
banner_ok ? "" : ", DISCONNECTING", resp->num, resp->msg);
if (banner_ok == FALSE) {
pr_inet_close(p, server_conn);
proxy_sess->backend_ctrl_conn = NULL;
errno = EPERM;
return -1;
}
}
} else {
pr_gettimeofday_millis(&connected_ms);
use_tls = PROXY_TLS_ENGINE_OFF;
}
pr_trace_msg(trace_channel, 8,
"connected to backend '%.100s' in %ld ms",
proxy_conn_get_uri(proxy_sess->dst_pconn),
(long) (connected_ms - connecting_ms));
if (reverse_connect_index_used(p, main_server->sid, backend_id,
(long) (connected_ms - connecting_ms)) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error updating database for backend server index %d: %s", backend_id,
strerror(errno));
}
if (proxy_sess->use_ftp == TRUE) {
/* Get the features supported by the backend server. */
if (proxy_ftp_sess_get_feat(p, proxy_sess) < 0) {
if (errno != EPERM) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to determine features of backend server: %s",
strerror(errno));
}
}
pr_response_block(TRUE);
if (use_tls != PROXY_TLS_ENGINE_OFF &&
use_tls != PROXY_TLS_ENGINE_IMPLICIT) {
if (proxy_ftp_sess_send_auth_tls(p, proxy_sess) < 0 &&
errno != ENOSYS) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error enabling TLS on control connection to backend server: %s",
strerror(xerrno));
pr_inet_close(p, server_conn);
proxy_sess->backend_ctrl_conn = NULL;
pr_response_block(FALSE);
errno = xerrno;
return -1;
}
use_tls = proxy_tls_using_tls();
}
if (use_tls != PROXY_TLS_ENGINE_OFF &&
use_tls != PROXY_TLS_ENGINE_IMPLICIT) {
if (reverse_tls_postopen(p, proxy_sess, server_conn) < 0) {
return -1;
}
}
if (use_tls != PROXY_TLS_ENGINE_OFF) {
if (proxy_sess_state & PROXY_SESS_STATE_BACKEND_HAS_CTRL_TLS) {
/* NOTE: should this be a fatal error? */
(void) proxy_ftp_sess_send_pbsz_prot(p, proxy_sess);
}
}
if (reverse_flags == PROXY_REVERSE_FL_CONNECT_AT_SESS_INIT) {
pr_response_block(FALSE);
}
/* Only send the banner if we haven't done so already; the banner
* might already have been sent due to e.g. TLS SNI.
*/
if (pr_table_get(session.notes, "mod_tls.sni", NULL) == NULL) {
if (proxy_ftp_ctrl_send_resp(p, session.c, resp, resp_nlines) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to send banner to client: %s", strerror(errno));
}
}
if (reverse_flags == PROXY_REVERSE_FL_CONNECT_AT_SESS_INIT) {
pr_response_block(TRUE);
}
(void) proxy_ftp_sess_send_host(p, proxy_sess);
}
/* Populate the session notes about this connection. */
memset(port_text, '\0', sizeof(port_text));
pr_snprintf(port_text, sizeof(port_text)-1, "%d",
proxy_conn_get_port(proxy_sess->dst_pconn));
(void) pr_table_add_dup(session.notes, "mod_proxy.backend-ip",
pr_netaddr_get_ipstr(dst_addr), 0);
(void) pr_table_remove(session.notes, "mod_proxy.backend-port", NULL);
(void) pr_table_add_dup(session.notes, "mod_proxy.backend-port",
port_text, 0);
(void) pr_table_add_dup(session.notes, "mod_proxy.backend-url",
proxy_conn_get_uri(proxy_sess->dst_pconn), 0);
proxy_sess_state |= PROXY_SESS_STATE_CONNECTED;
return 0;
}
int proxy_reverse_connect(pool *p, struct proxy_session *proxy_sess,
const void *connect_data) {
register int i;
int res;
if (p == NULL ||
proxy_sess == NULL) {
errno = EINVAL;
return -1;
}
for (i = 0; i < reverse_retry_count; i++) {
pr_signals_handle();
res = reverse_try_connect(p, proxy_sess, connect_data);
if (res == 0) {
return 0;
}
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"ProxyRetryCount %d reached with no successful connection, failing",
reverse_retry_count);
errno = EPERM;
return -1;
}
int proxy_reverse_use_proxy_auth(void) {
if (proxy_opts & PROXY_OPT_USE_REVERSE_PROXY_AUTH) {
return TRUE;
}
return FALSE;
}
int proxy_reverse_init(pool *p, const char *tables_dir, int flags) {
const char *ds_name = "(unknown/unsupported)";
int res, xerrno;
void *dsh = NULL;
server_rec *s = NULL;
if (p == NULL) {
errno = EINVAL;
return -1;
}
memset(&reverse_ds, 0, sizeof(reverse_ds));
reverse_ds.backend_id = -1;
switch (proxy_datastore) {
case PROXY_DATASTORE_SQLITE:
ds_name = "SQLite";
res = proxy_reverse_db_as_datastore(&reverse_ds, proxy_datastore_data,
proxy_datastore_datasz);
xerrno = errno;
break;
case PROXY_DATASTORE_REDIS:
ds_name = "Redis";
res = proxy_reverse_redis_as_datastore(&reverse_ds, proxy_datastore_data,
proxy_datastore_datasz);
xerrno = errno;
break;
default:
res = -1;
xerrno = errno = EINVAL;
break;
}
if (res < 0) {
return -1;
}
dsh = (reverse_ds.init)(p, tables_dir, flags);
if (dsh == NULL) {
pr_log_pri(PR_LOG_NOTICE, MOD_PROXY_VERSION
": failed to initialize %s datastore: %s", ds_name, strerror(xerrno));
errno = xerrno;
return -1;
}
for (s = (server_rec *) server_list->xas_list; s; s = s->next) {
config_rec *c;
array_header *backends = NULL;
int connect_policy = reverse_connect_policy;
unsigned long opts = 0UL;
c = find_config(s->conf, CONF_PARAM, "ProxyReverseServers", FALSE);
while (c != NULL) {
const char *uri;
pr_signals_handle();
uri = c->argv[1];
if (uri != NULL) {
int defer = FALSE;
/* Handling of sql:// URIs is done later, in the session init
* call, assuming we've connected to a SQL server.
*/
if (strncmp(uri, "sql:/", 5) == 0) {
defer = TRUE;
}
/* Skip any %U- or %g-bearing URIs. */
if (defer == FALSE &&
(strstr(uri, "%U") != NULL ||
strstr(uri, "%g") != NULL)) {
defer = TRUE;
}
if (defer) {
c = find_config_next(c, c->next, CONF_PARAM, "ProxyReverseServers",
FALSE);
continue;
}
}
if (backends == NULL) {
backends = c->argv[0];
} else {
array_cat(backends, c->argv[0]);
}
c = find_config_next(c, c->next, CONF_PARAM, "ProxyReverseServers",
FALSE);
}
c = find_config(s->conf, CONF_PARAM, "ProxyReverseConnectPolicy", FALSE);
if (c != NULL) {
connect_policy = *((int *) c->argv[0]);
}
c = find_config(s->conf, CONF_PARAM, "ProxyOptions", FALSE);
while (c != NULL) {
unsigned long o;
pr_signals_handle();
o = *((unsigned long *) c->argv[0]);
opts |= o;
c = find_config_next(c, c->next, CONF_PARAM, "ProxyOptions", FALSE);
}
res = (reverse_ds.policy_init)(p, dsh, connect_policy, s->sid,
backends, opts);
if (res < 0) {
xerrno = errno;
break;
}
}
(void) (reverse_ds.close)(p, dsh);
if (res < 0) {
errno = xerrno;
return -1;
}
return 0;
}
int proxy_reverse_free(pool *p) {
if (p == NULL) {
errno = EINVAL;
return -1;
}
/* TODO: Implement any necessary cleanup */
if (reverse_ds.dsh != NULL) {
(void) (reverse_ds.close)(p, reverse_ds.dsh);
reverse_ds.dsh = NULL;
}
return 0;
}
int proxy_reverse_sess_exit(pool *p) {
if (reverse_backends != NULL &&
reverse_backend_id >= 0) {
if (reverse_backend_updated == TRUE) {
int res;
res = (reverse_ds.policy_update_backend)(p, reverse_ds.dsh,
reverse_connect_policy, main_server->sid, reverse_ds.backend_id,
-1, -1);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error updating backend ID %d: %s", reverse_ds.backend_id,
strerror(errno));
}
}
}
return 0;
}
static int set_reverse_flags(void) {
if (proxy_opts & PROXY_OPT_USE_REVERSE_PROXY_AUTH) {
reverse_flags = PROXY_REVERSE_FL_CONNECT_AT_PASS;
} else {
if (reverse_connect_policy == PROXY_REVERSE_CONNECT_POLICY_PER_USER) {
reverse_flags = PROXY_REVERSE_FL_CONNECT_AT_USER;
} else if (reverse_connect_policy == PROXY_REVERSE_CONNECT_POLICY_PER_GROUP) {
/* Incompatible configuration: PerGroup balancing requires that the USER
* name be authenticated, in order to discovery the primary group name.
*/
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"ReverseProxyConnectPolicy PerGroup requires the UseReverseProxyAuth ProxyOption, rejecting connection due to incompatible configuration");
pr_log_pri(PR_LOG_NOTICE, MOD_PROXY_VERSION
": ReverseProxyConnectPolicy PerGroup requires the UseReverseProxyAuth ProxyOption, rejecting connection due to incompatible configuration");
errno = EINVAL;
return -1;
} else {
reverse_flags = PROXY_REVERSE_FL_CONNECT_AT_SESS_INIT;
}
}
return 0;
}
int proxy_reverse_sess_free(pool *p, struct proxy_session *proxy_sess) {
/* Reset any state. */
reverse_backends = NULL;
reverse_backend_id = -1;
reverse_connect_policy = PROXY_REVERSE_CONNECT_POLICY_ROUND_ROBIN;
reverse_flags = 0UL;
reverse_retry_count = PROXY_DEFAULT_RETRY_COUNT;
if (reverse_ds.dsh != NULL) {
(void) (reverse_ds.close)(p, reverse_ds.dsh);
reverse_ds.dsh = NULL;
}
return 0;
}
int proxy_reverse_sess_init(pool *p, const char *tables_dir,
struct proxy_session *proxy_sess, int flags) {
int res;
config_rec *c;
void *dsh;
if (p == NULL) {
errno = EINVAL;
return - 1;
}
c = find_config(main_server->conf, CONF_PARAM, "ProxyRetryCount", FALSE);
if (c != NULL) {
reverse_retry_count = *((int *) c->argv[0]);
}
c = find_config(main_server->conf, CONF_PARAM, "ProxyReverseServers",
FALSE);
if (c == NULL) {
pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"'ProxyRole reverse' in effect, but no ProxyReverseServers configured");
pr_log_pri(PR_LOG_NOTICE, MOD_PROXY_VERSION
": 'ProxyRole reverse' in effect, but no ProxyReverseServers configured");
errno = EPERM;
return -1;
}
/* We need to find the ProxyReverseServers that are NOT user/group-specific.
*/
while (c != NULL) {
const char *uri;
pr_signals_handle();
uri = c->argv[1];
if (uri == NULL) {
if (default_backends == NULL) {
default_backends = c->argv[0];
} else {
array_cat(default_backends, c->argv[0]);
}
break;
}
c = find_config_next(c, c->next, CONF_PARAM, "ProxyReverseServers", FALSE);
}
c = find_config(main_server->conf, CONF_PARAM, "ProxyReverseConnectPolicy",
FALSE);
if (c != NULL) {
reverse_connect_policy = *((int *) c->argv[0]);
}
dsh = (reverse_ds.open)(p, tables_dir, default_backends);
if (dsh == NULL) {
return -1;
}
reverse_ds.dsh = dsh;
if (set_reverse_flags() < 0) {
return -1;
}
if (reverse_flags == PROXY_REVERSE_FL_CONNECT_AT_SESS_INIT) {
res = proxy_reverse_connect(p, proxy_sess, NULL);
if (res < 0) {
return -1;
}
}
return 0;
}
int proxy_reverse_have_authenticated(cmd_rec *cmd) {
int authd = FALSE;
/* Authenticated here means authenticated *to the proxy*, i.e. should we
* allow more commands, or reject them because the client hasn't authenticated
* yet.
*/
if (proxy_sess_state & PROXY_SESS_STATE_BACKEND_AUTHENTICATED) {
authd = TRUE;
}
if (authd == FALSE) {
pr_response_send(R_530, _("Please login with USER and PASS"));
}
return authd;
}
static pr_json_array_t *read_json_array(pool *p, pr_fh_t *fh, off_t filesz) {
pr_json_array_t *json = NULL;
char *buf, *ptr;
int res;
off_t len;
len = filesz;
buf = ptr = palloc(p, len+1);
buf[len] = '\0';
res = pr_fsio_read(fh, buf, len);
while (res != len) {
if (res < 0) {
if (errno == EINTR) {
pr_signals_handle();
res = pr_fsio_read(fh, buf, len);
continue;
}
return NULL;
}
if (res == 0) {
/* EOF, but we shouldn't reach this. */
pr_trace_msg(trace_channel, 14,
"unexpectedly reached EOF when reading '%s'", fh->fh_path);
errno = EOF;
return NULL;
}
/* Paranoia, paranoia...*/
if (len > res) {
errno = EIO;
return NULL;
}
/* Short read: advance the buffer, decrement the length, and read more. */
buf += res;
len -= res;
pr_signals_handle();
res = pr_fsio_read(fh, buf, len);
}
json = pr_json_array_from_text(p, ptr);
if (json == NULL) {
pr_trace_msg(trace_channel, 3,
"invalid JSON format found in '%s'", fh->fh_path);
errno = EINVAL;
return NULL;
}
return json;
}
array_header *proxy_reverse_json_parse_uris(pool *p, const char *path,
unsigned int flags) {
register unsigned int i, nelts;
int count = 0, reached_eol = TRUE, res, xerrno = 0;
pr_fh_t *fh;
array_header *uris = NULL;
struct stat st;
pool *tmp_pool;
pr_json_array_t *json = NULL;
if (p == NULL ||
path == NULL) {
errno = EINVAL;
return NULL;
}
if (*path != '/') {
/* A relative path? Unacceptable. */
errno = EINVAL;
return NULL;
}
res = check_file_perms(p, path);
if (res < 0) {
return NULL;
}
/* Use a nonblocking open() for the path; it could be a FIFO, and we don't
* want to block forever if the other end of the FIFO is not running.
*/
fh = pr_fsio_open(path, O_RDONLY|O_NONBLOCK);
if (fh == NULL) {
xerrno = errno;
pr_trace_msg(trace_channel, 7,
"error opening ProxyReverseServers file '%s': %s", path,
strerror(xerrno));
errno = xerrno;
return NULL;
}
pr_fsio_set_block(fh);
/* Stat the file to find the optimal buffer size for reading. */
res = pr_fsio_fstat(fh, &st);
if (res < 0) {
xerrno = errno;
pr_trace_msg(trace_channel, 3,
"unable to fstat '%s': %s", path, strerror(xerrno));
(void) pr_fsio_close(fh);
errno = xerrno;
return NULL;
}
if (st.st_size == 0) {
/* Return an empty array for this empty file. */
pr_trace_msg(trace_channel, 15,
"found no items in empty file '%s'", fh->fh_path);
(void) pr_fsio_close(fh);
uris = make_array(p, 1, sizeof(struct proxy_conn *));
return uris;
}
if (st.st_size > PROXY_REVERSE_JSON_MAX_FILE_SIZE) {
pr_trace_msg(trace_channel, 1,
"'%s' file size (%lu bytes) exceeds max JSON file size (%lu bytes)",
path, (unsigned long) st.st_size,
(unsigned long) PROXY_REVERSE_JSON_MAX_FILE_SIZE);
(void) pr_fsio_close(fh);
errno = EPERM;
return NULL;
}
fh->fh_iosz = st.st_blksize;
tmp_pool = make_sub_pool(p);
json = read_json_array(tmp_pool, fh, st.st_size);
xerrno = errno;
(void) pr_fsio_close(fh);
if (json == NULL) {
pr_trace_msg(trace_channel, 1,
"unable to read JSON array from '%s': %s", path, strerror(xerrno));
destroy_pool(tmp_pool);
errno = xerrno;
return NULL;
}
count = pr_json_array_count(json);
if (count >= 0) {
pr_trace_msg(trace_channel, 12,
"found items (count %d) in JSON file '%s'", count, path);
}
uris = make_array(p, 1, sizeof(struct proxy_conn *));
nelts = count;
if (nelts > PROXY_REVERSE_JSON_MAX_ITEMS) {
nelts = PROXY_REVERSE_JSON_MAX_ITEMS;
reached_eol = FALSE;
}
for (i = 0; i < nelts; i++) {
char *uri = NULL;
const struct proxy_conn *pconn;
pr_signals_handle();
if (pr_json_array_get_string(p, json, i, &uri) == 0) {
pconn = proxy_conn_create(p, uri, flags);
if (pconn == NULL) {
pr_trace_msg(trace_channel, 9,
"skipping malformed URL '%s' found in file '%s'", uri, path);
continue;
}
*((const struct proxy_conn **) push_array(uris)) = pconn;
} else {
pr_trace_msg(trace_channel, 2,
"error getting string from JSON array at index %u: %s", i,
strerror(errno));
}
}
(void) pr_json_array_free(json);
destroy_pool(tmp_pool);
if (reached_eol == FALSE) {
pr_trace_msg(trace_channel, 3,
"warning: skipped ProxyReverseServers '%s' data (only used "
"first %u items)", path, i);
}
pr_trace_msg(trace_channel, 12,
"created URIs (count %u) from JSON file '%s'", uris->nelts, path);
return uris;
}
int proxy_reverse_get_connect_policy(void) {
return reverse_connect_policy;
}
int proxy_reverse_connect_get_policy_id(const char *policy) {
if (policy == NULL) {
errno = EINVAL;
return -1;
}
if (strcasecmp(policy, "Random") == 0) {
return PROXY_REVERSE_CONNECT_POLICY_RANDOM;
} else if (strcasecmp(policy, "RoundRobin") == 0) {
return PROXY_REVERSE_CONNECT_POLICY_ROUND_ROBIN;
} else if (strcasecmp(policy, "Shuffle") == 0) {
return PROXY_REVERSE_CONNECT_POLICY_SHUFFLE;
} else if (strcasecmp(policy, "LeastConns") == 0) {
return PROXY_REVERSE_CONNECT_POLICY_LEAST_CONNS;
} else if (strcasecmp(policy, "PerUser") == 0) {
return PROXY_REVERSE_CONNECT_POLICY_PER_USER;
} else if (strcasecmp(policy, "PerGroup") == 0) {
return PROXY_REVERSE_CONNECT_POLICY_PER_GROUP;
} else if (strcasecmp(policy, "PerHost") == 0) {
return PROXY_REVERSE_CONNECT_POLICY_PER_HOST;
} else if (strcasecmp(policy, "LeastResponseTime") == 0) {
return PROXY_REVERSE_CONNECT_POLICY_LEAST_RESPONSE_TIME;
}
errno = ENOENT;
return -1;
}
static int send_user(struct proxy_session *proxy_sess, cmd_rec *cmd,
int *successful) {
int res, xerrno;
pr_response_t *resp;
unsigned int resp_nlines = 0;
char *orig_user;
const char *uri_user;
orig_user = cmd->arg;
uri_user = proxy_conn_get_username(proxy_sess->dst_pconn);
if (uri_user != NULL) {
/* We have URI-specific USER name to use, instead of the client-provided
* one.
*/
pr_trace_msg(trace_channel, 18,
"using URI-specific username '%s' instead of client-provided '%s'",
uri_user, orig_user);
cmd->argv[1] = cmd->arg = pstrdup(cmd->pool, uri_user);
}
res = proxy_ftp_ctrl_send_cmd(cmd->tmp_pool, proxy_sess->backend_ctrl_conn,
cmd);
cmd->argv[1] = cmd->arg = orig_user;
if (res < 0) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error sending %s to backend: %s", (char *) cmd->argv[0],
strerror(xerrno));
errno = xerrno;
return -1;
}
resp = proxy_ftp_ctrl_recv_resp(cmd->tmp_pool, proxy_sess->backend_ctrl_conn,
&resp_nlines, 0);
if (resp == NULL) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error receiving %s response from backend: %s", (char *) cmd->argv[0],
strerror(xerrno));
errno = xerrno;
return -1;
}
/* Note that the response message may contain the per-URI user name we
* sent; be sure to preserve the illusion, and re-write the response as
* necessary.
*/
if (uri_user != NULL) {
/* TODO: handle the case where there are multiple response lines. */
if (strstr(resp->msg, uri_user) != NULL) {
resp->msg = sreplace(cmd->pool, resp->msg, uri_user, orig_user, NULL);
}
}
if (resp->num[0] == '2' ||
resp->num[0] == '3') {
*successful = TRUE;
if (strcmp(resp->num, R_232) == 0) {
proxy_sess_state |= PROXY_SESS_STATE_BACKEND_AUTHENTICATED;
clear_user_creds();
pr_timer_remove(PR_TIMER_LOGIN, ANY_MODULE);
}
}
res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool, proxy_sess->frontend_ctrl_conn,
resp, resp_nlines);
if (res < 0) {
xerrno = errno;
pr_response_block(TRUE);
errno = xerrno;
return -1;
}
return 0;
}
int proxy_reverse_handle_user(cmd_rec *cmd, struct proxy_session *proxy_sess,
int *successful, int *block_responses) {
int res;
if (reverse_flags == PROXY_REVERSE_FL_CONNECT_AT_PASS) {
/* If we're already connected, then proxy this USER command through to the
* backend, otherwise we let the proftpd internals deal with it locally,
* leading to proxy auth.
*/
if (!(proxy_sess_state & PROXY_SESS_STATE_PROXY_AUTHENTICATED)) {
*block_responses = FALSE;
return 0;
}
}
if (reverse_flags == PROXY_REVERSE_FL_CONNECT_AT_USER) {
int connected = FALSE, xerrno = 0;
res = proxy_reverse_connect(proxy_pool, proxy_sess, cmd->arg);
xerrno = errno;
if (res == 0) {
connected = TRUE;
}
pr_response_block(FALSE);
if (connected == FALSE) {
*successful = FALSE;
if (xerrno != EINVAL) {
errno = EPERM;
} else {
errno = xerrno;
}
return -1;
}
}
res = send_user(proxy_sess, cmd, successful);
if (res < 0) {
return -1;
}
if (reverse_flags == PROXY_REVERSE_FL_CONNECT_AT_USER) {
/* Restore the normal response blocking, undone for the PerUser policy. */
pr_response_block(TRUE);
}
return 1;
}
static int send_pass(struct proxy_session *proxy_sess, cmd_rec *cmd,
int *successful) {
int res, xerrno;
pr_response_t *resp;
unsigned int resp_nlines = 0;
const char *uri_user, *uri_pass;
char *orig_pass;
if (proxy_sess == NULL ||
proxy_sess->backend_ctrl_conn == NULL) {
pr_trace_msg(trace_channel, 4,
"unable to send PASS to backend server: No backend control connection");
errno = EPERM;
return -1;
}
orig_pass = cmd->arg;
uri_user = proxy_conn_get_username(proxy_sess->dst_pconn);
uri_pass = proxy_conn_get_password(proxy_sess->dst_pconn);
if (uri_pass != NULL) {
size_t uri_passlen;
uri_passlen = strlen(uri_pass);
if (uri_passlen > 0) {
/* We have URI-specific password to use, instead of the client-provided
* one.
*/
pr_trace_msg(trace_channel, 18,
"using URI-specific password instead of client-provided one");
cmd->argv[1] = cmd->arg = pstrdup(cmd->pool, uri_pass);
}
}
res = proxy_ftp_ctrl_send_cmd(cmd->tmp_pool, proxy_sess->backend_ctrl_conn,
cmd);
cmd->argv[1] = cmd->arg = orig_pass;
if (res < 0) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error sending %s to backend: %s", (char *) cmd->argv[0],
strerror(xerrno));
errno = xerrno;
return -1;
}
resp = proxy_ftp_ctrl_recv_resp(cmd->tmp_pool, proxy_sess->backend_ctrl_conn,
&resp_nlines, 0);
if (resp == NULL) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error receiving %s response from backend: %s", (char *) cmd->argv[0],
strerror(xerrno));
/* If we receive an EPERM here, it is probably because the backend
* closed its control connection, yielding an EOF. To better indicate
* this situation, propagate the error using EPIPE.
*/
if (xerrno == EPERM) {
xerrno = EPIPE;
}
errno = xerrno;
return -1;
}
/* Note that the response message may contain the per-URI user name we
* sent; be sure to preserve the illusion, and re-write the response as
* necessary.
*/
if (uri_user != NULL) {
const char *orig_user;
orig_user = pr_table_get(session.notes, "mod_auth.orig-user", NULL);
if (orig_user != NULL) {
/* TODO: handle the case where there are multiple response lines. */
if (strstr(resp->msg, uri_user) != NULL) {
resp->msg = sreplace(cmd->pool, resp->msg, uri_user, orig_user, NULL);
}
}
}
/* XXX What about other response codes for PASS? */
if (resp->num[0] == '2') {
*successful = TRUE;
proxy_sess_state |= PROXY_SESS_STATE_BACKEND_AUTHENTICATED;
clear_user_creds();
pr_timer_remove(PR_TIMER_LOGIN, ANY_MODULE);
}
res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool, proxy_sess->frontend_ctrl_conn,
resp, resp_nlines);
if (res < 0) {
xerrno = errno;
pr_response_block(TRUE);
errno = xerrno;
return -1;
}
return 0;
}
int proxy_reverse_handle_pass(cmd_rec *cmd, struct proxy_session *proxy_sess,
int *successful, int *block_responses) {
int res, xerrno = 0;
/* This CONNECT_AT_PASS flag indicates that we are using proxy auth when
* reverse proxying.
*/
if (reverse_flags == PROXY_REVERSE_FL_CONNECT_AT_PASS) {
if (!(proxy_sess_state & PROXY_SESS_STATE_PROXY_AUTHENTICATED)) {
const char *user = NULL;
user = pr_table_get(session.notes, "mod_auth.orig-user", NULL);
res = proxy_session_check_password(cmd->pool, user, cmd->arg);
if (res < 0) {
errno = EINVAL;
return -1;
}
res = proxy_session_setup_env(proxy_pool, user,
PROXY_SESSION_FL_CHECK_LOGIN_ACL);
if (res < 0) {
errno = EINVAL;
return -1;
}
if (session.auth_mech != NULL) {
pr_log_debug(DEBUG2, "user '%s' authenticated by %s", user,
session.auth_mech);
}
}
if (!(proxy_sess_state & PROXY_SESS_STATE_CONNECTED)) {
int connected = FALSE;
const char *user = NULL, *connect_name = NULL;
cmd_rec *user_cmd;
/* If we're using a sticky policy, we need to know the USER name that was
* sent.
*/
if (proxy_reverse_policy_is_sticky(reverse_connect_policy) == TRUE) {
user = connect_name = pr_table_get(session.notes, "mod_auth.orig-user",
NULL);
/* If the sticky policy in question is PerGroup, then we also need
* to know the authenticated user's primary group name.
*/
if (proxy_sess_state & PROXY_SESS_STATE_PROXY_AUTHENTICATED) {
if (reverse_connect_policy == PROXY_REVERSE_CONNECT_POLICY_PER_GROUP) {
connect_name = session.group;
}
}
}
res = proxy_reverse_connect(proxy_pool, proxy_sess, connect_name);
xerrno = errno;
if (res == 0) {
connected = TRUE;
}
pr_response_block(FALSE);
if (connected == FALSE) {
*successful = FALSE;
if (xerrno != EINVAL) {
errno = EPERM;
} else {
errno = xerrno;
}
return -1;
}
if (user == NULL) {
user = pr_table_get(session.notes, "mod_auth.orig-user", NULL);
}
user_cmd = pr_cmd_alloc(cmd->tmp_pool, 2, C_USER, user);
user_cmd->arg = pstrdup(cmd->tmp_pool, user);
/* Since we're replaying the USER command here, we want to make sure
* that the USER response from the backend isn't played back to the
* frontend client.
*/
pr_response_block(TRUE);
res = send_user(proxy_sess, user_cmd, successful);
xerrno = errno;
pr_response_block(FALSE);
if (res < 0) {
errno = xerrno;
return -1;
}
}
}
res = send_pass(proxy_sess, cmd, successful);
if (res < 0) {
return -1;
}
if (reverse_flags != PROXY_REVERSE_FL_CONNECT_AT_PASS &&
*successful == TRUE) {
const char *user = NULL;
/* If we're not using proxy auth, still make sure that everything is
* set up properly.
*/
user = pr_table_get(session.notes, "mod_auth.orig-user", NULL);
res = proxy_session_setup_env(proxy_pool, user, 0);
if (res < 0) {
errno = EINVAL;
return -1;
}
}
return 1;
}
proftpd-mod_proxy-0.9.5/lib/proxy/reverse/ 0000775 0000000 0000000 00000000000 14757370167 0020644 5 ustar 00root root 0000000 0000000 proftpd-mod_proxy-0.9.5/lib/proxy/reverse/db.c 0000664 0000000 0000000 00000155443 14757370167 0021411 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy reverse datastore implementation
* Copyright (c) 2012-2025 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "proxy/db.h"
#include "proxy/conn.h"
#include "proxy/reverse.h"
#include "proxy/reverse/db.h"
#include "proxy/random.h"
#include "proxy/tls.h"
#include "proxy/ftp/ctrl.h"
#include
extern xaset_t *server_list;
#define PROXY_REVERSE_DB_SCHEMA_NAME "proxy_reverse"
#define PROXY_REVERSE_DB_SCHEMA_VERSION 6
/* PerHost/PerUser/PerGroup table limits */
#define PROXY_REVERSE_DB_PERHOST_MAX_ENTRIES 8192
#define PROXY_REVERSE_DB_PERUSER_MAX_ENTRIES 8192
#define PROXY_REVERSE_DB_PERGROUP_MAX_ENTRIES 8192
static array_header *db_backends = NULL;
static const char *trace_channel = "proxy.reverse.db";
static unsigned int str2hash(const void *key, size_t keysz) {
unsigned int i = 0;
size_t sz = !keysz ? strlen((const char *) key) : keysz;
while (sz--) {
const char *k = key;
unsigned int c;
pr_signals_handle();
c = k[sz];
i = (i * 33) + c;
}
return i;
}
static int reverse_db_add_schema(pool *p, struct proxy_dbh *dbh,
const char *db_path) {
int res;
const char *stmt, *errstr = NULL;
/* CREATE TABLE proxy_vhosts (
* vhost_id INTEGER NOT NULL PRIMARY KEY,
* vhost_name TEXT NOT NULL
* );
*/
stmt = "CREATE TABLE IF NOT EXISTS proxy_vhosts (vhost_id INTEGER NOT NULL PRIMARY KEY, vhost_name TEXT NOT NULL);";
res = proxy_db_exec_stmt(p, dbh, stmt, &errstr);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr);
errno = EPERM;
return -1;
}
/* TODO: Add these columns:
* unhealthy BOOLEAN,
* unhealthy_ms BIGINT,
* unhealthy_reason TEXT
*/
/* CREATE TABLE proxy_vhost_backends (
* vhost_id INTEGER NOT NULL,
* backend_id INTEGER NOT NULL,
* backend_uri TEXT NOT NULL,
* conn_count INTEGER NOT NULL,
* connect_ms INTEGER
* );
*
* Note: while it might be tempting to have a FOREIGN KEY constraint on
* vhost_id to the proxy_vhosts.vhost_id column, doing so also means that
* vhost_id MUST be unique. And there will be vhosts that have MULTIPLE
* backend URIs, which would violate that uniqueness constraint. Thus we
* create our own separate index on the vhost_id column.
*/
stmt = "CREATE TABLE IF NOT EXISTS proxy_vhost_backends (vhost_id INTEGER NOT NULL, backend_id INTEGER NOT NULL, backend_uri TEXT NOT NULL, conn_count INTEGER NOT NULL, connect_ms INTEGER);";
res = proxy_db_exec_stmt(p, dbh, stmt, &errstr);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr);
errno = EPERM;
return -1;
}
/* CREATE INDEX proxy_vhost_backends_vhost_id_idx */
stmt = "CREATE INDEX IF NOT EXISTS proxy_vhost_backends_vhost_id_idx ON proxy_vhost_backends (vhost_id);";
res = proxy_db_exec_stmt(p, dbh, stmt, &errstr);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr);
errno = EPERM;
return -1;
}
/* CREATE TABLE proxy_vhost_reverse_roundrobin (
* vhost_id INTEGER NOT NULL,
* current_backend_id INTEGER NOT NULL,
* FOREIGN KEY (vhost_id) REFERENCES proxy_vhosts (vhost_id),
* FOREIGN KEY (current_backend_id) REFERENCES proxy_vhost_backends (backend_id)
* );
*/
stmt = "CREATE TABLE IF NOT EXISTS proxy_vhost_reverse_roundrobin (vhost_id INTEGER NOT NULL, current_backend_id INTEGER NOT NULL, FOREIGN KEY (vhost_id) REFERENCES proxy_vhosts (vhost_id), FOREIGN KEY (current_backend_id) REFERENCES proxy_vhost_backends (backend_id));";
res = proxy_db_exec_stmt(p, dbh, stmt, &errstr);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr);
errno = EPERM;
return -1;
}
/* CREATE TABLE proxy_vhost_reverse_shuffle (
* vhost_id INTEGER NOT NULL,
* avail_backend_id INTEGER NOT NULL,
* FOREIGN KEY (vhost_id) REFERENCES proxy_vhosts (vhost_id),
* FOREIGN KEY (avail_backend_id) REFERENCES proxy_vhost_backends (backend_id)
* );
*/
stmt = "CREATE TABLE IF NOT EXISTS proxy_vhost_reverse_shuffle (vhost_id INTEGER NOT NULL, avail_backend_id INTEGER NOT NULL, FOREIGN KEY (vhost_id) REFERENCES proxy_vhosts (vhost_id), FOREIGN KEY (avail_backend_id) REFERENCES proxy_vhost_backends (backend_id));";
res = proxy_db_exec_stmt(p, dbh, stmt, &errstr);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr);
errno = EPERM;
return -1;
}
/* CREATE TABLE proxy_vhost_reverse_per_user (
* vhost_id INTEGER NOT NULL,
* user_name TEXT NOT NULL,
* backend_uri TEXT,
* FOREIGN KEY (vhost_id) REFERENCES proxy_vhosts (vhost_id),
* UNIQUE (vhost_id, user_name)
* );
*/
stmt = "CREATE TABLE IF NOT EXISTS proxy_vhost_reverse_per_user (vhost_id INTEGER NOT NULL, user_name TEXT NOT NULL, backend_uri TEXT, FOREIGN KEY (vhost_id) REFERENCES proxy_vhosts (vhost_id), UNIQUE (vhost_id, user_name));";
res = proxy_db_exec_stmt(p, dbh, stmt, &errstr);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr);
errno = EPERM;
return -1;
}
/* CREATE INDEX proxy_vhost_reverse_per_user_name_idx */
stmt = "CREATE INDEX IF NOT EXISTS proxy_vhost_reverse_per_user_name_idx ON proxy_vhost_reverse_per_user (user_name);";
res = proxy_db_exec_stmt(p, dbh, stmt, &errstr);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr);
errno = EPERM;
return -1;
}
/* CREATE TABLE proxy_vhost_reverse_per_group (
* vhost_id INTEGER NOT NULL,
* group_name TEXT NOT NULL,
* backend_uri TEXT,
* FOREIGN KEY (vhost_id) REFERENCES proxy_vhosts (vhost_id),
* UNIQUE (vhost_id, group_name)
* );
*/
stmt = "CREATE TABLE IF NOT EXISTS proxy_vhost_reverse_per_group (vhost_id INTEGER NOT NULL, group_name TEXT NOT NULL, backend_uri TEXT, FOREIGN KEY (vhost_id) REFERENCES proxy_vhosts (vhost_id), UNIQUE (vhost_id, group_name));";
res = proxy_db_exec_stmt(p, dbh, stmt, &errstr);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr);
errno = EPERM;
return -1;
}
/* CREATE INDEX proxy_vhost_reverse_per_group_name_idx */
stmt = "CREATE INDEX IF NOT EXISTS proxy_vhost_reverse_per_group_name_idx ON proxy_vhost_reverse_per_group (group_name);";
res = proxy_db_exec_stmt(p, dbh, stmt, &errstr);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr);
errno = EPERM;
return -1;
}
/* CREATE TABLE proxy_vhost_reverse_per_host (
* vhost_id INTEGER NOT NULL,
* ip_addr TEXT NOT NULL,
* backend_uri TEXT,
* FOREIGN KEY (vhost_id) REFERENCES proxy_vhosts (vhost_id),
* UNIQUE (vhost_id, ip_addr)
* );
*/
stmt = "CREATE TABLE IF NOT EXISTS proxy_vhost_reverse_per_host (vhost_id INTEGER NOT NULL, ip_addr TEXT NOT NULL, backend_uri TEXT, FOREIGN KEY (vhost_id) REFERENCES proxy_vhosts (vhost_id), UNIQUE (vhost_id, ip_addr));";
res = proxy_db_exec_stmt(p, dbh, stmt, &errstr);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr);
errno = EPERM;
return -1;
}
/* CREATE INDEX proxy_vhost_reverse_per_host_ipaddr_idx */
stmt = "CREATE INDEX IF NOT EXISTS proxy_vhost_reverse_per_host_ipaddr_idx ON proxy_vhost_reverse_per_host (ip_addr);";
res = proxy_db_exec_stmt(p, dbh, stmt, &errstr);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr);
errno = EPERM;
return -1;
}
return 0;
}
static int reverse_db_truncate_tables(pool *p, struct proxy_dbh *dbh) {
int res;
const char *index_name, *stmt, *errstr = NULL;
stmt = "DELETE FROM proxy_vhosts;";
res = proxy_db_exec_stmt(p, dbh, stmt, &errstr);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr);
errno = EPERM;
return -1;
}
stmt = "DELETE FROM proxy_vhost_backends;";
res = proxy_db_exec_stmt(p, dbh, stmt, &errstr);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr);
errno = EPERM;
return -1;
}
stmt = "DELETE FROM proxy_vhost_reverse_roundrobin;";
res = proxy_db_exec_stmt(p, dbh, stmt, &errstr);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr);
errno = EPERM;
return -1;
}
stmt = "DELETE FROM proxy_vhost_reverse_shuffle;";
res = proxy_db_exec_stmt(p, dbh, stmt, &errstr);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr);
errno = EPERM;
return -1;
}
stmt = "DELETE FROM proxy_vhost_reverse_per_user;";
res = proxy_db_exec_stmt(p, dbh, stmt, &errstr);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr);
errno = EPERM;
return -1;
}
stmt = "DELETE FROM proxy_vhost_reverse_per_group;";
res = proxy_db_exec_stmt(p, dbh, stmt, &errstr);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr);
errno = EPERM;
return -1;
}
stmt = "DELETE FROM proxy_vhost_reverse_per_host;";
res = proxy_db_exec_stmt(p, dbh, stmt, &errstr);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr);
errno = EPERM;
return -1;
}
/* Note: don't forget to rebuild the indices, too! */
index_name = "proxy_vhost_backends_vhost_id_idx";
res = proxy_db_reindex(p, dbh, index_name, &errstr);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error reindexing '%s': %s", index_name, errstr);
errno = EPERM;
return -1;
}
index_name = "proxy_vhost_reverse_per_user_name_idx";
res = proxy_db_reindex(p, dbh, index_name, &errstr);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error reindexing '%s': %s", index_name, errstr);
errno = EPERM;
return -1;
}
index_name = "proxy_vhost_reverse_per_group_name_idx";
res = proxy_db_reindex(p, dbh, index_name, &errstr);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error reindexing '%s': %s", index_name, errstr);
errno = EPERM;
return -1;
}
index_name = "proxy_vhost_reverse_per_host_ipaddr_idx";
res = proxy_db_reindex(p, dbh, index_name, &errstr);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error reindexing '%s': %s", index_name, errstr);
errno = EPERM;
return -1;
}
return 0;
}
static int reverse_db_add_vhost(pool *p, struct proxy_dbh *dbh, server_rec *s) {
int res, xerrno = 0;
const char *stmt, *errstr = NULL;
array_header *results;
stmt = "INSERT INTO proxy_vhosts (vhost_id, vhost_name) VALUES (?, ?);";
res = proxy_db_prepare_stmt(p, dbh, stmt);
if (res < 0) {
xerrno = errno;
(void) pr_log_debug(DEBUG3, MOD_PROXY_VERSION
": error preparing statement '%s': %s", stmt, strerror(xerrno));
errno = xerrno;
return -1;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT,
(void *) &(s->sid), 0);
if (res < 0) {
return -1;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT,
(void *) s->ServerName, -1);
if (res < 0) {
return -1;
}
results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr);
if (results == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr ? errstr : strerror(errno));
errno = EPERM;
return -1;
}
return 0;
}
static int reverse_db_add_backend(pool *p, struct proxy_dbh *dbh,
unsigned int vhost_id, const char *backend_uri, int backend_id) {
int res;
const char *stmt, *errstr = NULL;
array_header *results;
stmt = "INSERT INTO proxy_vhost_backends (vhost_id, backend_uri, backend_id, conn_count) VALUES (?, ?, ?, 0);";
res = proxy_db_prepare_stmt(p, dbh, stmt);
if (res < 0) {
return -1;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT,
(void *) &vhost_id, 0);
if (res < 0) {
return -1;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT,
(void *) backend_uri, -1);
if (res < 0) {
return -1;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 3, PROXY_DB_BIND_TYPE_INT,
(void *) &backend_id, 0);
if (res < 0) {
return -1;
}
pr_trace_msg(trace_channel, 13,
"adding backend '%.100s' to database table at index %d", backend_uri,
backend_id);
results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr);
if (results == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr ? errstr : strerror(errno));
errno = EPERM;
return -1;
}
return 0;
}
static int reverse_db_add_backends(pool *p, struct proxy_dbh *dbh,
unsigned int vhost_id, array_header *backends) {
register unsigned int i;
for (i = 0; i < backends->nelts; i++) {
int res;
struct proxy_conn *pconn;
const char *backend_uri;
pconn = ((struct proxy_conn **) backends->elts)[i];
backend_uri = proxy_conn_get_uri(pconn);
res = reverse_db_add_backend(p, dbh, vhost_id, backend_uri, i);
if (res < 0) {
int xerrno = errno;
pr_trace_msg(trace_channel, 6,
"error adding database entry for backend '%.100s': %s", backend_uri,
strerror(xerrno));
errno = xerrno;
return -1;
}
pr_trace_msg(trace_channel, 18,
"added database entry for backend '%.100s' (ID %u)", backend_uri, i);
}
return 0;
}
/* ProxyReverseConnectPolicy: Shuffle */
static int reverse_db_add_shuffle(pool *p, struct proxy_dbh *dbh,
unsigned int vhost_id, int backend_id) {
int res;
const char *stmt, *errstr = NULL;
array_header *results;
stmt = "INSERT INTO proxy_vhost_reverse_shuffle (vhost_id, avail_backend_id) VALUES (?, ?);";
res = proxy_db_prepare_stmt(p, dbh, stmt);
if (res < 0) {
return -1;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT,
(void *) &vhost_id, 0);
if (res < 0) {
return -1;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_INT,
(void *) &backend_id, 0);
if (res < 0) {
return -1;
}
results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr);
if (results == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr ? errstr : strerror(errno));
errno = EPERM;
return -1;
}
return 0;
}
static int reverse_db_shuffle_init(pool *p, struct proxy_dbh *dbh,
unsigned int vhost_id, array_header *backends) {
register unsigned int i;
for (i = 0; i < backends->nelts; i++) {
int res;
res = reverse_db_add_shuffle(p, dbh, vhost_id, i);
if (res < 0) {
int xerrno = errno;
pr_trace_msg(trace_channel, 6,
"error adding shuffle database entry for ID %d: %s", i,
strerror(xerrno));
errno = xerrno;
return -1;
}
}
return 0;
}
static int reverse_db_shuffle_next(pool *p, struct proxy_dbh *dbh,
unsigned int vhost_id) {
int backend_id = -1, res;
const char *stmt, *errstr = NULL;
array_header *results;
unsigned int nrows = 0;
stmt = "SELECT COUNT(*) FROM proxy_vhost_reverse_shuffle WHERE vhost_id = ?;";
res = proxy_db_prepare_stmt(p, dbh, stmt);
if (res < 0) {
return -1;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT,
(void *) &vhost_id, 0);
if (res < 0) {
return -1;
}
results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr);
if (results == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr ? errstr : strerror(errno));
errno = EPERM;
return -1;
}
if (results->nelts != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"expected 1 result from statement '%s', got %d", stmt,
results->nelts);
errno = EINVAL;
return -1;
}
nrows = atoi(((char **) results->elts)[0]);
if (nrows == 0) {
res = reverse_db_shuffle_init(p, dbh, vhost_id, db_backends);
if (res < 0) {
return -1;
}
nrows = db_backends->nelts;
}
backend_id = (int) proxy_random_next(0, nrows-1);
return backend_id;
}
static int reverse_db_shuffle_used(pool *p, struct proxy_dbh *dbh,
unsigned int vhost_id, int backend_id) {
int res, xerrno = 0;
const char *stmt, *errstr = NULL;
array_header *results;
stmt = "DELETE FROM proxy_vhost_reverse_shuffle WHERE vhost_id = ? AND avail_backend_id = ?;";
res = proxy_db_prepare_stmt(p, dbh, stmt);
if (res < 0) {
xerrno = errno;
(void) pr_log_debug(DEBUG3, MOD_PROXY_VERSION
": error preparing statement '%s': %s", stmt, strerror(xerrno));
errno = xerrno;
return -1;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT,
(void *) &vhost_id, 0);
if (res < 0) {
return -1;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_INT,
(void *) &backend_id, 0);
if (res < 0) {
return -1;
}
results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr);
if (results == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr ? errstr : strerror(errno));
errno = EPERM;
return -1;
}
return 0;
}
/* ProxyReverseConnectPolicy: RoundRobin */
static int reverse_db_roundrobin_update(pool *p, struct proxy_dbh *dbh,
unsigned int vhost_id, int backend_id) {
int res;
const char *stmt, *errstr = NULL;
array_header *results;
stmt = "UPDATE proxy_vhost_reverse_roundrobin SET current_backend_id = ? WHERE vhost_id = ?;";
res = proxy_db_prepare_stmt(p, dbh, stmt);
if (res < 0) {
return -1;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT,
(void *) &backend_id, 0);
if (res < 0) {
return -1;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_INT,
(void *) &vhost_id, 0);
if (res < 0) {
return -1;
}
results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr);
if (results == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr ? errstr : strerror(errno));
errno = EPERM;
return -1;
}
return 0;
}
static int reverse_db_roundrobin_init(pool *p, struct proxy_dbh *dbh,
unsigned int vhost_id, int backend_id) {
int res;
const char *stmt, *errstr = NULL;
array_header *results;
stmt = "INSERT INTO proxy_vhost_reverse_roundrobin (vhost_id, current_backend_id) VALUES (?, ?);";
res = proxy_db_prepare_stmt(p, dbh, stmt);
if (res < 0) {
return -1;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT,
(void *) &vhost_id, 0);
if (res < 0) {
return -1;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_INT,
(void *) &backend_id, 0);
if (res < 0) {
return -1;
}
results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr);
if (results == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr ? errstr : strerror(errno));
errno = EPERM;
return -1;
}
return 0;
}
static int reverse_db_roundrobin_next(pool *p, struct proxy_dbh *dbh,
unsigned int vhost_id) {
int backend_id = 0, res;
const char *stmt, *errstr = NULL;
array_header *results;
stmt = "SELECT current_backend_id FROM proxy_vhost_reverse_roundrobin WHERE vhost_id = ?;";
res = proxy_db_prepare_stmt(p, dbh, stmt);
if (res < 0) {
return -1;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT,
(void *) &vhost_id, 0);
if (res < 0) {
return -1;
}
results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr);
if (results == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr ? errstr : strerror(errno));
errno = EPERM;
return -1;
}
if (results->nelts != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"expected 1 result from statement '%s', got %d", stmt,
results->nelts);
errno = EINVAL;
return -1;
}
backend_id = atoi(((char **) results->elts)[0]);
/* If the current backend ID is the last one, wrap around to index 0. */
if (backend_id == ((int) db_backends->nelts-1)) {
backend_id = 0;
} else {
backend_id++;
}
return backend_id;
}
static int reverse_db_roundrobin_used(pool *p, struct proxy_dbh *dbh,
unsigned int vhost_id, int backend_id) {
return reverse_db_roundrobin_update(p, dbh, vhost_id, backend_id);
}
/* ProxyReverseConnectPolicy: LeastConns */
static int reverse_db_leastconns_next(pool *p, struct proxy_dbh *dbh,
unsigned int vhost_id) {
int backend_id = 0, res;
const char *stmt, *errstr = NULL;
array_header *results;
stmt = "SELECT backend_id FROM proxy_vhost_backends WHERE vhost_id = ? ORDER BY conn_count ASC LIMIT 1;";
res = proxy_db_prepare_stmt(p, dbh, stmt);
if (res < 0) {
return -1;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT,
(void *) &vhost_id, 0);
if (res < 0) {
return -1;
}
results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr);
if (results == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr ? errstr : strerror(errno));
errno = EPERM;
return -1;
}
if (results->nelts == 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"expected results from statement '%s', got %d", stmt,
results->nelts);
errno = EINVAL;
return -1;
}
/* Just pick the first index/backend returned. */
backend_id = atoi(((char **) results->elts)[0]);
return backend_id;
}
static int reverse_db_leastconns_used(pool *p, struct proxy_dbh *dbh,
unsigned int vhost_id, int backend_id) {
/* TODO: anything to do here? */
return 0;
}
/* ProxyReverseConnectPolicy: LeastResponseTime */
/* Note: "least response time" is determined by calculating the following
* for each backend server:
*
* N = connection count * connect time (ms)
*
* and choosing the backend with the lowest value for N. If there are no
* backend servers with connect time values, choose the one with the lowest
* connection count.
*/
static int reverse_db_leastresponsetime_next(pool *p, struct proxy_dbh *dbh,
unsigned int vhost_id) {
int backend_id = 0, res;
const char *stmt, *errstr = NULL;
array_header *results;
stmt = "SELECT backend_id FROM proxy_vhost_backends WHERE vhost_id = ? ORDER BY (conn_count * connect_ms) ASC LIMIT 1;";
res = proxy_db_prepare_stmt(p, dbh, stmt);
if (res < 0) {
return -1;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT,
(void *) &vhost_id, 0);
if (res < 0) {
return -1;
}
results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr);
if (results == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr ? errstr : strerror(errno));
errno = EPERM;
return -1;
}
if (results->nelts == 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"expected results from statement '%s', got %d", stmt,
results->nelts);
errno = EINVAL;
return -1;
}
/* Just pick the first index/backend returned. */
backend_id = atoi(((char **) results->elts)[0]);
return backend_id;
}
static int reverse_db_leastresponsetime_used(pool *p, struct proxy_dbh *dbh,
unsigned int vhost_id, int backend_id) {
/* TODO: anything to do here? */
return 0;
}
/* ProxyReverseConnectPolicy: PerUser */
static array_header *reverse_db_peruser_get(pool *p, struct proxy_dbh *dbh,
unsigned int vhost_id, const char *user) {
int res;
const char *stmt, *errstr = NULL;
array_header *results;
stmt = "SELECT backend_uri FROM proxy_vhost_reverse_per_user WHERE vhost_id = ? AND user_name = ?;";
res = proxy_db_prepare_stmt(p, dbh, stmt);
if (res < 0) {
return NULL;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT,
(void *) &vhost_id, 0);
if (res < 0) {
return NULL;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT,
(void *) user, -1);
if (res < 0) {
return NULL;
}
results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr);
if (results == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr ? errstr : strerror(errno));
errno = EPERM;
return NULL;
}
return results;
}
static const struct proxy_conn *reverse_db_peruser_init(pool *p,
struct proxy_dbh *dbh, unsigned int vhost_id, const char *user) {
const struct proxy_conn *pconn = NULL;
struct proxy_conn **conns = NULL;
int backend_count = 0, res;
const char *stmt, *uri, *errstr = NULL;
array_header *backends, *results;
backends = proxy_reverse_pername_backends(p, user, TRUE);
if (backends == NULL) {
return NULL;
}
backend_count = backends->nelts;
conns = backends->elts;
if (backend_count == 1) {
pconn = conns[0];
} else {
size_t user_len;
unsigned int h;
int idx;
user_len = strlen(user);
h = str2hash(user, user_len);
idx = h % backend_count;
pconn = conns[idx];
}
/* TODO: What happens if the chosen backend URI cannot be used, e.g.
* because it is down/unreachable? In reverse_try_connect(), we'll know
* that it failed to connect, but how to tunnel that back down here, to
* choose another?
*/
stmt = "INSERT OR IGNORE INTO proxy_vhost_reverse_per_user (vhost_id, user_name, backend_uri) VALUES (?, ?, ?);";
res = proxy_db_prepare_stmt(p, dbh, stmt);
if (res < 0) {
return NULL;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT,
(void *) &vhost_id, 0);
if (res < 0) {
return NULL;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT,
(void *) user, -1);
if (res < 0) {
return NULL;
}
uri = proxy_conn_get_uri(pconn);
res = proxy_db_bind_stmt(p, dbh, stmt, 3, PROXY_DB_BIND_TYPE_TEXT,
(void *) uri, -1);
if (res < 0) {
return NULL;
}
results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr);
if (results == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr ? errstr : strerror(errno));
errno = EPERM;
return NULL;
}
return pconn;
}
static const struct proxy_conn *reverse_db_peruser_next(pool *p,
struct proxy_dbh *dbh, unsigned int vhost_id, const char *user) {
array_header *results;
const struct proxy_conn *pconn = NULL;
pconn = reverse_db_peruser_init(p, dbh, vhost_id, user);
if (pconn == NULL &&
errno != ENOENT) {
results = reverse_db_peruser_get(p, dbh, vhost_id, user);
if (results != NULL &&
results->nelts > 0) {
char **vals;
vals = results->elts;
pconn = proxy_conn_create(p, vals[0], 0);
}
}
if (pconn != NULL) {
return pconn;
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error preparing database for ProxyReverseConnectPolicy PerUser for "
"user '%s': %s", user, strerror(ENOENT));
errno = EPERM;
return NULL;
}
static int reverse_db_peruser_used(pool *p, struct proxy_dbh *dbh,
unsigned int vhost_id, int backend_id) {
int count, res;
const char *stmt, *errstr = NULL;
array_header *results;
/* To prevent database bloating too much, delete all of the entries
* in the table if we're over our limit.
*/
stmt = "SELECT COUNT(*) FROM proxy_vhost_reverse_per_user;";
res = proxy_db_prepare_stmt(p, dbh, stmt);
if (res < 0) {
return -1;
}
results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr);
if (results == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr ? errstr : strerror(errno));
errno = EPERM;
return -1;
}
if (results->nelts != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"expected 1 result from statement '%s', got %d", stmt,
results->nelts);
errno = EINVAL;
return -1;
}
count = atoi(((char **) results->elts)[0]);
if (count <= PROXY_REVERSE_DB_PERUSER_MAX_ENTRIES) {
return 0;
}
pr_trace_msg(trace_channel, 5,
"PerUser entry count (%d) exceeds max (%d), purging", count,
PROXY_REVERSE_DB_PERUSER_MAX_ENTRIES);
stmt = "DELETE FROM proxy_vhost_reverse_per_user;";
res = proxy_db_prepare_stmt(p, dbh, stmt);
if (res < 0) {
return -1;
}
results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr);
if (results == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr ? errstr : strerror(errno));
errno = EPERM;
return -1;
}
return 0;
}
/* ProxyReverseConnectPolicy: PerGroup */
static array_header *reverse_db_pergroup_get(pool *p, struct proxy_dbh *dbh,
unsigned int vhost_id, const char *group) {
int res;
const char *stmt, *errstr = NULL;
array_header *results;
stmt = "SELECT backend_uri FROM proxy_vhost_reverse_per_group WHERE vhost_id = ? AND group_name = ?;";
res = proxy_db_prepare_stmt(p, dbh, stmt);
if (res < 0) {
return NULL;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT,
(void *) &vhost_id, 0);
if (res < 0) {
return NULL;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT,
(void *) group, -1);
if (res < 0) {
return NULL;
}
results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr);
if (results == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr ? errstr : strerror(errno));
errno = EPERM;
return NULL;
}
return results;
}
static const struct proxy_conn *reverse_db_pergroup_init(pool *p,
struct proxy_dbh *dbh, unsigned int vhost_id, const char *group) {
const struct proxy_conn *pconn = NULL;
struct proxy_conn **conns = NULL;
int backend_count = 0, res;
const char *stmt, *uri, *errstr = NULL;
array_header *backends, *results;
backends = proxy_reverse_pername_backends(p, group, FALSE);
if (backends == NULL) {
return NULL;
}
backend_count = backends->nelts;
conns = backends->elts;
if (backend_count == 1) {
pconn = conns[0];
} else {
size_t group_len;
unsigned int h;
int idx;
group_len = strlen(group);
h = str2hash(group, group_len);
idx = h % backend_count;
pconn = conns[idx];
}
/* TODO: What happens if the chosen backend URI cannot be used, e.g.
* because it is down/unreachable? In reverse_try_connect(), we'll know
* that it failed to connect, but how to tunnel that back down here, to
* choose another?
*/
stmt = "INSERT OR IGNORE INTO proxy_vhost_reverse_per_group (vhost_id, group_name, backend_uri) VALUES (?, ?, ?);";
res = proxy_db_prepare_stmt(p, dbh, stmt);
if (res < 0) {
return NULL;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT,
(void *) &vhost_id, 0);
if (res < 0) {
return NULL;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT,
(void *) group, -1);
if (res < 0) {
return NULL;
}
uri = proxy_conn_get_uri(pconn);
res = proxy_db_bind_stmt(p, dbh, stmt, 3, PROXY_DB_BIND_TYPE_TEXT,
(void *) uri, -1);
if (res < 0) {
return NULL;
}
results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr);
if (results == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr ? errstr : strerror(errno));
errno = EPERM;
return NULL;
}
return pconn;
}
static const struct proxy_conn *reverse_db_pergroup_next(pool *p,
struct proxy_dbh *dbh, unsigned int vhost_id, const char *group) {
array_header *results;
const struct proxy_conn *pconn = NULL;
pconn = reverse_db_pergroup_init(p, dbh, vhost_id, group);
if (pconn == NULL &&
errno != ENOENT) {
results = reverse_db_pergroup_get(p, dbh, vhost_id, group);
if (results != NULL &&
results->nelts > 0) {
char **vals;
vals = results->elts;
pconn = proxy_conn_create(p, vals[0], 0);
}
}
if (pconn != NULL) {
return pconn;
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error preparing database for ProxyReverseConnectPolicy PerGroup for "
"group '%s': %s", group, strerror(ENOENT));
errno = EPERM;
return NULL;
}
static int reverse_db_pergroup_used(pool *p, struct proxy_dbh *dbh,
unsigned int vhost_id, int backend_id) {
int count, res;
const char *stmt, *errstr = NULL;
array_header *results;
/* To prevent database bloating too much, delete all of the entries
* in the table if we're over our limit.
*/
stmt = "SELECT COUNT(*) FROM proxy_vhost_reverse_per_group;";
res = proxy_db_prepare_stmt(p, dbh, stmt);
if (res < 0) {
return -1;
}
results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr);
if (results == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr ? errstr : strerror(errno));
errno = EPERM;
return -1;
}
if (results->nelts != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"expected 1 result from statement '%s', got %d", stmt,
results->nelts);
errno = EINVAL;
return -1;
}
count = atoi(((char **) results->elts)[0]);
if (count <= PROXY_REVERSE_DB_PERGROUP_MAX_ENTRIES) {
return 0;
}
pr_trace_msg(trace_channel, 5,
"PerGroup entry count (%d) exceeds max (%d), purging", count,
PROXY_REVERSE_DB_PERGROUP_MAX_ENTRIES);
stmt = "DELETE FROM proxy_vhost_reverse_per_group;";
res = proxy_db_prepare_stmt(p, dbh, stmt);
if (res < 0) {
return -1;
}
results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr);
if (results == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr ? errstr : strerror(errno));
errno = EPERM;
return -1;
}
return 0;
}
/* ProxyReverseConnectPolicy: PerHost */
static array_header *reverse_db_perhost_get(pool *p, struct proxy_dbh *dbh,
unsigned int vhost_id, const pr_netaddr_t *addr) {
int res;
const char *stmt, *errstr = NULL, *ip;
array_header *results;
stmt = "SELECT backend_uri FROM proxy_vhost_reverse_per_host WHERE vhost_id = ? AND ip_addr = ?;";
res = proxy_db_prepare_stmt(p, dbh, stmt);
if (res < 0) {
return NULL;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT,
(void *) &vhost_id, 0);
if (res < 0) {
return NULL;
}
ip = pr_netaddr_get_ipstr(addr);
res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT,
(void *) ip, -1);
if (res < 0) {
return NULL;
}
results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr);
if (results == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr ? errstr : strerror(errno));
errno = EPERM;
return NULL;
}
return results;
}
static const struct proxy_conn *reverse_db_perhost_init(pool *p,
struct proxy_dbh *dbh, unsigned int vhost_id, array_header *backends,
const pr_netaddr_t *addr) {
const struct proxy_conn *pconn = NULL;
struct proxy_conn **conns;
int res;
const char *ip, *stmt, *uri, *errstr = NULL;
array_header *results;
ip = pr_netaddr_get_ipstr(addr);
conns = backends->elts;
if (backends->nelts == 1) {
pconn = conns[0];
} else {
size_t iplen;
unsigned int h;
int idx;
iplen = strlen(ip);
h = str2hash(ip, iplen);
idx = h % backends->nelts;
pconn = conns[idx];
}
stmt = "INSERT OR IGNORE INTO proxy_vhost_reverse_per_host (vhost_id, ip_addr, backend_uri) VALUES (?, ?, ?);";
res = proxy_db_prepare_stmt(p, dbh, stmt);
if (res < 0) {
return NULL;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT,
(void *) &vhost_id, 0);
if (res < 0) {
return NULL;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT,
(void *) ip, -1);
if (res < 0) {
return NULL;
}
uri = proxy_conn_get_uri(pconn);
res = proxy_db_bind_stmt(p, dbh, stmt, 3, PROXY_DB_BIND_TYPE_TEXT,
(void *) uri, -1);
if (res < 0) {
return NULL;
}
results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr);
if (results == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr ? errstr : strerror(errno));
errno = EPERM;
return NULL;
}
return pconn;
}
static const struct proxy_conn *reverse_db_perhost_next(pool *p,
struct proxy_dbh *dbh, unsigned int vhost_id, const pr_netaddr_t *addr) {
array_header *results;
const struct proxy_conn *pconn = NULL;
results = reverse_db_perhost_get(p, dbh, vhost_id, addr);
if (results == NULL) {
return NULL;
}
if (results->nelts == 0) {
/* This can happen the very first time; perform an on-demand discovery
* of the backends for this host, and try again.
*/
pconn = reverse_db_perhost_init(p, dbh, vhost_id, db_backends, addr);
if (pconn == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error preparing database for ProxyReverseConnectPolicy "
"PerHost for host '%s': %s", pr_netaddr_get_ipstr(addr),
strerror(errno));
errno = EPERM;
return NULL;
}
} else {
char **vals;
vals = results->elts;
pconn = proxy_conn_create(p, vals[0], 0);
}
return pconn;
}
static int reverse_db_perhost_used(pool *p, struct proxy_dbh *dbh,
unsigned int vhost_id, int backend_id) {
int count, res;
const char *stmt, *errstr = NULL;
array_header *results;
/* To prevent database bloating too much, delete all of the entries
* in the table if we're over our limit.
*/
stmt = "SELECT COUNT(*) FROM proxy_vhost_reverse_per_host;";
res = proxy_db_prepare_stmt(p, dbh, stmt);
if (res < 0) {
return -1;
}
results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr);
if (results == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr ? errstr : strerror(errno));
errno = EPERM;
return -1;
}
if (results->nelts != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"expected 1 result from statement '%s', got %d", stmt,
results->nelts);
errno = EINVAL;
return -1;
}
count = atoi(((char **) results->elts)[0]);
if (count <= PROXY_REVERSE_DB_PERHOST_MAX_ENTRIES) {
return 0;
}
pr_trace_msg(trace_channel, 5,
"PerHost entry count (%d) exceeds max (%d), purging", count,
PROXY_REVERSE_DB_PERHOST_MAX_ENTRIES);
stmt = "DELETE FROM proxy_vhost_reverse_per_host;";
res = proxy_db_prepare_stmt(p, dbh, stmt);
if (res < 0) {
return -1;
}
results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr);
if (results == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr ? errstr : strerror(errno));
errno = EPERM;
return -1;
}
return 0;
}
/* ProxyReverseServers API/handling */
static int reverse_db_policy_init(pool *p, void *dbh, int policy_id,
unsigned int vhost_id, array_header *backends, unsigned long opts) {
int res, xerrno;
switch (policy_id) {
case PROXY_REVERSE_CONNECT_POLICY_RANDOM:
case PROXY_REVERSE_CONNECT_POLICY_LEAST_CONNS:
case PROXY_REVERSE_CONNECT_POLICY_LEAST_RESPONSE_TIME:
case PROXY_REVERSE_CONNECT_POLICY_PER_USER:
case PROXY_REVERSE_CONNECT_POLICY_PER_HOST:
/* No preparation needed at this time. */
res = 0;
break;
case PROXY_REVERSE_CONNECT_POLICY_ROUND_ROBIN: {
int backend_id = 0;
if (backends != NULL) {
backend_id = backends->nelts-1;
}
res = reverse_db_roundrobin_init(p, dbh, vhost_id, backend_id);
if (res < 0) {
xerrno = errno;
pr_log_debug(DEBUG3, MOD_PROXY_VERSION
": error preparing database for ProxyReverseConnectPolicy "
"RoundRobin: %s", strerror(xerrno));
errno = xerrno;
}
break;
}
case PROXY_REVERSE_CONNECT_POLICY_SHUFFLE:
if (backends != NULL) {
res = reverse_db_shuffle_init(p, dbh, vhost_id, backends);
if (res < 0) {
xerrno = errno;
pr_log_debug(DEBUG3, MOD_PROXY_VERSION
": error preparing database for ProxyReverseConnectPolicy "
"Shuffle: %s", strerror(xerrno));
errno = xerrno;
}
} else {
res = 0;
}
break;
case PROXY_REVERSE_CONNECT_POLICY_PER_GROUP:
if (!(opts & PROXY_OPT_USE_REVERSE_PROXY_AUTH)) {
pr_log_pri(PR_LOG_NOTICE, MOD_PROXY_VERSION
": PerGroup ProxyReverseConnectPolicy requires the "
"UseReverseProxyAuth ProxyOption");
errno = EPERM;
res = -1;
} else {
res = 0;
}
break;
default:
errno = EINVAL;
res = -1;
break;
}
return res;
}
static const struct proxy_conn *reverse_db_policy_next_backend(pool *p,
void *dbh, int policy_id, unsigned int vhost_id,
array_header *default_backends, const void *policy_data, int *backend_id) {
const struct proxy_conn *pconn = NULL;
struct proxy_conn **conns = NULL;
int idx = -1, nelts = 0;
if (db_backends != NULL) {
conns = db_backends->elts;
nelts = db_backends->nelts;
}
if (proxy_reverse_policy_is_sticky(policy_id) != TRUE) {
if (conns == NULL &&
db_backends == NULL) {
if (default_backends != NULL) {
conns = default_backends->elts;
nelts = default_backends->nelts;
} else {
/* Prevent possible null pointer dereferences later due to missing
* default URIs for non-sticky ConnectPolicy configurations.
*/
pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"missing required default/global ProxyReverseServers");
errno = EPERM;
return NULL;
}
}
}
switch (policy_id) {
case PROXY_REVERSE_CONNECT_POLICY_RANDOM:
idx = (int) proxy_random_next(0, nelts-1);
if (idx >= 0) {
pr_trace_msg(trace_channel, 11, "%s policy: selected index %d of %u",
proxy_reverse_policy_name(policy_id), idx, nelts-1);
pconn = conns[idx];
}
break;
case PROXY_REVERSE_CONNECT_POLICY_ROUND_ROBIN:
idx = reverse_db_roundrobin_next(p, dbh, vhost_id);
if (idx >= 0) {
pr_trace_msg(trace_channel, 11, "%s policy: selected index %d of %u",
proxy_reverse_policy_name(policy_id), idx, nelts-1);
pconn = conns[idx];
}
break;
case PROXY_REVERSE_CONNECT_POLICY_SHUFFLE:
idx = reverse_db_shuffle_next(p, dbh, vhost_id);
if (idx >= 0) {
pr_trace_msg(trace_channel, 11, "%s policy: selected index %d of %u",
proxy_reverse_policy_name(policy_id), idx, nelts-1);
pconn = conns[idx];
}
break;
case PROXY_REVERSE_CONNECT_POLICY_LEAST_CONNS:
idx = reverse_db_leastconns_next(p, dbh, vhost_id);
if (idx >= 0) {
pr_trace_msg(trace_channel, 11, "%s policy: selected index %d of %u",
proxy_reverse_policy_name(policy_id), idx, nelts-1);
pconn = conns[idx];
}
break;
case PROXY_REVERSE_CONNECT_POLICY_LEAST_RESPONSE_TIME:
idx = reverse_db_leastresponsetime_next(p, dbh, vhost_id);
if (idx >= 0) {
pr_trace_msg(trace_channel, 11, "%s policy: selected index %d of %u",
proxy_reverse_policy_name(policy_id), idx, nelts-1);
pconn = conns[idx];
}
break;
case PROXY_REVERSE_CONNECT_POLICY_PER_USER:
pconn = reverse_db_peruser_next(p, dbh, vhost_id, policy_data);
if (pconn != NULL) {
pr_trace_msg(trace_channel, 11,
"%s policy: selected backend '%.100s' for user '%s'",
proxy_reverse_policy_name(policy_id), proxy_conn_get_uri(pconn),
(const char *) policy_data);
}
break;
case PROXY_REVERSE_CONNECT_POLICY_PER_GROUP:
pconn = reverse_db_pergroup_next(p, dbh, vhost_id, policy_data);
if (pconn != NULL) {
pr_trace_msg(trace_channel, 11,
"%s policy: selected backend '%.100s' for user '%s'",
proxy_reverse_policy_name(policy_id), proxy_conn_get_uri(pconn),
(const char *) policy_data);
}
break;
case PROXY_REVERSE_CONNECT_POLICY_PER_HOST:
pconn = reverse_db_perhost_next(p, dbh, vhost_id, session.c->remote_addr);
if (pconn != NULL) {
pr_trace_msg(trace_channel, 11,
"%s policy: selected backend '%.100s' for host '%s'",
proxy_reverse_policy_name(policy_id), proxy_conn_get_uri(pconn),
pr_netaddr_get_ipstr(session.c->remote_addr));
}
break;
default:
errno = ENOSYS;
return NULL;
}
if (backend_id != NULL) {
*backend_id = idx;
}
return pconn;
}
static int reverse_db_policy_update_backend(pool *p, void *dbh, int policy_id,
unsigned vhost_id, int backend_id, int conn_incr, long connect_ms) {
/* Increment the conn count for this backend ID. */
int res, idx = 1;
const char *stmt, *errstr = NULL;
array_header *results;
/* If our ReverseConnectPolicy is one of PerUser, PerGroup, or PerHost,
* we can skip this step: those policies do not use the connection count/time.
* This also helps avoid database contention under load for these policies.
*/
if (proxy_reverse_policy_is_sticky(policy_id) == TRUE) {
pr_trace_msg(trace_channel, 17,
"sticky policy %s does not require updates, skipping",
proxy_reverse_policy_name(policy_id));
return 0;
}
/* TODO: Right now, we simply overwrite/track the very latest connect ms.
* But this could unfairly skew policies such as LeastResponseTime, as when
* the server in question had higher latency for that particular connection,
* due to e.g. OCSP response cache expiration.
*
* Another way would to be average the given connect ms with the previous
* one (if present), and store that. Something to ponder for the future.
*/
if (connect_ms > 0) {
stmt = "UPDATE proxy_vhost_backends SET conn_count = conn_count + ?, connect_ms = ? WHERE vhost_id = ? AND backend_id = ?;";
} else {
stmt = "UPDATE proxy_vhost_backends SET conn_count = conn_count + ? WHERE vhost_id = ? AND backend_id = ?;";
}
res = proxy_db_prepare_stmt(p, dbh, stmt);
if (res < 0) {
return -1;
}
res = proxy_db_bind_stmt(p, dbh, stmt, idx, PROXY_DB_BIND_TYPE_INT,
(void *) &conn_incr, 0);
if (res < 0) {
return -1;
}
idx++;
if (connect_ms > 0) {
res = proxy_db_bind_stmt(p, dbh, stmt, idx, PROXY_DB_BIND_TYPE_LONG,
(void *) &connect_ms, 0);
if (res < 0) {
return -1;
}
idx++;
}
res = proxy_db_bind_stmt(p, dbh, stmt, idx, PROXY_DB_BIND_TYPE_INT,
(void *) &vhost_id, 0);
if (res < 0) {
return -1;
}
idx++;
res = proxy_db_bind_stmt(p, dbh, stmt, idx, PROXY_DB_BIND_TYPE_INT,
(void *) &backend_id, 0);
if (res < 0) {
return -1;
}
results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr);
if (results == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr ? errstr : strerror(errno));
errno = EPERM;
return -1;
}
return 0;
}
static int reverse_db_policy_used_backend(pool *p, void *dbh, int policy_id,
unsigned int vhost_id, int idx) {
int res;
switch (policy_id) {
case PROXY_REVERSE_CONNECT_POLICY_RANDOM:
res = 0;
break;
case PROXY_REVERSE_CONNECT_POLICY_ROUND_ROBIN:
res = reverse_db_roundrobin_used(p, dbh, vhost_id, idx);
break;
case PROXY_REVERSE_CONNECT_POLICY_SHUFFLE:
res = reverse_db_shuffle_used(p, dbh, vhost_id, idx);
break;
case PROXY_REVERSE_CONNECT_POLICY_LEAST_CONNS:
res = reverse_db_leastconns_used(p, dbh, vhost_id, idx);
break;
case PROXY_REVERSE_CONNECT_POLICY_LEAST_RESPONSE_TIME:
res = reverse_db_leastresponsetime_used(p, dbh, vhost_id, idx);
break;
case PROXY_REVERSE_CONNECT_POLICY_PER_USER:
res = reverse_db_peruser_used(p, dbh, vhost_id, idx);
break;
case PROXY_REVERSE_CONNECT_POLICY_PER_GROUP:
res = reverse_db_pergroup_used(p, dbh, vhost_id, idx);
break;
case PROXY_REVERSE_CONNECT_POLICY_PER_HOST:
res = reverse_db_perhost_used(p, dbh, vhost_id, idx);
break;
default:
errno = ENOSYS;
return -1;
}
if (res < 0) {
int xerrno = errno;
errno = xerrno;
return -1;
}
return 0;
}
static void *reverse_db_init(pool *p, const char *tables_path, int flags) {
int db_flags, res, xerrno = 0;
const char *db_path = NULL;
server_rec *s;
struct proxy_dbh *dbh;
if (tables_path == NULL) {
errno = EINVAL;
return NULL;
}
db_path = pdircat(p, tables_path, "proxy-reverse.db", NULL);
db_flags = PROXY_DB_OPEN_FL_SCHEMA_VERSION_CHECK|PROXY_DB_OPEN_FL_INTEGRITY_CHECK|PROXY_DB_OPEN_FL_VACUUM;
if (flags & PROXY_DB_OPEN_FL_SKIP_VACUUM) {
/* If the caller needs us to skip the vacuum, we will. */
db_flags &= ~PROXY_DB_OPEN_FL_VACUUM;
}
PRIVS_ROOT
dbh = proxy_db_open_with_version(p, db_path, PROXY_REVERSE_DB_SCHEMA_NAME,
PROXY_REVERSE_DB_SCHEMA_VERSION, db_flags);
xerrno = errno;
PRIVS_RELINQUISH
if (dbh == NULL) {
(void) pr_log_pri(PR_LOG_NOTICE, MOD_PROXY_VERSION
": error opening database '%s' for schema '%s', version %u: %s",
db_path, PROXY_REVERSE_DB_SCHEMA_NAME, PROXY_REVERSE_DB_SCHEMA_VERSION,
strerror(xerrno));
errno = xerrno;
return NULL;
}
res = reverse_db_add_schema(p, dbh, db_path);
if (res < 0) {
xerrno = errno;
(void) pr_log_debug(DEBUG0, MOD_PROXY_VERSION
": error creating schema in database '%s' for '%s': %s", db_path,
PROXY_REVERSE_DB_SCHEMA_NAME, strerror(xerrno));
(void) proxy_db_close(p, dbh);
errno = xerrno;
return NULL;
}
res = reverse_db_truncate_tables(p, dbh);
if (res < 0) {
xerrno = errno;
(void) proxy_db_close(p, dbh);
errno = xerrno;
return NULL;
}
for (s = (server_rec *) server_list->xas_list; s; s = s->next) {
config_rec *c;
array_header *backends = NULL;
res = reverse_db_add_vhost(p, dbh, s);
if (res < 0) {
xerrno = errno;
(void) pr_log_debug(DEBUG0, MOD_PROXY_VERSION
": error adding database entry for server '%s' in schema '%s': %s",
s->ServerName, PROXY_REVERSE_DB_SCHEMA_NAME, strerror(xerrno));
(void) proxy_db_close(p, dbh);
errno = xerrno;
return NULL;
}
c = find_config(s->conf, CONF_PARAM, "ProxyReverseServers", FALSE);
while (c != NULL) {
const char *uri;
pr_signals_handle();
uri = c->argv[1];
if (uri != NULL) {
int defer = FALSE;
/* Handling of sql:// URIs is done later, in the session init
* call, assuming we've connected to a SQL server.
*/
if (strncmp(uri, "sql:/", 5) == 0) {
defer = TRUE;
}
/* Skip any %U- or %g-bearing URIs. */
if (defer == FALSE &&
(strstr(uri, "%U") != NULL ||
strstr(uri, "%g") != NULL)) {
defer = TRUE;
}
if (defer) {
c = find_config_next(c, c->next, CONF_PARAM, "ProxyReverseServers",
FALSE);
continue;
}
}
if (backends == NULL) {
backends = c->argv[0];
} else {
array_cat(backends, c->argv[0]);
}
c = find_config_next(c, c->next, CONF_PARAM, "ProxyReverseServers",
FALSE);
}
/* What if ALL of the ProxyReverseServers are deferred? In that case, we
* have no backend servers to add at this time.
*/
if (backends != NULL) {
res = reverse_db_add_backends(p, dbh, s->sid, backends);
if (res < 0) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error adding database entries for ProxyReverseServers: %s",
strerror(xerrno));
(void) proxy_db_close(p, dbh);
errno = xerrno;
return NULL;
}
}
}
return dbh;
}
static int reverse_db_close(pool *p, void *dbh) {
if (p == NULL) {
errno = EINVAL;
return -1;
}
/* TODO: Implement any necessary cleanup */
if (dbh != NULL) {
if (proxy_db_close(p, dbh) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error detaching database with schema '%s': %s",
PROXY_REVERSE_DB_SCHEMA_NAME, strerror(errno));
}
}
return 0;
}
static void *reverse_db_open(pool *p, const char *tables_path,
array_header *backends) {
int xerrno = 0;
struct proxy_dbh *dbh;
const char *db_path;
db_path = pdircat(p, tables_path, "proxy-reverse.db", NULL);
/* Make sure we have our own per-session database handle, per SQLite3
* recommendation.
*/
PRIVS_ROOT
dbh = proxy_db_open_with_version(p, db_path, PROXY_REVERSE_DB_SCHEMA_NAME,
PROXY_REVERSE_DB_SCHEMA_VERSION, 0);
xerrno = errno;
PRIVS_RELINQUISH
if (dbh == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error opening database '%s' for schema '%s', version %u: %s",
db_path, PROXY_REVERSE_DB_SCHEMA_NAME, PROXY_REVERSE_DB_SCHEMA_VERSION,
strerror(xerrno));
errno = xerrno;
return NULL;
}
db_backends = backends;
return dbh;
}
int proxy_reverse_db_as_datastore(struct proxy_reverse_datastore *ds,
void *ds_data, size_t ds_datasz) {
if (ds == NULL) {
errno = EINVAL;
return -1;
}
(void) ds_data;
(void) ds_datasz;
ds->policy_init = reverse_db_policy_init;
ds->policy_next_backend = reverse_db_policy_next_backend;
ds->policy_used_backend = reverse_db_policy_used_backend;
ds->policy_update_backend = reverse_db_policy_update_backend;
ds->init = reverse_db_init;
ds->open = reverse_db_open;
ds->close = reverse_db_close;
return 0;
}
proftpd-mod_proxy-0.9.5/lib/proxy/reverse/redis.c 0000664 0000000 0000000 00000077325 14757370167 0022134 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy reverse datastore implementation
* Copyright (c) 2012-2020 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "redis.h"
#include "proxy/conn.h"
#include "proxy/reverse.h"
#include "proxy/reverse/redis.h"
#include "proxy/random.h"
#include "proxy/tls.h"
#include "proxy/ftp/ctrl.h"
/* PerHost/PerUser/PerGroup table limits */
#define PROXY_REVERSE_REDIS_PERHOST_MAX_ENTRIES 8192
#define PROXY_REVERSE_REDIS_PERUSER_MAX_ENTRIES 8192
#define PROXY_REVERSE_REDIS_PERGROUP_MAX_ENTRIES 8192
static array_header *redis_backends = NULL;
static const char *trace_channel = "proxy.reverse.redis";
static void *redis_prefix = NULL;
static size_t redis_prefixsz = 0;
static char *make_key(pool *p, const char *policy, unsigned int vhost_id,
const char *name) {
char *key;
size_t keysz;
/* It's 21 characters for "proxy_reverse:" and ":vhost#", and one for the
* trailing NUL. Allocate enough room for a large vhost ID, e.g.
* optimistically in the thousands.
*/
keysz = 22 + 6 + strlen(policy);
if (name != NULL) {
keysz += strlen(name) + 1;
}
key = pcalloc(p, keysz + 1);
if (name == NULL) {
snprintf(key, keysz, "proxy_reverse:%s:vhost#%u", policy, vhost_id);
} else {
snprintf(key, keysz, "proxy_reverse:%s:vhost#%u:%s", policy, vhost_id,
name);
}
return key;
}
static unsigned int str2hash(const void *key, size_t keysz) {
unsigned int i = 0;
size_t sz = !keysz ? strlen((const char *) key) : keysz;
while (sz--) {
const char *k = key;
unsigned int c;
pr_signals_handle();
c = k[sz];
i = (i * 33) + c;
}
return i;
}
/* Given an index into the array_header of backend pconns, return the URI
* for the indexed conn.
*/
static const char *backend_uri_by_idx(int idx) {
const struct proxy_conn *pconn;
if (redis_backends == NULL) {
errno = EPERM;
return NULL;
}
if (idx < 0) {
errno = EPERM;
return NULL;
}
pconn = ((struct proxy_conn **) redis_backends->elts)[idx];
return proxy_conn_get_uri(pconn);
}
/* Redis List helpers */
static array_header *redis_get_list_backend_uris(pool *p,
pr_redis_t *redis, const char *policy, unsigned int vhost_id,
const char *name) {
int res;
pool *tmp_pool;
char *key;
array_header *backend_uris, *values = NULL, *valueszs = NULL;
tmp_pool = make_sub_pool(p);
key = make_key(tmp_pool, policy, vhost_id, name);
res = pr_redis_list_getall(tmp_pool, redis, &proxy_module, key, &values,
&valueszs);
if (res < 0) {
int xerrno = errno;
pr_trace_msg(trace_channel, 3,
"error retrieving %s Redis entries using key '%s': %s", policy, key,
strerror(xerrno));
destroy_pool(tmp_pool);
errno = xerrno;
return NULL;
}
backend_uris = copy_array_str(p, values);
destroy_pool(tmp_pool);
return backend_uris;
}
static int redis_set_list_backends(pool *p, pr_redis_t *redis,
const char *policy, unsigned int vhost_id, const char *name,
array_header *backends) {
register unsigned int i;
int res = 0, xerrno;
pool *tmp_pool;
char *key;
array_header *backend_uris, *backend_uriszs;
tmp_pool = make_sub_pool(p);
key = make_key(tmp_pool, policy, vhost_id, name);
backend_uris = make_array(tmp_pool, 0, sizeof(char *));
backend_uriszs = make_array(tmp_pool, 0, sizeof(size_t));
for (i = 0; i < backends->nelts; i++) {
struct proxy_conn *pconn;
const char *backend_uri;
size_t backend_urisz;
pconn = ((struct proxy_conn **) backends->elts)[i];
backend_uri = proxy_conn_get_uri(pconn);
*((char **) push_array(backend_uris)) = pstrdup(tmp_pool, backend_uri);
backend_urisz = strlen(backend_uri);
*((size_t *) push_array(backend_uriszs)) = backend_urisz;
pr_trace_msg(trace_channel, 19, "adding %s list backend #%u: '%.*s'",
policy, i+1, (int) backend_urisz, backend_uri);
}
res = pr_redis_list_setall(redis, &proxy_module, key, backend_uris,
backend_uriszs);
xerrno = errno;
if (res < 0) {
pr_trace_msg(trace_channel, 6,
"error adding %s Redis entries: %s", policy, strerror(xerrno));
}
destroy_pool(tmp_pool);
errno = xerrno;
return res;
}
/* Redis Sorted Set helpers */
static int redis_set_sorted_set_backends(pool *p, pr_redis_t *redis,
const char *policy, unsigned int vhost_id, array_header *backends,
float init_score) {
register unsigned int i;
int res = 0, xerrno;
pool *tmp_pool;
char *key;
array_header *backend_uris, *backend_uriszs, *backend_scores;
tmp_pool = make_sub_pool(p);
key = make_key(tmp_pool, policy, vhost_id, NULL);
backend_uris = make_array(tmp_pool, 0, sizeof(char *));
backend_uriszs = make_array(tmp_pool, 0, sizeof(size_t));
backend_scores = make_array(tmp_pool, 0, sizeof(float));
for (i = 0; i < backends->nelts; i++) {
struct proxy_conn *pconn;
const char *backend_uri;
size_t backend_urisz;
pconn = ((struct proxy_conn **) backends->elts)[i];
backend_uri = proxy_conn_get_uri(pconn);
*((char **) push_array(backend_uris)) = pstrdup(tmp_pool, backend_uri);
backend_urisz = strlen(backend_uri);
*((size_t *) push_array(backend_uriszs)) = backend_urisz;
*((float *) push_array(backend_scores)) = init_score;
pr_trace_msg(trace_channel, 19,
"adding %s sorted set backend #%u: '%.*s' (%0.3f)", policy, i+1,
(int) backend_urisz, backend_uri, init_score);
}
res = pr_redis_sorted_set_setall(redis, &proxy_module, key, backend_uris,
backend_uriszs, backend_scores);
xerrno = errno;
if (res < 0) {
pr_trace_msg(trace_channel, 6,
"error adding %s Redis entries: %s", policy, strerror(xerrno));
}
destroy_pool(tmp_pool);
errno = xerrno;
return res;
}
/* ProxyReverseConnectPolicy: Shuffle */
/* The implementation of shuffling here requires two Redis lists, the A and B
* lists. URIs are consumed (via random selection) from the A list and added
* to the B list, until the A list is empty. At which point, the B list is
* renamed to the A list, and we start again.
*/
static int reverse_redis_shuffle_init(pool *p, pr_redis_t *redis,
unsigned int vhost_id, array_header *backends) {
return redis_set_list_backends(p, redis, "Shuffle", vhost_id, "A", backends);
}
static long reverse_redis_shuffle_next(pool *p, pr_redis_t *redis,
unsigned int vhost_id) {
int res, xerrno;
pool *tmp_pool;
char *akey, *bkey;
const char *val;
size_t valsz;
uint64_t count = 0;
long idx;
tmp_pool = make_sub_pool(p);
akey = make_key(tmp_pool, "Shuffle", vhost_id, "A");
res = pr_redis_list_count(redis, &proxy_module, akey, &count);
xerrno = errno;
if (res < 0) {
if (xerrno != ENOENT) {
pr_trace_msg(trace_channel, 6,
"error getting count of Redis list '%s': %s", akey, strerror(xerrno));
destroy_pool(tmp_pool);
errno = xerrno;
return -1;
}
count = 0;
}
if (count == 0) {
res = reverse_redis_shuffle_init(p, redis, vhost_id, redis_backends);
xerrno = errno;
if (res < 0) {
destroy_pool(tmp_pool);
errno = xerrno;
return -1;
}
count = redis_backends->nelts;
}
idx = proxy_random_next(0, count-1);
/* XXX Now we want to remove that backend URI from the A list, and add it to
* the B list.
*/
val = backend_uri_by_idx((int) idx);
xerrno = errno;
if (val == NULL) {
destroy_pool(tmp_pool);
errno = xerrno;
return -1;
}
valsz = strlen(val);
res = pr_redis_list_delete(redis, &proxy_module, akey, (void *) val, valsz);
xerrno = errno;
if (res < 0) {
destroy_pool(tmp_pool);
errno = xerrno;
return -1;
}
bkey = make_key(tmp_pool, "Shuffle", vhost_id, "B");
res = pr_redis_list_append(redis, &proxy_module, bkey, (void *) val, valsz);
xerrno = errno;
if (res < 0) {
destroy_pool(tmp_pool);
errno = xerrno;
return -1;
}
/* If count is one, it means we just removed the last backend from the A
* list. Thus rename the B list to be the A list.
*/
if (count == 1) {
res = pr_redis_rename(redis, &proxy_module, bkey, akey);
xerrno = errno;
if (res < 0) {
pr_trace_msg(trace_channel, 3,
"error renaming Shuffle key '%s' to '%s': %s", bkey, akey,
strerror(xerrno));
idx = -1;
}
}
destroy_pool(tmp_pool);
errno = xerrno;
return idx;
}
static int reverse_redis_shuffle_used(pool *p, pr_redis_t *redis,
unsigned int vhost_id, int backend_idx) {
/* Nothing to do here. */
return 0;
}
/* ProxyReverseConnectPolicy: RoundRobin */
static int reverse_redis_roundrobin_init(pool *p, pr_redis_t *redis,
unsigned int vhost_id, array_header *backends) {
return redis_set_list_backends(p, redis, "RoundRobin", vhost_id, NULL,
backends);
}
static const struct proxy_conn *reverse_redis_roundrobin_next(pool *p,
pr_redis_t *redis, unsigned int vhost_id) {
int res, xerrno;
pool *tmp_pool;
char *key, *backend_uri = NULL;
size_t backend_urisz = 0;
const struct proxy_conn *pconn;
tmp_pool = make_sub_pool(p);
key = make_key(tmp_pool, "RoundRobin", vhost_id, NULL);
res = pr_redis_list_rotate(tmp_pool, redis, &proxy_module, key,
(void **) &backend_uri, &backend_urisz);
xerrno = errno;
if (res < 0) {
pr_trace_msg(trace_channel, 3,
"error rotating RoundRobin Redis list using key '%s': %s", key,
strerror(xerrno));
destroy_pool(tmp_pool);
errno = xerrno;
return NULL;
}
pconn = proxy_conn_create(p, pstrndup(tmp_pool, backend_uri, backend_urisz),
0);
xerrno = errno;
if (pconn == NULL) {
pr_trace_msg(trace_channel, 3,
"error creating proxy connection from URI '%.*s': %s",
(int) backend_urisz, backend_uri, strerror(xerrno));
}
destroy_pool(tmp_pool);
errno = xerrno;
return pconn;
}
static int reverse_redis_roundrobin_used(pool *p, pr_redis_t *redis,
unsigned int vhost_id, int backend_idx) {
/* Nothing to do here. */
return 0;
}
/* ProxyReverseConnectPolicy: LeastConns */
static int reverse_redis_leastconns_init(pool *p, pr_redis_t *redis,
unsigned int vhost_id, array_header *backends) {
return redis_set_sorted_set_backends(p, redis, "LeastConns", vhost_id,
backends, 0.0);
}
static const struct proxy_conn *reverse_redis_leastconns_next(pool *p,
pr_redis_t *redis, unsigned int vhost_id) {
int res, xerrno;
pool *tmp_pool;
char *key;
array_header *vals = NULL, *valszs = NULL;
const struct proxy_conn *pconn = NULL;
tmp_pool = make_sub_pool(p);
key = make_key(tmp_pool, "LeastConns", vhost_id, NULL);
res = pr_redis_sorted_set_getn(tmp_pool, redis, &proxy_module, key, 0, 1,
&vals, &valszs, PR_REDIS_SORTED_SET_FL_ASC);
xerrno = errno;
if (res == 0) {
char *backend_uri;
backend_uri = ((char **) vals->elts)[0];
pconn = proxy_conn_create(p, backend_uri, 0);
}
destroy_pool(tmp_pool);
errno = xerrno;
return pconn;
}
static int reverse_redis_leastconns_used(pool *p, pr_redis_t *redis,
unsigned int vhost_id, int backend_idx) {
/* Nothing to do here. */
return 0;
}
static int reverse_redis_leastconns_update(pool *p, pr_redis_t *redis,
unsigned int vhost_id, int backend_idx, int conn_incr, long connect_ms) {
int res, xerrno;
pool *tmp_pool;
char *key;
const char *val;
float score;
size_t valsz;
val = backend_uri_by_idx(backend_idx);
if (val == NULL) {
return -1;
}
valsz = strlen(val);
tmp_pool = make_sub_pool(p);
key = make_key(tmp_pool, "LeastConns", vhost_id, NULL);
score = (float) conn_incr;
res = pr_redis_sorted_set_set(redis, &proxy_module, key, (void *) val, valsz,
score);
xerrno = errno;
destroy_pool(tmp_pool);
errno = xerrno;
return res;
}
/* ProxyReverseConnectPolicy: LeastResponseTime */
/* Note: "least response time" is determined by calculating the following
* for each backend server:
*
* N = connection count * connect time (ms)
*
* and choosing the backend with the lowest value for N. If there are no
* backend servers with connect time values, choose the one with the lowest
* connection count.
*/
static int reverse_redis_leastresponsetime_init(pool *p, pr_redis_t *redis,
unsigned int vhost_id, array_header *backends) {
return redis_set_sorted_set_backends(p, redis, "LeastResponseTime", vhost_id,
backends, 0.0);
}
static const struct proxy_conn *reverse_redis_leastresponsetime_next(pool *p,
pr_redis_t *redis, unsigned int vhost_id) {
int res, xerrno;
pool *tmp_pool;
char *key;
array_header *vals = NULL, *valszs = NULL;
const struct proxy_conn *pconn = NULL;
tmp_pool = make_sub_pool(p);
key = make_key(tmp_pool, "LeastResponseTime", vhost_id, NULL);
res = pr_redis_sorted_set_getn(tmp_pool, redis, &proxy_module, key, 0, 1,
&vals, &valszs, PR_REDIS_SORTED_SET_FL_ASC);
xerrno = errno;
if (res == 0) {
char *backend_uri;
backend_uri = ((char **) vals->elts)[0];
pconn = proxy_conn_create(p, backend_uri, 0);
}
destroy_pool(tmp_pool);
errno = xerrno;
return pconn;
}
static int reverse_redis_leastresponsetime_used(pool *p, pr_redis_t *redis,
unsigned int vhost_id, int backend_idx) {
/* TODO: anything to do here? */
return 0;
}
static int reverse_redis_leastresponsetime_update(pool *p, pr_redis_t *redis,
unsigned int vhost_id, int backend_idx, int conn_incr, long connect_ms) {
int res, xerrno;
pool *tmp_pool;
char *key;
const char *val;
float score;
size_t valsz;
val = backend_uri_by_idx(backend_idx);
if (val == NULL) {
return -1;
}
valsz = strlen(val);
tmp_pool = make_sub_pool(p);
key = make_key(tmp_pool, "LeastResponseTime", vhost_id, NULL);
score = (float) conn_incr;
if (connect_ms > 0) {
score *= (float) connect_ms;
}
res = pr_redis_sorted_set_set(redis, &proxy_module, key, (void *) val, valsz,
score);
xerrno = errno;
destroy_pool(tmp_pool);
errno = xerrno;
return res;
}
/* ProxyReverseConnectPolicy: PerUser */
static array_header *reverse_redis_peruser_get(pool *p, pr_redis_t *redis,
unsigned int vhost_id, const char *user) {
return redis_get_list_backend_uris(p, redis, "PerUser", vhost_id, user);
}
static const struct proxy_conn *reverse_redis_peruser_init(pool *p,
pr_redis_t *redis, unsigned int vhost_id, const char *user) {
int res;
const struct proxy_conn *pconn = NULL;
struct proxy_conn **conns = NULL;
unsigned int backend_count;
array_header *backends;
backends = proxy_reverse_pername_backends(p, user, TRUE);
if (backends == NULL) {
return NULL;
}
/* Store these backends for later use. */
res = redis_set_list_backends(p, redis, "PerUser", vhost_id, user, backends);
if (res < 0) {
return NULL;
}
backend_count = backends->nelts;
conns = backends->elts;
if (backend_count == 1) {
pconn = conns[0];
} else {
size_t user_len;
unsigned int h;
int idx;
user_len = strlen(user);
h = str2hash(user, user_len);
idx = h % backend_count;
pconn = conns[idx];
}
return pconn;
}
static const struct proxy_conn *reverse_redis_peruser_next(pool *p,
pr_redis_t *redis, unsigned int vhost_id, const char *user) {
array_header *backend_uris;
const struct proxy_conn *pconn = NULL;
pconn = reverse_redis_peruser_init(p, redis, vhost_id, user);
if (pconn == NULL &&
errno != ENOENT) {
backend_uris = reverse_redis_peruser_get(p, redis, vhost_id, user);
if (backend_uris != NULL) {
char **vals;
vals = backend_uris->elts;
pconn = proxy_conn_create(p, vals[0], 0);
}
}
if (pconn != NULL) {
return pconn;
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error preparing PerUser Redis entries for user '%s': %s", user,
strerror(ENOENT));
errno = EPERM;
return NULL;
}
static int reverse_redis_peruser_used(pool *p, pr_redis_t *redis,
unsigned int vhost_id, int backend_idx) {
/* Nothing to do here. */
return 0;
}
/* ProxyReverseConnectPolicy: PerGroup */
static array_header *reverse_redis_pergroup_get(pool *p, pr_redis_t *redis,
unsigned int vhost_id, const char *group) {
return redis_get_list_backend_uris(p, redis, "PerGroup", vhost_id, group);
}
static const struct proxy_conn *reverse_redis_pergroup_init(pool *p,
pr_redis_t *redis, unsigned int vhost_id, const char *group) {
int res;
const struct proxy_conn *pconn = NULL;
struct proxy_conn **conns = NULL;
unsigned int backend_count;
array_header *backends;
backends = proxy_reverse_pername_backends(p, group, FALSE);
if (backends == NULL) {
return NULL;
}
/* Store these backends for later use. */
res = redis_set_list_backends(p, redis, "PerGroup", vhost_id, group,
backends);
if (res < 0) {
return NULL;
}
backend_count = backends->nelts;
conns = backends->elts;
if (backend_count == 1) {
pconn = conns[0];
} else {
size_t group_len;
unsigned int h;
int idx;
group_len = strlen(group);
h = str2hash(group, group_len);
idx = h % backend_count;
pconn = conns[idx];
}
return pconn;
}
static const struct proxy_conn *reverse_redis_pergroup_next(pool *p,
pr_redis_t *redis, unsigned int vhost_id, const char *group) {
array_header *backend_uris;
const struct proxy_conn *pconn = NULL;
pconn = reverse_redis_pergroup_init(p, redis, vhost_id, group);
if (pconn == NULL &&
errno != ENOENT) {
backend_uris = reverse_redis_pergroup_get(p, redis, vhost_id, group);
if (backend_uris != NULL) {
char **vals;
vals = backend_uris->elts;
pconn = proxy_conn_create(p, vals[0], 0);
}
}
if (pconn != NULL) {
return pconn;
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error preparing PerGroup Redis entries for group '%s': %s", group,
strerror(ENOENT));
errno = EPERM;
return NULL;
}
static int reverse_redis_pergroup_used(pool *p, pr_redis_t *redis,
unsigned int vhost_id, int backend_idx) {
/* Nothing to do here. */
return 0;
}
/* ProxyReverseConnectPolicy: PerHost */
static array_header *reverse_redis_perhost_get(pool *p, pr_redis_t *redis,
unsigned int vhost_id, const pr_netaddr_t *addr) {
return redis_get_list_backend_uris(p, redis, "PerHost", vhost_id,
pr_netaddr_get_ipstr(addr));
}
static const struct proxy_conn *reverse_redis_perhost_init(pool *p,
pr_redis_t *redis, unsigned int vhost_id, array_header *backends,
const pr_netaddr_t *addr) {
int res;
const struct proxy_conn *pconn = NULL;
struct proxy_conn **conns;
const char *ip;
ip = pr_netaddr_get_ipstr(addr);
/* Store these backends for later use. */
res = redis_set_list_backends(p, redis, "PerHost", vhost_id, ip, backends);
if (res < 0) {
return NULL;
}
conns = backends->elts;
if (backends->nelts == 1) {
pconn = conns[0];
} else {
size_t iplen;
unsigned int h;
int idx;
iplen = strlen(ip);
h = str2hash(ip, iplen);
idx = h % backends->nelts;
pconn = conns[idx];
}
return pconn;
}
static const struct proxy_conn *reverse_redis_perhost_next(pool *p,
pr_redis_t *redis, unsigned int vhost_id, const pr_netaddr_t *addr) {
array_header *backend_uris;
const struct proxy_conn *pconn = NULL;
backend_uris = reverse_redis_perhost_get(p, redis, vhost_id, addr);
if (backend_uris == NULL &&
errno == ENOENT) {
/* This can happen the very first time; perform an on-demand discovery
* of the backends for this host, and try again.
*/
pconn = reverse_redis_perhost_init(p, redis, vhost_id, redis_backends,
addr);
if (pconn == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error preparing PerHost Redis entries for host '%s': %s",
pr_netaddr_get_ipstr(addr), strerror(errno));
errno = EPERM;
return NULL;
}
} else {
char **vals;
vals = backend_uris->elts;
pconn = proxy_conn_create(p, vals[0], 0);
}
return pconn;
}
static int reverse_redis_perhost_used(pool *p, pr_redis_t *redis,
unsigned int vhost_id, int backend_idx) {
/* Nothing to do here. */
return 0;
}
/* ProxyReverseServers API/handling */
static int reverse_redis_policy_init(pool *p, void *redis, int policy_id,
unsigned int vhost_id, array_header *backends, unsigned long opts) {
int res = 0, xerrno;
switch (policy_id) {
case PROXY_REVERSE_CONNECT_POLICY_RANDOM:
case PROXY_REVERSE_CONNECT_POLICY_PER_USER:
case PROXY_REVERSE_CONNECT_POLICY_PER_HOST:
/* No preparation needed at this time. */
break;
case PROXY_REVERSE_CONNECT_POLICY_ROUND_ROBIN:
if (backends != NULL) {
res = reverse_redis_roundrobin_init(p, redis, vhost_id, backends);
if (res < 0) {
xerrno = errno;
pr_log_debug(DEBUG3, MOD_PROXY_VERSION
": error preparing %s Redis entries: %s",
proxy_reverse_policy_name(policy_id), strerror(xerrno));
errno = xerrno;
}
}
break;
case PROXY_REVERSE_CONNECT_POLICY_SHUFFLE:
if (backends != NULL) {
res = reverse_redis_shuffle_init(p, redis, vhost_id, backends);
if (res < 0) {
xerrno = errno;
pr_log_debug(DEBUG3, MOD_PROXY_VERSION
": error preparing %s Redis entries: %s",
proxy_reverse_policy_name(policy_id), strerror(xerrno));
errno = xerrno;
}
}
break;
case PROXY_REVERSE_CONNECT_POLICY_LEAST_CONNS:
if (backends != NULL) {
res = reverse_redis_leastconns_init(p, redis, vhost_id, backends);
if (res < 0) {
xerrno = errno;
pr_log_debug(DEBUG3, MOD_PROXY_VERSION
": error preparing %s Redis entries: %s",
proxy_reverse_policy_name(policy_id), strerror(xerrno));
errno = xerrno;
}
}
break;
case PROXY_REVERSE_CONNECT_POLICY_LEAST_RESPONSE_TIME:
if (backends != NULL) {
res = reverse_redis_leastresponsetime_init(p, redis, vhost_id,
backends);
if (res < 0) {
xerrno = errno;
pr_log_debug(DEBUG3, MOD_PROXY_VERSION
": error preparing %s Redis entries: %s",
proxy_reverse_policy_name(policy_id), strerror(xerrno));
errno = xerrno;
}
}
break;
case PROXY_REVERSE_CONNECT_POLICY_PER_GROUP:
if (!(opts & PROXY_OPT_USE_REVERSE_PROXY_AUTH)) {
pr_log_pri(PR_LOG_NOTICE, MOD_PROXY_VERSION
": PerGroup ProxyReverseConnectPolicy requires the "
"UseReverseProxyAuth ProxyOption");
errno = EPERM;
res = -1;
}
break;
default:
errno = EINVAL;
res = -1;
break;
}
return res;
}
static const struct proxy_conn *reverse_redis_policy_next_backend(pool *p,
void *redis, int policy_id, unsigned int vhost_id,
array_header *default_backends, const void *policy_data, int *backend_id) {
const struct proxy_conn *pconn = NULL;
struct proxy_conn **conns = NULL;
int idx = -1, nelts = 0;
if (redis_backends != NULL) {
conns = redis_backends->elts;
nelts = redis_backends->nelts;
}
if (proxy_reverse_policy_is_sticky(policy_id) != TRUE) {
if (conns == NULL &&
default_backends != NULL &&
redis_backends == NULL) {
conns = default_backends->elts;
nelts = default_backends->nelts;
}
}
switch (policy_id) {
case PROXY_REVERSE_CONNECT_POLICY_RANDOM:
idx = (int) proxy_random_next(0, nelts-1);
if (idx >= 0) {
pr_trace_msg(trace_channel, 11, "%s policy: selected index %d of %u",
proxy_reverse_policy_name(policy_id), idx, nelts-1);
pconn = conns[idx];
}
break;
case PROXY_REVERSE_CONNECT_POLICY_ROUND_ROBIN:
pconn = reverse_redis_roundrobin_next(p, redis, vhost_id);
if (pconn != NULL) {
pr_trace_msg(trace_channel, 11,
"%s policy: selected backend '%.100s'",
proxy_reverse_policy_name(policy_id), proxy_conn_get_uri(pconn));
}
break;
case PROXY_REVERSE_CONNECT_POLICY_SHUFFLE:
idx = (int) reverse_redis_shuffle_next(p, redis, vhost_id);
if (idx >= 0) {
pr_trace_msg(trace_channel, 11, "%s policy: selected index %d of %u",
proxy_reverse_policy_name(policy_id), idx, nelts-1);
pconn = conns[idx];
}
break;
break;
case PROXY_REVERSE_CONNECT_POLICY_LEAST_CONNS:
pconn = reverse_redis_leastconns_next(p, redis, vhost_id);
if (pconn != NULL) {
pr_trace_msg(trace_channel, 11,
"%s policy: selected backend '%.100s'",
proxy_reverse_policy_name(policy_id), proxy_conn_get_uri(pconn));
}
break;
case PROXY_REVERSE_CONNECT_POLICY_LEAST_RESPONSE_TIME:
pconn = reverse_redis_leastresponsetime_next(p, redis, vhost_id);
if (pconn != NULL) {
pr_trace_msg(trace_channel, 11,
"%s policy: selected backend '%.100s'",
proxy_reverse_policy_name(policy_id), proxy_conn_get_uri(pconn));
}
break;
case PROXY_REVERSE_CONNECT_POLICY_PER_USER:
pconn = reverse_redis_peruser_next(p, redis, vhost_id, policy_data);
if (pconn != NULL) {
pr_trace_msg(trace_channel, 11,
"%s policy: selected backend '%.100s' for user '%s'",
proxy_reverse_policy_name(policy_id), proxy_conn_get_uri(pconn),
(const char *) policy_data);
}
break;
case PROXY_REVERSE_CONNECT_POLICY_PER_GROUP:
pconn = reverse_redis_pergroup_next(p, redis, vhost_id, policy_data);
if (pconn != NULL) {
pr_trace_msg(trace_channel, 11,
"%s policy: selected backend '%.100s' for user '%s'",
proxy_reverse_policy_name(policy_id), proxy_conn_get_uri(pconn),
(const char *) policy_data);
}
break;
case PROXY_REVERSE_CONNECT_POLICY_PER_HOST:
pconn = reverse_redis_perhost_next(p, redis, vhost_id,
session.c->remote_addr);
if (pconn != NULL) {
pr_trace_msg(trace_channel, 11,
"%s policy: selected backend '%.100s' for host '%s'",
proxy_reverse_policy_name(policy_id), proxy_conn_get_uri(pconn),
pr_netaddr_get_ipstr(session.c->remote_addr));
}
break;
default:
errno = ENOSYS;
return NULL;
}
if (backend_id != NULL) {
*backend_id = idx;
}
return pconn;
}
static int reverse_redis_policy_update_backend(pool *p, void *redis,
int policy_id, unsigned vhost_id, int backend_idx, int conn_incr,
long connect_ms) {
int res = 0, xerrno = 0;
/* If our ReverseConnectPolicy is one of PerUser, PerGroup, or PerHost,
* we can skip this step: those policies do not use the connection count/time.
* This also helps avoid contention under load for these policies.
*/
if (proxy_reverse_policy_is_sticky(policy_id) == TRUE) {
pr_trace_msg(trace_channel, 17,
"sticky policy %s does not require updates, skipping",
proxy_reverse_policy_name(policy_id));
return 0;
}
/* TODO: Right now, we simply overwrite/track the very latest connect ms.
* But this could unfairly skew policies such as LeastResponseTime, as when
* the server in question had higher latency for that particular connection,
* due to e.g. OCSP response cache expiration.
*
* Another way would to be average the given connect ms with the previous
* one (if present), and store that. Something to ponder for the future.
*/
switch (policy_id) {
case PROXY_REVERSE_CONNECT_POLICY_LEAST_CONNS:
res = reverse_redis_leastconns_update(p, redis, vhost_id, backend_idx,
conn_incr, connect_ms);
xerrno = errno;
break;
case PROXY_REVERSE_CONNECT_POLICY_LEAST_RESPONSE_TIME:
res = reverse_redis_leastresponsetime_update(p, redis, vhost_id,
backend_idx, conn_incr, connect_ms);
xerrno = errno;
break;
default:
res = 0;
break;
}
errno = xerrno;
return res;
}
static int reverse_redis_policy_used_backend(pool *p, void *redis,
int policy_id, unsigned int vhost_id, int backend_idx) {
int res, xerrno = 0;
switch (policy_id) {
case PROXY_REVERSE_CONNECT_POLICY_RANDOM:
res = 0;
break;
case PROXY_REVERSE_CONNECT_POLICY_ROUND_ROBIN:
res = reverse_redis_roundrobin_used(p, redis, vhost_id, backend_idx);
xerrno = errno;
break;
case PROXY_REVERSE_CONNECT_POLICY_SHUFFLE:
res = reverse_redis_shuffle_used(p, redis, vhost_id, backend_idx);
xerrno = errno;
break;
case PROXY_REVERSE_CONNECT_POLICY_LEAST_CONNS:
res = reverse_redis_leastconns_used(p, redis, vhost_id, backend_idx);
xerrno = errno;
break;
case PROXY_REVERSE_CONNECT_POLICY_LEAST_RESPONSE_TIME:
res = reverse_redis_leastresponsetime_used(p, redis, vhost_id,
backend_idx);
xerrno = errno;
break;
case PROXY_REVERSE_CONNECT_POLICY_PER_USER:
res = reverse_redis_peruser_used(p, redis, vhost_id, backend_idx);
xerrno = errno;
break;
case PROXY_REVERSE_CONNECT_POLICY_PER_GROUP:
res = reverse_redis_pergroup_used(p, redis, vhost_id, backend_idx);
xerrno = errno;
break;
case PROXY_REVERSE_CONNECT_POLICY_PER_HOST:
res = reverse_redis_perhost_used(p, redis, vhost_id, backend_idx);
xerrno = errno;
break;
default:
xerrno = ENOSYS;
res = -1;
break;
}
errno = xerrno;
return res;
}
static void *reverse_redis_init(pool *p, const char *tables_path, int flags) {
int xerrno = 0;
pr_redis_t *redis;
(void) tables_path;
(void) flags;
redis = pr_redis_conn_new(p, &proxy_module, 0);
xerrno = errno;
if (redis == NULL) {
(void) pr_log_pri(PR_LOG_NOTICE, MOD_PROXY_VERSION
": error opening Redis connection: %s", strerror(xerrno));
errno = xerrno;
return NULL;
}
(void) pr_redis_conn_set_namespace(redis, &proxy_module, redis_prefix,
redis_prefixsz);
return redis;
}
static int reverse_redis_close(pool *p, void *redis) {
if (redis != NULL) {
if (pr_redis_conn_close(redis) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error closing Redis connection: %s", strerror(errno));
}
}
return 0;
}
static void *reverse_redis_open(pool *p, const char *tables_path,
array_header *backends) {
int xerrno = 0;
pr_redis_t *redis;
redis = pr_redis_conn_new(p, &proxy_module, 0);
xerrno = errno;
if (redis == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error opening Redis connection: %s", strerror(xerrno));
errno = xerrno;
return NULL;
}
(void) pr_redis_conn_set_namespace(redis, &proxy_module, redis_prefix,
redis_prefixsz);
redis_backends = backends;
return redis;
}
int proxy_reverse_redis_as_datastore(struct proxy_reverse_datastore *ds,
void *ds_data, size_t ds_datasz) {
if (ds == NULL) {
errno = EINVAL;
return -1;
}
ds->policy_init = reverse_redis_policy_init;
ds->policy_next_backend = reverse_redis_policy_next_backend;
ds->policy_used_backend = reverse_redis_policy_used_backend;
ds->policy_update_backend = reverse_redis_policy_update_backend;
ds->init = reverse_redis_init;
ds->open = reverse_redis_open;
ds->close = reverse_redis_close;
redis_prefix = ds_data;
redis_prefixsz = ds_datasz;
return 0;
}
proftpd-mod_proxy-0.9.5/lib/proxy/session.c 0000664 0000000 0000000 00000021415 14757370167 0021023 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy session routines
* Copyright (c) 2012-2024 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "proxy/session.h"
static const char *trace_channel = "proxy.session";
const struct proxy_session *proxy_session_alloc(pool *p) {
pool *sess_pool;
struct proxy_session *proxy_sess;
if (p == NULL) {
errno = EINVAL;
return NULL;
}
sess_pool = make_sub_pool(p);
pr_pool_tag(sess_pool, "Proxy Session pool");
proxy_sess = pcalloc(sess_pool, sizeof(struct proxy_session));
proxy_sess->pool = sess_pool;
/* This will be configured by the ProxySourceAddress directive, if present. */
proxy_sess->src_addr = NULL;
proxy_session_reset_dataxfer(proxy_sess);
/* This will be configured by the ProxyDataTransferPolicy directive, if
* present.
*/
proxy_sess->dataxfer_policy = PROXY_SESS_DATA_TRANSFER_POLICY_DEFAULT;
/* Fill in the defaults for the session members. */
proxy_sess->connect_timeout = -1;
proxy_sess->connect_timerno = -1;
proxy_sess->linger_timeout = -1;
proxy_sess->use_ftp = TRUE;
proxy_sess->use_ssh = FALSE;
return proxy_sess;
}
int proxy_session_free(pool *p, const struct proxy_session *proxy_sess) {
conn_t *conn;
struct proxy_session *sess;
if (p == NULL ||
proxy_sess == NULL) {
errno = EINVAL;
return -1;
}
/* Close any open connections. */
sess = (struct proxy_session *) proxy_sess;
conn = proxy_sess->frontend_data_conn;
if (conn != NULL) {
pr_inet_close(p, conn);
sess->frontend_data_conn = session.d = NULL;
}
conn = proxy_sess->backend_ctrl_conn;
if (conn != NULL) {
pr_inet_close(p, conn);
sess->backend_ctrl_conn = NULL;
}
conn = proxy_sess->backend_data_conn;
if (conn != NULL) {
pr_inet_close(p, conn);
sess->backend_data_conn = NULL;
}
destroy_pool(proxy_sess->pool);
return 0;
}
int proxy_session_reset_dataxfer(struct proxy_session *proxy_sess) {
if (proxy_sess == NULL) {
errno = EINVAL;
return -1;
}
if (proxy_sess->dataxfer_pool != NULL) {
destroy_pool(proxy_sess->dataxfer_pool);
}
proxy_sess->dataxfer_pool = make_sub_pool(proxy_sess->pool);
pr_pool_tag(proxy_sess->dataxfer_pool, "Proxy Session Data Transfer pool");
return 0;
}
int proxy_session_check_password(pool *p, const char *user,
const char *passwd) {
int res;
pr_trace_msg(trace_channel, 18, "checking password for user '%s'", user);
res = pr_auth_authenticate(p, user, passwd);
switch (res) {
case PR_AUTH_OK:
break;
case PR_AUTH_NOPWD:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"password authentication for user '%s' failed: No such user", user);
pr_log_auth(PR_LOG_NOTICE, "USER %s (Login failed): No such user found",
user);
errno = ENOENT;
return -1;
case PR_AUTH_BADPWD:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"password authentication for user '%s' failed: Incorrect password",
user);
pr_log_auth(PR_LOG_NOTICE, "USER %s (Login failed): Incorrect password",
user);
errno = EACCES;
return -1;
case PR_AUTH_AGEPWD:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"password authentication for user '%s' failed: Password expired",
user);
pr_log_auth(PR_LOG_NOTICE, "USER %s (Login failed): Password expired",
user);
errno = EPERM;
return -1;
case PR_AUTH_DISABLEDPWD:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"password authentication for user '%s' failed: Account disabled",
user);
pr_log_auth(PR_LOG_NOTICE, "USER %s (Login failed): Account disabled",
user);
errno = EPERM;
return -1;
default:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unknown authentication value (%d), returning error", res);
errno = EINVAL;
return -1;
}
return 0;
}
int proxy_session_setup_env(pool *p, const char *user, int flags) {
struct passwd *pw;
config_rec *c;
int i, res = 0, xerrno = 0;
const char *xferlog = NULL;
if (p == NULL ||
user == NULL) {
errno = EINVAL;
return -1;
}
session.hide_password = TRUE;
/* Note: the given user name may not be known locally on the proxy; thus
* having pr_auth_getpwnam() returning NULL here is not an unexpected
* use case.
*/
pw = pr_auth_getpwnam(p, user);
if (pw != NULL) {
if (pw->pw_uid == PR_ROOT_UID) {
int root_login = FALSE;
pr_event_generate("mod_auth.root-login", NULL);
c = find_config(main_server->conf, CONF_PARAM, "RootLogin", FALSE);
if (c != NULL) {
root_login = *((int *) c->argv[0]);
}
if (root_login == FALSE) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"root login attempted, denied by RootLogin configuration");
pr_log_auth(PR_LOG_NOTICE, "SECURITY VIOLATION: Root login attempted");
return -1;
}
pr_log_auth(PR_LOG_WARNING, "ROOT proxy login successful");
}
res = pr_auth_is_valid_shell(main_server->conf, pw->pw_shell);
if (res == FALSE) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"authentication for user '%s' failed: Invalid shell", user);
pr_log_auth(PR_LOG_NOTICE, "USER %s (Login failed): Invalid shell: '%s'",
user, pw->pw_shell);
errno = EPERM;
return -1;
}
res = pr_auth_banned_by_ftpusers(main_server->conf, pw->pw_name);
if (res == TRUE) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"authentication for user '%s' failed: User in " PR_FTPUSERS_PATH, user);
pr_log_auth(PR_LOG_NOTICE, "USER %s (Login failed): User in "
PR_FTPUSERS_PATH, pw->pw_name);
errno = EPERM;
return -1;
}
session.user = pstrdup(p, pw->pw_name);
session.group = pstrdup(p, pr_auth_gid2name(p, pw->pw_gid));
session.login_uid = pw->pw_uid;
session.login_gid = pw->pw_gid;
} else {
session.user = pstrdup(session.pool, user);
/* XXX What should session.group, session.login_uid, session.login_gid
* be? Kept as is?
*/
}
if (session.gids == NULL &&
session.groups == NULL) {
res = pr_auth_getgroups(p, session.user, &session.gids, &session.groups);
if (res < 1 &&
errno != ENOENT) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"no supplemental groups found for user '%s'", session.user);
}
}
if (flags & PROXY_SESSION_FL_CHECK_LOGIN_ACL) {
int login_acl;
login_acl = login_check_limits(main_server->conf, FALSE, TRUE, &i);
if (!login_acl) {
pr_log_auth(PR_LOG_NOTICE, "USER %s (Login failed): Limit configuration "
"denies login", user);
return -1;
}
}
/* XXX Will users want wtmp logging for a proxy login? */
session.wtmp_log = FALSE;
c = find_config(main_server->conf, CONF_PARAM, "TransferLog", FALSE);
if (c == NULL) {
xferlog = PR_XFERLOG_PATH;
} else {
xferlog = c->argv[0];
}
PRIVS_ROOT
if (strcasecmp(xferlog, "none") == 0) {
xferlog_open(NULL);
} else {
xferlog_open(xferlog);
}
res = xerrno = 0;
if (pw != NULL) {
res = set_groups(p, pw->pw_gid, session.gids);
xerrno = errno;
}
PRIVS_RELINQUISH
if (res < 0) {
pr_log_pri(PR_LOG_WARNING, "unable to set process groups: %s",
strerror(xerrno));
}
session.proc_prefix = pstrdup(session.pool, session.c->remote_name);
session.sf_flags = 0;
pr_scoreboard_entry_update(session.pid,
PR_SCORE_USER, session.user,
PR_SCORE_CWD, pr_fs_getcwd(),
NULL);
if (session.group != NULL) {
session.group = pstrdup(session.pool, session.group);
}
if (session.groups != NULL) {
session.groups = copy_array_str(session.pool, session.groups);
}
proxy_sess_state |= PROXY_SESS_STATE_PROXY_AUTHENTICATED;
pr_timer_remove(PR_TIMER_LOGIN, ANY_MODULE);
return 0;
}
proftpd-mod_proxy-0.9.5/lib/proxy/ssh.c 0000664 0000000 0000000 00000070124 14757370167 0020136 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH implementation
* Copyright (c) 2021-2022 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "proxy/conn.h"
#include "proxy/netio.h"
#include "proxy/reverse.h"
#include "proxy/session.h"
#include "proxy/ssh.h"
#include "proxy/ssh/ssh2.h"
#include "proxy/ssh/msg.h"
#include "proxy/ssh/auth.h"
#include "proxy/ssh/db.h"
#include "proxy/ssh/redis.h"
#include "proxy/ssh/crypto.h"
#include "proxy/ssh/packet.h"
#include "proxy/ssh/interop.h"
#include "proxy/ssh/kex.h"
#include "proxy/ssh/keys.h"
#include "proxy/ssh/cipher.h"
#include "proxy/ssh/mac.h"
#include "proxy/ssh/utf8.h"
#if defined(PR_USE_OPENSSL)
#include
#include
#include
static const char *ssh_tables_path = NULL;
static struct proxy_ssh_datastore ssh_ds;
static const char *ssh_client_version = PROXY_SSH_ID_DEFAULT_STRING;
static const char *ssh_server_version = NULL;
static const char *trace_channel = "proxy.ssh";
/* The number of packets to handle, while polling the backend connection, is
* tricky. Too many, and we risk stalling the frontend client. Too few,
* and we risk losing backend packets (and deadlocking the frontend client).
*
* This is most noticeable when most of the packets flow one way during the
* session, as for SFTP/SCP uploads/downloads.
*
* With the use of packet_mpoll(), we set this number quite high; perhaps
* this limit should be removed altogether?
*/
#define MAX_POLL_PACKETS 5000
static unsigned long ssh_opts = 0UL;
static void ssh_ssh2_read_poll_ev(const void *, void *);
static int ssh_get_server_version(pool *p,
const struct proxy_session *proxy_sess) {
int res;
/* 255 is the RFC-defined maximum banner/ID string size */
char buf[256], *banner = NULL;
size_t buflen = 0;
/* Read server version. This looks ugly, reading one byte at a time.
* It is necessary, though. The banner sent by the server is not of any
* guaranteed length. The server might also send the next SSH packet in
* the exchange, such that both messages are in the socket buffer. If
* we read too much of the banner, we'll read into the KEXINIT, for example,
* and cause problems later.
*/
while (TRUE) {
register unsigned int i;
int bad_proto = FALSE;
pr_signals_handle();
memset(buf, '\0', sizeof(buf));
for (i = 0; i < sizeof(buf) - 1; i++) {
res = proxy_ssh_packet_conn_read(proxy_sess->backend_ctrl_conn, &buf[i],
1, 0);
while (res <= 0) {
int xerrno = errno;
if (xerrno == EINTR) {
pr_signals_handle();
res = proxy_ssh_packet_conn_read(proxy_sess->backend_ctrl_conn,
&buf[i], 1, 0);
continue;
}
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error reading from server rfd %d: %s",
proxy_sess->backend_ctrl_conn->rfd, strerror(xerrno));
}
errno = xerrno;
return res;
}
/* We continue reading until the server has sent the terminating
* CRLF sequence.
*/
if (buf[i] == '\r') {
buf[i] = '\0';
continue;
}
if (buf[i] == '\n') {
buf[i] = '\0';
break;
}
}
if (i == sizeof(buf)-1) {
bad_proto = TRUE;
} else {
buf[sizeof(buf)-1] = '\0';
buflen = strlen(buf);
}
/* If the line does not begin with "SSH-2.0-", skip it. RFC4253, Section
* 4.2 does not specify what should happen if the server sends data
* other than the proper version string initially.
*
* If we have been configured for compatibility with old protocol
* implementations, check for "SSH-1.99-" as well.
*
* OpenSSH simply disconnects the server after saying "Protocol mismatch"
* if the server's version string does not begin with "SSH-2.0-"
* (or "SSH-1.99-"). Works for me.
*/
if (bad_proto == FALSE) {
if (strncmp(buf, "SSH-2.0-", 8) != 0) {
bad_proto = TRUE;
if (proxy_opts & PROXY_OPT_SSH_OLD_PROTO_COMPAT) {
if (strncmp(buf, "SSH-1.99-", 9) == 0) {
if (buflen == 9) {
/* The client sent ONLY "SSH-1.99-". OpenSSH handles this as a
* "Protocol mismatch", so shall we.
*/
bad_proto = TRUE;
} else {
banner = buf + 9;
bad_proto = FALSE;
}
}
}
} else {
if (buflen == 8) {
/* The client sent ONLY "SSH-2.0-". OpenSSH handles this as a
* "Protocol mismatch", so shall we.
*/
bad_proto = TRUE;
} else {
banner = buf + 8;
}
}
}
if (banner != NULL) {
char *k, *v;
k = pstrdup(session.pool, "PROXY_SSH_SERVER_BANNER");
v = pstrdup(session.pool, banner);
pr_env_unset(session.pool, k);
pr_env_set(session.pool, k, v);
(void) pr_table_add(session.notes, k, v, 0);
}
if (bad_proto) {
const char *errstr = "Protocol mismatch.\n";
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"Bad protocol version '%.100s' from %s", buf,
pr_netaddr_get_ipstr(proxy_sess->backend_ctrl_conn->remote_addr));
if (write(proxy_sess->backend_ctrl_conn->wfd, errstr,
strlen(errstr)) < 0) {
pr_trace_msg(trace_channel, 9,
"error sending 'Protocol mismatch' message to server: %s",
strerror(errno));
}
errno = EINVAL;
return -1;
}
break;
}
ssh_server_version = pstrdup(p, buf);
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"received server version '%s'", ssh_server_version);
if (proxy_ssh_interop_handle_version(session.pool, proxy_sess,
ssh_server_version) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error checking server version '%s' for interoperability: %s",
ssh_server_version, strerror(errno));
}
return 0;
}
/* Event listeners
*/
static void ssh_restart_ev(const void *event_data, void *user_data) {
/* Clear the host keys. */
proxy_ssh_keys_free();
/* Clear the client banner regexes. */
proxy_ssh_interop_free();
}
static int ssh_handle_kexinit(pool *p, struct proxy_session *proxy_sess) {
int res;
if (proxy_opts & PROXY_OPT_SSH_PESSIMISTIC_KEXINIT) {
/* If we are being pessimistic, we will send our version string to the
* server now, and send our KEXINIT message later.
*/
res = proxy_ssh_packet_send_version(proxy_sess->backend_ctrl_conn);
} else {
/* If we are being optimistic, we can reduce the connection latency
* by sending our KEXINIT message now; this will have the server version
* string automatically prepended.
*/
res = proxy_ssh_kex_send_first_kexinit(session.pool, proxy_sess);
}
if (res < 0) {
return -1;
}
/* Set the initial timeout for reading packets from servers. Using
* a value of -1 sets the default timeout value (i.e. TimeoutIdle).
*/
proxy_ssh_packet_set_poll_timeout(-1, 0);
res = ssh_get_server_version(proxy_pool, proxy_sess);
if (res < 0) {
return -1;
}
res = proxy_ssh_kex_init(session.pool, ssh_client_version,
ssh_server_version);
if (res < 0) {
/* XXX Should we disconnect here? */
}
/* If we didn't send our KEXINIT earlier, send it now. */
if (proxy_opts & PROXY_OPT_SSH_PESSIMISTIC_KEXINIT) {
res = proxy_ssh_kex_send_first_kexinit(session.pool, proxy_sess);
if (res < 0) {
return -1;
}
}
return 0;
}
static void ssh_handle_kex(pool *p, struct proxy_session *proxy_sess) {
while (TRUE) {
int res;
pr_signals_handle();
if (proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_KEX) {
/* We're done! */
break;
}
res = proxy_ssh_packet_process(proxy_pool, proxy_sess);
if (res < 0) {
destroy_pool(p);
pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION,
NULL);
}
}
}
static void ssh_ssh2_auth_completed_ev(const void *event_data,
void *user_data) {
int res;
struct proxy_session *proxy_sess;
const char *connect_data, *hook_symbol, *user;
cmdtable *sftp_cmdtab;
pool *tmp_pool;
cmd_rec *cmd;
modret_t *result;
struct proxy_ssh_packet *pkt = NULL;
unsigned char *buf, *ptr;
uint32_t bufsz, buflen, len = 0;
module m;
proxy_sess = user_data;
m.name = "mod_proxy";
tmp_pool = make_sub_pool(session.pool);
pr_pool_tag(tmp_pool, "Proxy SSH Auth completed pool");
/* Look up the hook for setting the callback for writing packets to the
* frontend client; we'll need it later.
*/
hook_symbol = "sftp_get_packet_write";
sftp_cmdtab = pr_stash_get_symbol2(PR_SYM_HOOK, hook_symbol, NULL, NULL,
NULL);
if (sftp_cmdtab == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to find SFTP hook symbol '%s'", hook_symbol);
destroy_pool(tmp_pool);
pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION,
NULL);
}
cmd = pr_cmd_alloc(tmp_pool, 1, NULL);
result = pr_module_call(sftp_cmdtab->m, sftp_cmdtab->handler, cmd);
if (result == NULL ||
MODRET_ISERROR(result)) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error getting SSH packet writer");
pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION,
NULL);
}
res = proxy_ssh_auth_set_frontend_success_handle(tmp_pool, NULL);
if (res < 0) {
destroy_pool(tmp_pool);
pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION,
NULL);
}
user = pr_table_get(session.notes, "mod_auth.orig-user", NULL);
res = proxy_session_setup_env(proxy_pool, user,
PROXY_SESSION_FL_CHECK_LOGIN_ACL);
if (res < 0) {
destroy_pool(tmp_pool);
pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_CONFIG_ACL, NULL);
}
/* The "connect data" we use here depends on the sticky ConnectPolicy in
* effect.
*/
connect_data = user;
if (proxy_reverse_get_connect_policy() == PROXY_REVERSE_CONNECT_POLICY_PER_GROUP) {
connect_data = session.group;
}
res = proxy_reverse_connect(proxy_pool, proxy_sess, connect_data);
if (res < 0) {
destroy_pool(tmp_pool);
pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION,
NULL);
}
res = ssh_handle_kexinit(tmp_pool, proxy_sess);
if (res < 0) {
destroy_pool(tmp_pool);
pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION,
NULL);
}
proxy_ssh_auth_init(proxy_pool);
/* Now we need to run the KEXINIT with the backend server to completion,
* but not more than that.
*/
ssh_handle_kex(tmp_pool, proxy_sess);
/* We now need to run the service, auth portions to completion; for these
* we act as if we were the frontend client sending packets. We'll want
* to reuse as much of our proxying machinery as possible, but we also need
* to ensure that that machinery does not actually send packets to the
* frontend client. Thus we temporarily use a null packet handler here.
*/
proxy_ssh_packet_set_frontend_packet_write(NULL);
pkt = proxy_ssh_packet_create(tmp_pool);
/* Note: It is important that this packet NOT come from mod_proxy. */
pkt->m = &m;
bufsz = buflen = 1024;
ptr = buf = palloc(pkt->pool, bufsz);
len += proxy_ssh_msg_write_byte(&buf, &buflen,
PROXY_SSH_MSG_SERVICE_REQUEST);
len += proxy_ssh_msg_write_string(&buf, &buflen, "ssh-userauth");
pkt->payload = ptr;
pkt->payload_len = len;
if (proxy_ssh_packet_handle(pkt) < 0) {
/* Restore the callback for writing our DISCONNECT packet to the frontend
* client.
*/
proxy_ssh_packet_set_frontend_packet_write(result->data);
pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION,
NULL);
}
pkt = proxy_ssh_packet_create(tmp_pool);
/* Note: It is important that this packet NOT come from mod_proxy. */
pkt->m = &m;
bufsz = buflen = 4096;
ptr = buf = palloc(pkt->pool, bufsz);
len += proxy_ssh_msg_write_byte(&buf, &buflen,
PROXY_SSH_MSG_USER_AUTH_REQUEST);
len += proxy_ssh_msg_write_string(&buf, &buflen, user);
len += proxy_ssh_msg_write_string(&buf, &buflen, "ssh-connection");
len += proxy_ssh_msg_write_string(&buf, &buflen, "hostbased");
pkt->payload = ptr;
pkt->payload_len = len;
if (proxy_ssh_packet_handle(pkt) < 0) {
/* Restore the callback for writing our DISCONNECT packet to the frontend
* client.
*/
proxy_ssh_packet_set_frontend_packet_write(result->data);
pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION,
NULL);
}
/* Now we should be successfully authenticated to the backend server. */
if (!(proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_AUTH)) {
destroy_pool(tmp_pool);
pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION,
NULL);
}
res = proxy_ssh_packet_set_frontend_packet_handle(tmp_pool,
proxy_ssh_packet_handle);
if (res < 0) {
destroy_pool(tmp_pool);
pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION,
NULL);
}
proxy_ssh_packet_set_frontend_packet_write(result->data);
/* Now we register for mod_sftp's read-loop, to listen for frontend and
* backend packets.
*/
pr_event_register(&proxy_module, "mod_sftp.ssh2.read-poll",
ssh_ssh2_read_poll_ev, proxy_sess);
/* To trigger mod_proxy to restrict this session, now that we have
* authenticated to the backend server, we generate an event as if we
* were handling an FTP session.
*/
pr_event_generate("mod_proxy.ctrl-read", NULL);
destroy_pool(tmp_pool);
}
static void ssh_ssh2_kex_completed_ev(const void *event_data, void *user_data) {
int res;
struct proxy_session *proxy_sess;
const char *hook_symbol;
cmdtable *sftp_cmdtab;
pool *tmp_pool;
cmd_rec *cmd;
modret_t *result;
proxy_sess = user_data;
tmp_pool = make_sub_pool(session.pool);
pr_pool_tag(tmp_pool, "Proxy SSH KEX completed pool");
res = proxy_ssh_packet_set_frontend_packet_handle(tmp_pool,
proxy_ssh_packet_handle);
if (res < 0) {
destroy_pool(tmp_pool);
/* XXX Should we disconnect here? */
return;
}
/* If we have already authenticated to the backend, then this is a rekey,
* and we do NOT want to interact with the backend anymore for this event.
*/
if (proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_AUTH) {
pr_trace_msg(trace_channel, 19, "frontend-initiated rekeying COMPLETED");
/* Now we register for mod_sftp's read-loop, to listen for frontend and
* backend packets.
*/
pr_event_register(&proxy_module, "mod_sftp.ssh2.read-poll",
ssh_ssh2_read_poll_ev, proxy_sess);
destroy_pool(tmp_pool);
return;
}
hook_symbol = "sftp_get_packet_write";
sftp_cmdtab = pr_stash_get_symbol2(PR_SYM_HOOK, hook_symbol, NULL, NULL,
NULL);
if (sftp_cmdtab == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to find SFTP hook symbol '%s'", hook_symbol);
destroy_pool(tmp_pool);
/* XXX Should we disconnect here? */
return;
}
cmd = pr_cmd_alloc(tmp_pool, 1, NULL);
result = pr_module_call(sftp_cmdtab->m, sftp_cmdtab->handler, cmd);
if (result == NULL ||
MODRET_ISERROR(result)) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error getting SSH packet writer");
/* XXX Should we disconnect here? */
}
/* Connecting to the selected backend server happened earlier (right?),
* in proxy_reverse_sess_init(). So now we start our SSH session with
* the selected backend host.
*
* Note, though, that if the UseReverseProxyAuth ProxyOption was configured,
* we may not have connected to the backend server yet, as the frontend
* client might not have authenticated; we have only completed the frontend
* KEX at this point. So how do we knkow if we need to connect to the
* backend server here?
*
* ProxyOptions UseReverseProxyAuth
* ProxyReverseConnectPolicy NOT PerUser/PerGroup
*/
if (!(proxy_sess_state & PROXY_SESS_STATE_CONNECTED) &&
proxy_reverse_use_proxy_auth() == TRUE) {
int connect_policy_id;
connect_policy_id = proxy_reverse_get_connect_policy();
switch (connect_policy_id) {
case PROXY_REVERSE_CONNECT_POLICY_PER_GROUP:
case PROXY_REVERSE_CONNECT_POLICY_PER_USER:
break;
default: {
res = proxy_reverse_connect(proxy_pool, proxy_sess, NULL);
if (res < 0) {
destroy_pool(tmp_pool);
pr_session_disconnect(&proxy_module,
PR_SESS_DISCONNECT_BY_APPLICATION, NULL);
}
}
}
}
res = ssh_handle_kexinit(tmp_pool, proxy_sess);
if (res < 0) {
destroy_pool(tmp_pool);
pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION,
NULL);
}
proxy_ssh_auth_init(proxy_pool);
/* Now we need to run the KEXINIT with the backend server to completion,
* but not more than that.
*/
ssh_handle_kex(tmp_pool, proxy_sess);
proxy_ssh_packet_set_frontend_packet_write(result->data);
/* Now we register for mod_sftp's read-loop, to listen for frontend and
* backend packets.
*/
pr_event_register(&proxy_module, "mod_sftp.ssh2.read-poll",
ssh_ssh2_read_poll_ev, proxy_sess);
/* To trigger mod_proxy to restrict this session, now that we have
* authenticated to the backend server, we generate an event as if we
* were handling an FTP session.
*/
pr_event_generate("mod_proxy.ctrl-read", NULL);
destroy_pool(tmp_pool);
}
static void ssh_ssh2_read_poll_ev(const void *event_data, void *user_data) {
const struct proxy_session *proxy_sess;
int poll_timeout_secs, res;
unsigned long poll_timeout_ms;
unsigned int npackets = 0, poll_attempts;
pool *tmp_pool;
/* We only want to do polling for, and processing of, backend packets once
* our SSH session has reached a necessary start (SSH_HAVE_AUTH in
* particular).
*/
if (!(proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_AUTH)) {
return;
}
proxy_sess = user_data;
tmp_pool = make_sub_pool(session.pool);
pr_pool_tag(tmp_pool, "Proxy SSH read-poll pool");
proxy_ssh_packet_get_poll_attempts(&poll_attempts);
proxy_ssh_packet_get_poll_timeout(&poll_timeout_secs, &poll_timeout_ms);
proxy_ssh_packet_set_poll_attempts(2);
proxy_ssh_packet_set_poll_timeout(0, 100);
/* We try to process multiple backend packets in a loop, if we can. */
res = proxy_ssh_packet_conn_mpoll(proxy_sess->frontend_ctrl_conn,
proxy_sess->backend_ctrl_conn, PROXY_SSH_PACKET_IO_READ);
pr_trace_msg(trace_channel, 10, "read-mpoll returned %d", res);
while (res == 1 &&
npackets < MAX_POLL_PACKETS) {
pr_signals_handle();
res = proxy_ssh_packet_process(tmp_pool, proxy_sess);
if (res < 0) {
pr_trace_msg(trace_channel, 2,
"error processing backend packet during frontend read poll: %s",
strerror(errno));
}
npackets++;
res = proxy_ssh_packet_conn_mpoll(proxy_sess->frontend_ctrl_conn,
proxy_sess->backend_ctrl_conn, PROXY_SSH_PACKET_IO_READ);
}
proxy_ssh_packet_set_poll_attempts(poll_attempts);
proxy_ssh_packet_set_poll_timeout(poll_timeout_secs, poll_timeout_ms);
destroy_pool(tmp_pool);
}
#endif /* PR_USE_OPENSSL */
int proxy_ssh_init(pool *p, const char *tables_path, int flags) {
#if defined(PR_USE_OPENSSL)
int res;
config_rec *c;
memset(&ssh_ds, 0, sizeof(ssh_ds));
switch (proxy_datastore) {
case PROXY_DATASTORE_REDIS:
res = proxy_ssh_redis_as_datastore(&ssh_ds, proxy_datastore_data,
proxy_datastore_datasz);
break;
case PROXY_DATASTORE_SQLITE:
res = proxy_ssh_db_as_datastore(&ssh_ds, proxy_datastore_data,
proxy_datastore_datasz);
break;
default:
res = -1;
errno = EINVAL;
break;
}
if (res < 0) {
return -1;
}
res = (ssh_ds.init)(p, tables_path, flags);
if (res < 0) {
return -1;
}
if (pr_module_exists("mod_sftp.c") == FALSE &&
pr_module_exists("mod_tls.c") == FALSE) {
#if OPENSSL_VERSION_NUMBER < 0x10100000L
OPENSSL_config(NULL);
#endif /* prior to OpenSSL-1.1.x */
SSL_load_error_strings();
SSL_library_init();
ERR_load_crypto_strings();
OpenSSL_add_all_algorithms();
}
ssh_tables_path = pstrdup(proxy_pool, tables_path);
/* Initialize SSH API */
proxy_ssh_interop_init();
proxy_ssh_cipher_init();
proxy_ssh_mac_init();
proxy_ssh_utf8_init();
pr_event_register(&proxy_module, "core.postparse", ssh_restart_ev, NULL);
/* Note that this function is called from mod_proxy's "core.postparse" event
* listener. So we do, now, anything that we would have done in our own
* postparse event listener.
*/
c = find_config(main_server->conf, CONF_PARAM, "ProxySFTPPassPhraseProvider",
FALSE);
if (c != NULL) {
proxy_ssh_keys_set_passphrase_provider(c->argv[0]);
}
proxy_ssh_keys_get_passphrases();
#endif /* PR_USE_OPENSSL */
return 0;
}
int proxy_ssh_free(pool *p) {
if (p == NULL) {
errno = EINVAL;
return -1;
}
#if defined(PR_USE_OPENSSL)
if (ssh_ds.dsh != NULL) {
int res;
res = (ssh_ds.close)(p, ssh_ds.dsh);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error closing datastore: %s", strerror(errno));
}
ssh_ds.dsh = NULL;
}
pr_event_unregister(&proxy_module, "core.restart", ssh_restart_ev);
proxy_ssh_interop_free();
proxy_ssh_keys_free();
proxy_ssh_cipher_free();
proxy_ssh_mac_free();
proxy_ssh_utf8_free();
proxy_ssh_crypto_free(0);
#endif /* PR_USE_OPENSSL */
return 0;
}
int proxy_ssh_sess_init(pool *p, struct proxy_session *proxy_sess, int flags) {
#if defined(PR_USE_OPENSSL)
int connect_policy_id = PROXY_REVERSE_CONNECT_POLICY_ROUND_ROBIN;
int sftp_engine, proxy_role = 0, verify_server, xerrno = 0;
config_rec *c;
if (p == NULL) {
errno = EINVAL;
return -1;
}
c = find_config(main_server->conf, CONF_PARAM, "SFTPEngine", FALSE);
if (c == NULL) {
return 0;
}
sftp_engine = *((int *) c->argv[0]);
if (sftp_engine != TRUE) {
return 0;
}
/* We currently only support SSH reverse proxying, not forward proxying,
* and therefore need to check the configured ProxyRole.
*/
c = find_config(main_server->conf, CONF_PARAM, "ProxyRole", FALSE);
if (c != NULL) {
proxy_role = *((int *) c->argv[0]);
}
/* Sadly, we cannot use the PROXY_ROLE constant here, since it is scoped
* only to mod_proxy.c.
*/
if (proxy_role != 1) {
pr_trace_msg(trace_channel, 1,
"unable to support non-reverse ProxyRole for SFTP");
return 0;
}
proxy_sess->use_ftp = FALSE;
proxy_sess->use_ssh = TRUE;
pr_response_block(TRUE);
c = find_config(main_server->conf, CONF_PARAM, "ServerIdent", FALSE);
if (c != NULL) {
if (*((unsigned char *) c->argv[0]) == FALSE) {
/* The admin configured "ServerIdent off". Set the version string to
* just "mod_proxy", and that's it, no version.
*/
ssh_client_version = pstrcat(proxy_pool, PROXY_SSH_ID_PREFIX, "mod_proxy",
NULL);
proxy_ssh_packet_set_version(ssh_client_version);
} else {
/* The admin configured "ServerIdent on", and possibly some custom
* string.
*/
if (c->argc > 1) {
ssh_client_version = pstrcat(proxy_pool, PROXY_SSH_ID_PREFIX,
c->argv[1], NULL);
proxy_ssh_packet_set_version(ssh_client_version);
}
}
}
c = find_config(main_server->conf, CONF_PARAM, "ProxySFTPOptions", FALSE);
while (c != NULL) {
unsigned long opts = 0;
pr_signals_handle();
opts = *((unsigned long *) c->argv[0]);
ssh_opts |= opts;
c = find_config_next(c, c->next, CONF_PARAM, "ProxySFTPOptions", FALSE);
}
proxy_opts |= ssh_opts;
c = find_config(main_server->conf, CONF_PARAM, "ProxySFTPHostKey", FALSE);
while (c != NULL) {
const char *path;
pr_signals_handle();
path = c->argv[0];
if (proxy_ssh_keys_get_hostkey(p, path) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error loading hostkey '%s', skipping key", path);
}
c = find_config_next(c, c->next, CONF_PARAM, "ProxySFTPHostKey", FALSE);
}
c = find_config(main_server->conf, CONF_PARAM, "ProxySFTPVerifyServer",
FALSE);
if (c != NULL) {
verify_server = *((int *) c->argv[0]);
} else {
verify_server = FALSE;
}
PRIVS_ROOT
ssh_ds.dsh = (ssh_ds.open)(proxy_pool, ssh_tables_path, ssh_opts);
xerrno = errno;
PRIVS_RELINQUISH
if (ssh_ds.dsh == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error opening SSH datastore: %s", strerror(xerrno));
errno = xerrno;
return -1;
}
proxy_ssh_kex_sess_init(p, &ssh_ds, verify_server);
/* For PerUser/PerGroup/PerHost connection policies, we pay attention to
* the mod_sftp events generated for successful authentication, otherwise
* we use the successful KEX event.
*/
c = find_config(main_server->conf, CONF_PARAM, "ProxyReverseConnectPolicy",
FALSE);
if (c != NULL) {
connect_policy_id = *((int *) c->argv[0]);
}
if (proxy_reverse_policy_is_sticky(connect_policy_id) == TRUE &&
connect_policy_id != PROXY_REVERSE_CONNECT_POLICY_PER_HOST) {
/* PerUser/PerGroup connect policies REQUIRE that we use "hostbased"
* authentication to the backend server; make sure that ProxySFTPHostKeys
* have been configured for this.
*/
if (proxy_ssh_keys_have_hostkey(PROXY_SSH_KEY_UNKNOWN) != 0) {
/* We have no configured hostkeys, thus cannot use "hostbased"
* authentication; return FAILURE to the frontend client.
*/
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to handle '%s' ProxyReverseConnectPolicy: "
"no ProxySFTPHostKeys configured",
proxy_reverse_policy_name(connect_policy_id));
errno = EPERM;
return -1;
}
pr_event_register(&proxy_module, "mod_sftp.ssh2.auth-hostbased",
ssh_ssh2_auth_completed_ev, proxy_sess);
pr_event_register(&proxy_module, "mod_sftp.ssh2.auth-kbdint",
ssh_ssh2_auth_completed_ev, proxy_sess);
pr_event_register(&proxy_module, "mod_sftp.ssh2.auth-password",
ssh_ssh2_auth_completed_ev, proxy_sess);
pr_event_register(&proxy_module, "mod_sftp.ssh2.auth-publickey",
ssh_ssh2_auth_completed_ev, proxy_sess);
} else {
pr_event_register(&proxy_module, "mod_sftp.ssh2.kex.completed",
ssh_ssh2_kex_completed_ev, proxy_sess);
}
if (proxy_ssh_auth_sess_init(p, proxy_sess) < 0) {
return -1;
}
#endif /* PR_USE_OPENSSL */
return 0;
}
int proxy_ssh_sess_free(pool *p) {
if (p == NULL) {
errno = EINVAL;
return -1;
}
#if defined(PR_USE_OPENSSL)
ssh_opts = 0UL;
if (ssh_ds.dsh != NULL) {
(void) (ssh_ds.close)(p, ssh_ds.dsh);
ssh_ds.dsh = NULL;
}
proxy_ssh_kex_sess_free();
pr_event_unregister(&proxy_module, "mod_sftp.ssh2.auth-hostbased",
ssh_ssh2_auth_completed_ev);
pr_event_unregister(&proxy_module, "mod_sftp.ssh2.auth-kbdint",
ssh_ssh2_auth_completed_ev);
pr_event_unregister(&proxy_module, "mod_sftp.ssh2.auth-password",
ssh_ssh2_auth_completed_ev);
pr_event_unregister(&proxy_module, "mod_sftp.ssh2.auth-publickey",
ssh_ssh2_auth_completed_ev);
pr_event_unregister(&proxy_module, "mod_sftp.ssh2.kex.completed",
ssh_ssh2_kex_completed_ev);
pr_event_unregister(&proxy_module, "mod_sftp.ssh2.read-poll",
ssh_ssh2_read_poll_ev);
#endif /* PR_USE_OPENSSL */
return 0;
}
proftpd-mod_proxy-0.9.5/lib/proxy/ssh/ 0000775 0000000 0000000 00000000000 14757370167 0017766 5 ustar 00root root 0000000 0000000 proftpd-mod_proxy-0.9.5/lib/proxy/ssh/agent.c 0000664 0000000 0000000 00000026017 14757370167 0021236 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH agent support
* Copyright (c) 2021-2023 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "proxy/ssh/agent.h"
#include "proxy/ssh/msg.h"
#if defined(PR_USE_OPENSSL)
static const char *trace_channel = "proxy.ssh.agent";
/* These values from https://tools.ietf.org/html/draft-miller-ssh-agent-04
*/
#define PROXY_SSH_AGENT_FAILURE 5
#define PROXY_SSH_AGENT_SUCCESS 6
#define PROXY_SSH_AGENT_REQ_IDS 11
#define PROXY_SSH_AGENT_RESP_IDS 12
#define PROXY_SSH_AGENT_REQ_SIGN_DATA 13
#define PROXY_SSH_AGENT_RESP_SIGN_DATA 14
#define PROXY_SSH_AGENT_EXTENDED_FAILURE 30
/* Error code for ssh.com's ssh-agent2 process. */
#define PROXY_SSHCOM_AGENT_FAILURE 102
/* Size of the buffer we use to talk to the agent. */
#define AGENT_REQUEST_MSGSZ 1024
/* Max size of the agent reply that we will handle. */
#define AGENT_REPLY_MAXSZ (256 * 1024)
/* Max number of identities/keys we're willing to handle at one time. */
#define AGENT_MAX_KEYS 1024
/* In proxy_ssh_keys_get_clientkey(), when dealing with the key data returned
* from the agent, use get_pkey_from_data() to create the EVP_PKEY. Keep
* the key_data around, for signing requests to send to the agent.
*/
static int agent_failure(char resp_status) {
int failed = FALSE;
switch (resp_status) {
case PROXY_SSH_AGENT_FAILURE:
case PROXY_SSH_AGENT_EXTENDED_FAILURE:
case PROXY_SSHCOM_AGENT_FAILURE:
failed = TRUE;
break;
}
return failed;
}
static unsigned char *agent_request(pool *p, int fd, const char *path,
unsigned char *req, uint32_t reqlen, uint32_t *resplen) {
unsigned char msg[AGENT_REQUEST_MSGSZ], *buf, *ptr;
uint32_t bufsz, buflen, len = 0;
size_t write_len;
int res;
bufsz = buflen = sizeof(msg);
buf = ptr = msg;
len += proxy_ssh_msg_write_int(&buf, &buflen, reqlen);
/* Send the message length to the agent. */
write_len = len;
res = write(fd, ptr, write_len);
if (res < 0) {
int xerrno = errno;
pr_trace_msg(trace_channel, 3,
"error sending request length to SSH agent at '%s': %s", path,
strerror(xerrno));
errno = xerrno;
return NULL;
}
/* Handle short writes. */
if ((size_t) res != write_len) {
pr_trace_msg(trace_channel, 3,
"short write (%d of %lu bytes sent) when talking to SSH agent at '%s'",
res, (unsigned long) (write_len), path);
errno = EIO;
return NULL;
}
/* Send the message payload to the agent. */
res = write(fd, req, reqlen);
if (res < 0) {
int xerrno = errno;
pr_trace_msg(trace_channel, 3,
"error sending request payload to SSH agent at '%s': %s", path,
strerror(xerrno));
errno = xerrno;
return NULL;
}
/* Handle short writes. */
if ((uint32_t) res != reqlen) {
pr_trace_msg(trace_channel, 3,
"short write (%d of %lu bytes sent) when talking to SSH agent at '%s'",
res, (unsigned long) reqlen, path);
errno = EIO;
return NULL;
}
/* Wait for a response from the server. */
/* XXX This needs a timeout, prevent a blocked/bad agent from stalling
* the client. Maybe just set an internal timer?
*/
res = read(fd, msg, sizeof(uint32_t));
if (res < 0) {
int xerrno = errno;
pr_trace_msg(trace_channel, 3,
"error reading response length from SSH agent at '%s': %s", path,
strerror(xerrno));
errno = xerrno;
return NULL;
}
/* Sanity check the returned length; we could be dealing with a buggy
* client (or something else is injecting data into the Unix domain socket).
* Best be conservative: if we get a response length of more than 256KB,
* it's too big. (What about very long lists of keys, and/or large keys?)
*/
if (res > AGENT_REPLY_MAXSZ) {
pr_trace_msg(trace_channel, 1,
"response length (%d) from SSH agent at '%s' exceeds maximum (%lu), "
"ignoring", res, path, (unsigned long) AGENT_REPLY_MAXSZ);
errno = EIO;
return NULL;
}
buf = msg;
buflen = res;
len = proxy_ssh_msg_read_int(p, &buf, &buflen, resplen);
bufsz = buflen = *resplen;
if (bufsz == 0 ||
bufsz > AGENT_REPLY_MAXSZ) {
pr_trace_msg(trace_channel, 1,
"response length (%lu) from SSH agent at '%s' exceeds maximum (%lu), "
"ignoring", (unsigned long) bufsz, path,
(unsigned long) AGENT_REPLY_MAXSZ);
errno = EIO;
return NULL;
}
buf = ptr = palloc(p, bufsz);
buflen = 0;
while (buflen != *resplen) {
pr_signals_handle();
res = read(fd, buf + buflen, bufsz - buflen);
if (res < 0) {
int xerrno = errno;
pr_trace_msg(trace_channel, 3,
"error reading %d bytes of response payload from SSH agent at '%s': %s",
(bufsz - buflen), path, strerror(xerrno));
errno = xerrno;
return NULL;
}
/* XXX Handle short reads? */
buflen += res;
}
return ptr;
}
static int agent_connect(const char *path) {
int fd, len, res, xerrno;
struct sockaddr_un sock;
memset(&sock, 0, sizeof(sock));
sock.sun_family = AF_UNIX;
sstrncpy(sock.sun_path, path, sizeof(sock.sun_path));
len = sizeof(sock);
fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0) {
xerrno = errno;
pr_trace_msg(trace_channel, 3, "error opening Unix domain socket: %s",
strerror(xerrno));
errno = xerrno;
return -1;
}
if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) {
pr_trace_msg(trace_channel, 3,
"error setting CLOEXEC on fd %d for talking to SSH agent: %s",
fd, strerror(errno));
}
PRIVS_ROOT
res = connect(fd, (struct sockaddr *) &sock, len);
xerrno = errno;
PRIVS_RELINQUISH
if (res < 0) {
pr_trace_msg(trace_channel, 2, "error connecting to SSH agent at '%s': %s",
path, strerror(xerrno));
(void) close(fd);
errno = xerrno;
return -1;
}
return fd;
}
int proxy_ssh_agent_get_keys(pool *p, const char *agent_path,
array_header *key_list) {
register unsigned int i;
int fd;
unsigned char *buf, *req, *resp;
uint32_t buflen, key_count, reqlen, reqsz, resplen, len = 0;
unsigned char resp_status;
fd = agent_connect(agent_path);
if (fd < 0) {
return -1;
}
/* Write out the request for the identities (i.e. the public keys). */
reqsz = buflen = 64;
req = buf = palloc(p, reqsz);
len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_AGENT_REQ_IDS);
reqlen = len;
resp = agent_request(p, fd, agent_path, req, reqlen, &resplen);
if (resp == NULL) {
int xerrno = errno;
(void) close(fd);
errno = xerrno;
return -1;
}
(void) close(fd);
/* Read the response from the agent. */
len = proxy_ssh_msg_read_byte(p, &resp, &resplen, &resp_status);
if (agent_failure(resp_status) == TRUE) {
pr_trace_msg(trace_channel, 5,
"SSH agent at '%s' indicated failure (%d) for identities request",
agent_path, resp_status);
errno = EPERM;
return -1;
}
if (resp_status != PROXY_SSH_AGENT_RESP_IDS) {
pr_trace_msg(trace_channel, 5,
"unknown response type %d from SSH agent at '%s'", resp_status,
agent_path);
errno = EACCES;
return -1;
}
len = proxy_ssh_msg_read_int(p, &resp, &resplen, &key_count);
if (key_count > AGENT_MAX_KEYS) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"SSH agent at '%s' returned too many keys (%lu, max %lu)", agent_path,
(unsigned long) key_count, (unsigned long) AGENT_MAX_KEYS);
errno = EPERM;
return -1;
}
for (i = 0; i < key_count; i++) {
unsigned char *key_data;
uint32_t key_datalen;
char *key_comment;
struct agent_key *key;
len = proxy_ssh_msg_read_int(p, &resp, &resplen, &key_datalen);
len = proxy_ssh_msg_read_data(p, &resp, &resplen, key_datalen, &key_data);
len = proxy_ssh_msg_read_string(p, &resp, &resplen, &key_comment);
if (key_comment != NULL) {
pr_trace_msg(trace_channel, 9,
"SSH agent at '%s' provided comment '%s' for key #%u", agent_path,
key_comment, (i + 1));
}
key = pcalloc(p, sizeof(struct agent_key));
key->key_data = key_data;
key->key_datalen = key_datalen;
key->agent_path = pstrdup(p, agent_path);
*((struct agent_key **) push_array(key_list)) = key;
}
pr_trace_msg(trace_channel, 9, "SSH agent at '%s' provided %lu %s",
agent_path, (unsigned long) key_count, key_count != 1 ? "keys" : "key");
return 0;
}
const unsigned char *proxy_ssh_agent_sign_data(pool *p, const char *agent_path,
const unsigned char *key_data, uint32_t key_datalen,
const unsigned char *data, uint32_t datalen, uint32_t *sig_datalen,
int flags) {
int fd;
unsigned char *buf, *req, *resp, *sig_data;
uint32_t buflen, sig_flags, reqlen, reqsz, resplen, len = 0;
unsigned char resp_status;
fd = agent_connect(agent_path);
if (fd < 0) {
return NULL;
}
/* XXX When to set flags to OLD_SIGNATURE? */
sig_flags = 0;
/* Write out the request for signing the given data. */
reqsz = buflen = 1 + key_datalen + 4 + datalen + 4 + 4;
req = buf = palloc(p, reqsz);
len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_AGENT_REQ_SIGN_DATA);
len += proxy_ssh_msg_write_data(&buf, &buflen, key_data, key_datalen, TRUE);
len += proxy_ssh_msg_write_data(&buf, &buflen, data, datalen, TRUE);
len += proxy_ssh_msg_write_int(&buf, &buflen, sig_flags);
reqlen = len;
resp = agent_request(p, fd, agent_path, req, reqlen, &resplen);
if (resp == NULL) {
int xerrno = errno;
(void) close(fd);
errno = xerrno;
return NULL;
}
(void) close(fd);
/* Read the response from the agent. */
len = proxy_ssh_msg_read_byte(p, &resp, &resplen, &resp_status);
if (agent_failure(resp_status) == TRUE) {
pr_trace_msg(trace_channel, 5,
"SSH agent at '%s' indicated failure (%d) for data signing request",
agent_path, resp_status);
errno = EPERM;
return NULL;
}
if (resp_status != PROXY_SSH_AGENT_RESP_SIGN_DATA) {
pr_trace_msg(trace_channel, 5,
"unknown response type %d from SSH agent at '%s'", resp_status,
agent_path);
errno = EACCES;
return NULL;
}
len = proxy_ssh_msg_read_int(p, &resp, &resplen, sig_datalen);
len = proxy_ssh_msg_read_data(p, &resp, &resplen, *sig_datalen, &sig_data);
return sig_data;
}
#endif /* PR_USE_OPENSSL */
proftpd-mod_proxy-0.9.5/lib/proxy/ssh/auth.c 0000664 0000000 0000000 00000107470 14757370167 0021104 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH user authentication
* Copyright (c) 2021-2022 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "proxy/ssh/ssh2.h"
#include "proxy/ssh/packet.h"
#include "proxy/ssh/msg.h"
#include "proxy/ssh/disconnect.h"
#include "proxy/ssh/interop.h"
#include "proxy/ssh/auth.h"
#include "proxy/ssh/crypto.h"
#include "proxy/ssh/cipher.h"
#include "proxy/ssh/mac.h"
#include "proxy/ssh/compress.h"
#include "proxy/ssh/session.h"
#include "proxy/ssh/keys.h"
#include "proxy/ssh/utf8.h"
#if defined(PR_USE_OPENSSL)
/* From response.c */
extern pr_response_t *resp_list, *resp_err_list;
static pool *auth_pool = NULL;
static const char *trace_channel = "proxy.ssh.auth";
static void dispatch_cmd_err(cmd_rec *cmd) {
pr_response_add_err(R_530, "Login incorrect.");
pr_cmd_dispatch_phase(cmd, POST_CMD_ERR, 0);
pr_cmd_dispatch_phase(cmd, LOG_CMD_ERR, 0);
pr_response_clear(&resp_err_list);
}
static int dispatch_user_cmd(pool *p, const char *orig_user,
char **new_user) {
cmd_rec *user_cmd;
user_cmd = pr_cmd_alloc(p, 2, pstrdup(p, C_USER), orig_user);
user_cmd->cmd_class = CL_AUTH|CL_SSH;
user_cmd->arg = (char *) orig_user;
pr_response_set_pool(user_cmd->pool);
/* Dispatch these as PRE_CMDs, so that mod_delay's tactics can be used
* to ameliorate any timing-based attacks.
*/
if (pr_cmd_dispatch_phase(user_cmd, PRE_CMD, 0) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"authentication request for user '%s' blocked by '%s' handler",
orig_user, (char *) user_cmd->argv[0]);
dispatch_cmd_err(user_cmd);
destroy_pool(user_cmd->pool);
pr_response_set_pool(NULL);
return -1;
}
if (strcmp(orig_user, user_cmd->arg) != 0) {
*new_user = pstrdup(p, user_cmd->arg);
}
pr_response_add(R_331, "Password required for %s", orig_user);
pr_cmd_dispatch_phase(user_cmd, POST_CMD, 0);
pr_cmd_dispatch_phase(user_cmd, LOG_CMD, 0);
pr_response_clear(&resp_list);
destroy_pool(user_cmd->pool);
pr_response_set_pool(NULL);
return 0;
}
static int dispatch_pass_cmd(pool *p, int success) {
cmd_rec *pass_cmd;
pass_cmd = pr_cmd_alloc(p, 1, pstrdup(p, C_PASS));
pass_cmd->cmd_class = CL_AUTH|CL_SSH;
pass_cmd->arg = pstrdup(pass_cmd->pool, "(hidden)");
pr_response_set_pool(pass_cmd->pool);
if (success == TRUE) {
pr_cmd_dispatch_phase(pass_cmd, POST_CMD, 0);
pr_cmd_dispatch_phase(pass_cmd, LOG_CMD, 0);
} else {
pr_cmd_dispatch_phase(pass_cmd, POST_CMD_ERR, 0);
pr_cmd_dispatch_phase(pass_cmd, LOG_CMD_ERR, 0);
}
pr_response_clear(&resp_list);
destroy_pool(pass_cmd->pool);
pr_response_set_pool(NULL);
return 0;
}
static struct proxy_ssh_packet *read_auth_packet(pool *p,
const struct proxy_session *proxy_sess) {
struct proxy_ssh_packet *pkt = NULL;
unsigned int poll_attempts;
unsigned long poll_timeout_ms;
int poll_timeout_secs, res, xerrno = 0;
char msg_type;
proxy_ssh_packet_get_poll_attempts(&poll_attempts);
proxy_ssh_packet_get_poll_timeout(&poll_timeout_secs, &poll_timeout_ms);
proxy_ssh_packet_set_poll_attempts(3);
proxy_ssh_packet_set_poll_timeout(0, 250);
pkt = proxy_ssh_packet_create(p);
res = proxy_ssh_packet_read(proxy_sess->backend_ctrl_conn, pkt);
if (res < 0) {
xerrno = errno;
proxy_ssh_packet_set_poll_attempts(poll_attempts);
proxy_ssh_packet_set_poll_timeout(poll_timeout_secs, poll_timeout_ms);
destroy_pool(pkt->pool);
errno = xerrno;
return NULL;
}
proxy_ssh_packet_set_poll_attempts(poll_attempts);
proxy_ssh_packet_set_poll_timeout(poll_timeout_secs, poll_timeout_ms);
msg_type = proxy_ssh_packet_peek_msg_type(pkt);
pr_trace_msg(trace_channel, 3, "received %s (%d) packet (from mod_%s.c)",
proxy_ssh_packet_get_msg_type_desc(msg_type), msg_type,
pkt->m->name);
return pkt;
}
/* Returns 0 if the packet was completely processed, 1 if the caller should
* process the packet, and -1 if the packet is invalid.
*/
static int process_auth_packet(struct proxy_ssh_packet *pkt,
const struct proxy_session *proxy_sess) {
char msg_type;
int res = 0;
msg_type = proxy_ssh_packet_peek_msg_type(pkt);
switch (msg_type) {
case PROXY_SSH_MSG_USER_AUTH_SUCCESS:
case PROXY_SSH_MSG_USER_AUTH_FAILURE:
res = 1;
break;
case PROXY_SSH_MSG_USER_AUTH_BANNER:
proxy_ssh_packet_log_cmd(pkt, FALSE);
proxy_ssh_packet_proxied(proxy_sess, pkt, FALSE);
destroy_pool(pkt->pool);
res = 0;
break;
case PROXY_SSH_MSG_DEBUG:
case PROXY_SSH_MSG_DISCONNECT:
case PROXY_SSH_MSG_IGNORE:
case PROXY_SSH_MSG_UNIMPLEMENTED:
proxy_ssh_packet_handle(pkt);
res = 0;
break;
default:
errno = EINVAL;
res = -1;
break;
}
return res;
}
static int handle_userauth_none(struct proxy_ssh_packet *pkt,
const struct proxy_session *proxy_sess) {
int res;
const char *methods;
unsigned char *buf, *ptr;
uint32_t bufsz, buflen, len = 0;
/* Ideally, we would query the backend server ourselves, and synthesize the
* full list of available methods for the frontend client. For now, however,
* return a response listing all implemented methods.
*/
destroy_pool(pkt->pool);
pkt = proxy_ssh_packet_create(auth_pool);
bufsz = buflen = 1024;
ptr = buf = palloc(pkt->pool, bufsz);
len += proxy_ssh_msg_write_byte(&buf, &buflen,
PROXY_SSH_MSG_USER_AUTH_FAILURE);
if (proxy_ssh_keys_have_hostkey(PROXY_SSH_KEY_UNKNOWN) == 0) {
methods = "password,keyboard-interactive,publickey,hostbased";
} else {
/* If we have no configured ProxySFTPHostKeys, do not include the
* "hostbased" method.
*/
methods = "password,keyboard-interactive,publickey";
}
len += proxy_ssh_msg_write_string(&buf, &buflen, methods);
len += proxy_ssh_msg_write_bool(&buf, &buflen, FALSE);
pkt->payload = ptr;
pkt->payload_len = len;
res = proxy_ssh_packet_write_frontend(proxy_sess->frontend_ctrl_conn, pkt);
if (res < 0 &&
errno != ENOSYS) {
destroy_pool(pkt->pool);
return -1;
}
destroy_pool(pkt->pool);
return 0;
}
static const unsigned char *write_userauth_signed_data(pool *p,
unsigned char *data, uint32_t datalen, size_t *sig_datalen) {
unsigned char *buf, *ptr;
const unsigned char *session_id;
uint32_t bufsz, buflen, session_idlen, len = 0;
/* XXX Is this buffer large enough? Too large? */
bufsz = buflen = 2048;
ptr = buf = palloc(p, bufsz);
/* Write the session ID. */
session_idlen = proxy_ssh_session_get_id(&session_id);
len += proxy_ssh_msg_write_data(&buf, &buflen, session_id, session_idlen,
TRUE);
/* Write the given data. */
len += proxy_ssh_msg_write_data(&buf, &buflen, data, datalen, FALSE);
*sig_datalen = len;
return ptr;
}
static int write_userauth_hostbased(struct proxy_ssh_packet *pkt,
const char *user, const char *service) {
unsigned char *buf, *ptr;
const unsigned char *hostkey_data, *sig_data, *signature;
uint32_t bufsz, buflen, hostkey_datalen, len = 0;
size_t signature_len, sig_datalen;
const char *hostkey_algo = NULL, *hostname;
enum proxy_ssh_key_type_e use_hostkey_type = PROXY_SSH_KEY_UNKNOWN;
/* XXX Is this buffer large enough? Too large? */
bufsz = buflen = 8192;
ptr = buf = palloc(pkt->pool, bufsz);
/* Retrieve our hostkey. We probe for our available hostkeys in preference
* order:
*
* Ed25519
* ECDSA521
* ECDSA384
* ECDSA256
* RSA
* DSA
*
* Note that RFC 8308 and the "server-sig-algs" EXT_INFO extension, for
* SHA256/512 signatures using RSA keys, only applies to "publickey"
* authentication requests, not "hostbased" -- hence why we do not probe
* for those combinations.
*/
len += proxy_ssh_msg_write_byte(&buf, &buflen,
PROXY_SSH_MSG_USER_AUTH_REQUEST);
len += proxy_ssh_msg_write_string(&buf, &buflen, pstrdup(pkt->pool, user));
len += proxy_ssh_msg_write_string(&buf, &buflen, pstrdup(pkt->pool, service));
len += proxy_ssh_msg_write_string(&buf, &buflen, pstrdup(pkt->pool,
"hostbased"));
if (proxy_ssh_keys_have_hostkey(PROXY_SSH_KEY_ED448) == 0) {
use_hostkey_type = PROXY_SSH_KEY_ED448;
hostkey_algo = "ssh-ed448";
} else if (proxy_ssh_keys_have_hostkey(PROXY_SSH_KEY_ED25519) == 0) {
use_hostkey_type = PROXY_SSH_KEY_ED25519;
hostkey_algo = "ssh-ed25519";
} else if (proxy_ssh_keys_have_hostkey(PROXY_SSH_KEY_ECDSA_521) == 0) {
use_hostkey_type = PROXY_SSH_KEY_ECDSA_521;
hostkey_algo = "ecdsa-sha2-nistp521";
} else if (proxy_ssh_keys_have_hostkey(PROXY_SSH_KEY_ECDSA_384) == 0) {
use_hostkey_type = PROXY_SSH_KEY_ECDSA_384;
hostkey_algo = "ecdsa-sha2-nistp384";
} else if (proxy_ssh_keys_have_hostkey(PROXY_SSH_KEY_ECDSA_256) == 0) {
use_hostkey_type = PROXY_SSH_KEY_ECDSA_256;
hostkey_algo = "ecdsa-sha2-nistp256";
} else if (proxy_ssh_keys_have_hostkey(PROXY_SSH_KEY_RSA) == 0) {
use_hostkey_type = PROXY_SSH_KEY_RSA;
hostkey_algo = "ssh-rsa";
} else if (proxy_ssh_keys_have_hostkey(PROXY_SSH_KEY_DSA) == 0) {
use_hostkey_type = PROXY_SSH_KEY_DSA;
hostkey_algo = "ssh-dss";
}
hostkey_data = proxy_ssh_keys_get_hostkey_data(pkt->pool,
use_hostkey_type, &hostkey_datalen);
if (hostkey_data == NULL) {
return -1;
}
len += proxy_ssh_msg_write_string(&buf, &buflen, hostkey_algo);
len += proxy_ssh_msg_write_data(&buf, &buflen, hostkey_data, hostkey_datalen,
TRUE);
hostname = pr_netaddr_get_localaddr_str(pkt->pool);
len += proxy_ssh_msg_write_string(&buf, &buflen, hostname);
len += proxy_ssh_msg_write_string(&buf, &buflen, pstrdup(pkt->pool, user));
sig_data = write_userauth_signed_data(pkt->pool, ptr, len, &sig_datalen);
if (sig_data == NULL) {
return -1;
}
signature = proxy_ssh_keys_sign_data(pkt->pool, use_hostkey_type,
sig_data, sig_datalen, &signature_len);
len += proxy_ssh_msg_write_data(&buf, &buflen, signature, signature_len,
TRUE);
pkt->payload = ptr;
pkt->payload_len = len;
return 0;
}
static int handle_userauth_hostbased(struct proxy_ssh_packet *pkt,
const struct proxy_session *proxy_sess) {
int res, xerrno, success = FALSE;
unsigned char *buf;
uint32_t buflen;
char *orig_user, *new_user = NULL, *user, *service;
pool *tmp_pool;
/* We cannot send this "hostbased" USER_AUTH_REQUEST packet to the backend
* server as-is, since the signed data involves the *frontend* session ID --
* which the backend server will not have.
*
* Thus to support this, we need our own ProxySFTPHostKey.
*/
buf = pkt->payload;
buflen = pkt->payload_len;
/* Skip past the message type. */
buf += sizeof(char);
buflen -= sizeof(char);
/* We only need to copy the user name, service name from the frontend packet;
* we can ignore the rest.
*/
proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &orig_user);
/* If mod_sftp has already authenticated the client (as for PerUser/PerGroup
* connect policies), then we need not dispatch USER/PASS commands again.
*/
if (session.auth_mech == NULL) {
res = dispatch_user_cmd(pkt->pool, orig_user, &new_user);
if (res < 0) {
destroy_pool(pkt->pool);
return -1;
}
}
proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &service);
tmp_pool = make_sub_pool(auth_pool);
user = pstrdup(tmp_pool, new_user != NULL ? new_user : orig_user);
service = pstrdup(tmp_pool, service);
destroy_pool(pkt->pool);
if (session.auth_mech == NULL) {
(void) pr_table_remove(session.notes, "mod_auth.orig-user", NULL);
if (pr_table_add_dup(session.notes, "mod_auth.orig-user", user, 0) < 0 &&
errno != EEXIST) {
pr_log_debug(DEBUG3, "error stashing 'mod_auth.orig-user' in "
"session.notes: %s", strerror(errno));
}
}
if (proxy_ssh_keys_have_hostkey(PROXY_SSH_KEY_UNKNOWN) != 0) {
unsigned char *ptr;
uint32_t bufsz, len = 0;
const char *methods;
/* We have no configured hostkeys, thus cannot use "hostbased"
* authentication; return FAILURE to the frontend client.
*/
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to handle client-requested hostbased authentication: "
"no ProxySFTPHostKeys configured");
pr_trace_msg(trace_channel, 9,
"writing USER_AUTH_FAILURE message to client");
pkt = proxy_ssh_packet_create(auth_pool);
bufsz = buflen = 1024;
ptr = buf = palloc(pkt->pool, bufsz);
len += proxy_ssh_msg_write_byte(&buf, &buflen,
PROXY_SSH_MSG_USER_AUTH_FAILURE);
methods = "password,keyboard-interactive,publickey";
len += proxy_ssh_msg_write_string(&buf, &buflen, methods);
len += proxy_ssh_msg_write_bool(&buf, &buflen, FALSE);
pkt->payload = ptr;
pkt->payload_len = len;
res = proxy_ssh_packet_write_frontend(proxy_sess->frontend_ctrl_conn, pkt);
if (res < 0 &&
errno != ENOSYS) {
destroy_pool(pkt->pool);
return -1;
}
return 0;
}
pr_trace_msg(trace_channel, 9,
"writing USER_AUTH_REQUEST hostbased message to server");
pkt = proxy_ssh_packet_create(auth_pool);
res = write_userauth_hostbased(pkt, user, service);
if (res < 0) {
xerrno = errno;
destroy_pool(pkt->pool);
destroy_pool(tmp_pool);
errno = xerrno;
return -1;
}
res = proxy_ssh_packet_write(proxy_sess->backend_ctrl_conn, pkt);
if (res < 0) {
xerrno = errno;
destroy_pool(pkt->pool);
destroy_pool(tmp_pool);
errno = xerrno;
return -1;
}
destroy_pool(pkt->pool);
destroy_pool(tmp_pool);
while (TRUE) {
char msg_type;
pr_signals_handle();
pkt = read_auth_packet(auth_pool, proxy_sess);
if (pkt == NULL) {
return -1;
}
msg_type = proxy_ssh_packet_peek_msg_type(pkt);
/* Handle the hostbased-specific message types, if any, here. */
res = process_auth_packet(pkt, proxy_sess);
if (res < 0) {
destroy_pool(pkt->pool);
/* Invalid protocol sequence */
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"received unexpected %s packet during SSH authentication, failing",
proxy_ssh_packet_get_msg_type_desc(msg_type));
errno = ENOSYS;
return -1;
}
if (res == 0) {
continue;
}
/* If we reach here, it should be for USER_AUTH_SUCCESS/FAILURE packets, to
* send to the frontend client.
*/
proxy_ssh_packet_log_cmd(pkt, FALSE);
if (msg_type == PROXY_SSH_MSG_USER_AUTH_SUCCESS) {
success = TRUE;
}
break;
}
res = proxy_ssh_packet_proxied(proxy_sess, pkt, FALSE);
if (res < 0) {
xerrno = errno;
destroy_pool(pkt->pool);
errno = xerrno;
return -1;
}
destroy_pool(pkt->pool);
return success;
}
static int write_userauth_kbdint(struct proxy_ssh_packet *pkt,
const char *user, const char *service, const char *language,
const char *submethods) {
unsigned char *buf, *ptr;
uint32_t bufsz, buflen, len = 0;
/* XXX Is this buffer large enough? Too large? */
bufsz = buflen = 8192;
ptr = buf = palloc(pkt->pool, bufsz);
len += proxy_ssh_msg_write_byte(&buf, &buflen,
PROXY_SSH_MSG_USER_AUTH_REQUEST);
len += proxy_ssh_msg_write_string(&buf, &buflen, pstrdup(pkt->pool, user));
len += proxy_ssh_msg_write_string(&buf, &buflen, pstrdup(pkt->pool, service));
len += proxy_ssh_msg_write_string(&buf, &buflen, pstrdup(pkt->pool,
"keyboard-interactive"));
len += proxy_ssh_msg_write_string(&buf, &buflen, pstrdup(pkt->pool,
language));
len += proxy_ssh_msg_write_string(&buf, &buflen, pstrdup(pkt->pool,
submethods));
pkt->payload = ptr;
pkt->payload_len = len;
return 0;
}
static int handle_userauth_kbdint(struct proxy_ssh_packet *pkt,
const struct proxy_session *proxy_sess) {
int res, xerrno, success = FALSE;
unsigned char *buf;
uint32_t buflen;
char *orig_user, *new_user = NULL;
buf = pkt->payload;
buflen = pkt->payload_len;
/* Skip past the message type. */
buf += sizeof(char);
buflen -= sizeof(char);
proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &orig_user);
res = dispatch_user_cmd(pkt->pool, orig_user, &new_user);
if (res < 0) {
destroy_pool(pkt->pool);
return -1;
}
if (new_user == NULL) {
/* No changes to the user; we can proxy the packet as is. */
(void) pr_table_remove(session.notes, "mod_auth.orig-user", NULL);
res = pr_table_add_dup(session.notes, "mod_auth.orig-user", orig_user, 0);
if (res < 0 &&
errno != EEXIST) {
pr_log_debug(DEBUG3, "error stashing 'mod_auth.orig-user' in "
"session.notes: %s", strerror(errno));
}
res = proxy_ssh_packet_proxied(proxy_sess, pkt, TRUE);
if (res < 0) {
destroy_pool(pkt->pool);
return -1;
}
} else {
char *user, *service, *method, *language, *submethods;
pool *tmp_pool;
/* The username changed; we need to write a new packet. */
proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &service);
proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &method);
proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &language);
proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &submethods);
tmp_pool = make_sub_pool(auth_pool);
user = pstrdup(tmp_pool, new_user);
service = pstrdup(tmp_pool, service);
language = pstrdup(tmp_pool, language);
submethods = pstrdup(tmp_pool, submethods);
destroy_pool(pkt->pool);
(void) pr_table_remove(session.notes, "mod_auth.orig-user", NULL);
if (pr_table_add_dup(session.notes, "mod_auth.orig-user", user, 0) < 0 &&
errno != EEXIST) {
pr_log_debug(DEBUG3, "error stashing 'mod_auth.orig-user' in "
"session.notes: %s", strerror(errno));
}
pkt = proxy_ssh_packet_create(auth_pool);
res = write_userauth_kbdint(pkt, user, service, language, submethods);
if (res < 0) {
xerrno = errno;
destroy_pool(pkt->pool);
destroy_pool(tmp_pool);
errno = xerrno;
return -1;
}
res = proxy_ssh_packet_write(proxy_sess->backend_ctrl_conn, pkt);
if (res < 0) {
xerrno = errno;
destroy_pool(pkt->pool);
destroy_pool(tmp_pool);
errno = xerrno;
return -1;
}
destroy_pool(tmp_pool);
}
destroy_pool(pkt->pool);
while (TRUE) {
char msg_type;
pr_signals_handle();
pkt = read_auth_packet(auth_pool, proxy_sess);
if (pkt == NULL) {
return -1;
}
msg_type = proxy_ssh_packet_peek_msg_type(pkt);
/* Handle the kbdint-specific message types, if any, here. */
if (msg_type == PROXY_SSH_MSG_USER_AUTH_INFO_REQ) {
proxy_ssh_packet_log_cmd(pkt, FALSE);
res = proxy_ssh_packet_proxied(proxy_sess, pkt, FALSE);
destroy_pool(pkt->pool);
if (res < 0) {
return -1;
}
return 0;
}
res = process_auth_packet(pkt, proxy_sess);
if (res < 0) {
destroy_pool(pkt->pool);
/* Invalid protocol sequence */
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"received unexpected %s packet during SSH authentication, failing",
proxy_ssh_packet_get_msg_type_desc(msg_type));
errno = ENOSYS;
return -1;
}
if (res == 0) {
continue;
}
/* If we reach here, it should be for USER_AUTH_SUCCESS/FAILURE packets, to
* send to the frontend client.
*/
proxy_ssh_packet_log_cmd(pkt, FALSE);
if (msg_type == PROXY_SSH_MSG_USER_AUTH_SUCCESS) {
success = TRUE;
}
break;
}
res = proxy_ssh_packet_proxied(proxy_sess, pkt, FALSE);
if (res < 0) {
xerrno = errno;
destroy_pool(pkt->pool);
errno = xerrno;
return -1;
}
destroy_pool(pkt->pool);
return success;
}
static int write_userauth_password(struct proxy_ssh_packet *pkt,
const char *user, const char *service, const char *password) {
unsigned char *buf, *ptr;
uint32_t bufsz, buflen, len = 0;
/* XXX Is this buffer large enough? Too large? */
bufsz = buflen = 8192;
ptr = buf = palloc(pkt->pool, bufsz);
len += proxy_ssh_msg_write_byte(&buf, &buflen,
PROXY_SSH_MSG_USER_AUTH_REQUEST);
len += proxy_ssh_msg_write_string(&buf, &buflen, pstrdup(pkt->pool, user));
len += proxy_ssh_msg_write_string(&buf, &buflen, pstrdup(pkt->pool, service));
len += proxy_ssh_msg_write_string(&buf, &buflen, pstrdup(pkt->pool,
"password"));
len += proxy_ssh_msg_write_bool(&buf, &buflen, FALSE);
len += proxy_ssh_msg_write_string(&buf, &buflen, pstrdup(pkt->pool,
password));
pkt->payload = ptr;
pkt->payload_len = len;
return 0;
}
static int handle_userauth_password(struct proxy_ssh_packet *pkt,
const struct proxy_session *proxy_sess) {
int res, xerrno, with_password, success = FALSE;
unsigned char *buf;
uint32_t buflen;
char *orig_user, *new_user = NULL;
buf = pkt->payload;
buflen = pkt->payload_len;
/* Skip past the message type. */
buf += sizeof(char);
buflen -= sizeof(char);
proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &orig_user);
res = dispatch_user_cmd(pkt->pool, orig_user, &new_user);
if (res < 0) {
destroy_pool(pkt->pool);
return -1;
}
if (new_user == NULL) {
/* No changes to the user; we can proxy the packet as is. */
(void) pr_table_remove(session.notes, "mod_auth.orig-user", NULL);
res = pr_table_add_dup(session.notes, "mod_auth.orig-user", orig_user, 0);
if (res < 0 &&
errno != EEXIST) {
pr_log_debug(DEBUG3, "error stashing 'mod_auth.orig-user' in "
"session.notes: %s", strerror(errno));
}
res = proxy_ssh_packet_proxied(proxy_sess, pkt, TRUE);
if (res < 0) {
destroy_pool(pkt->pool);
return -1;
}
} else {
char *user, *service, *method, *password;
pool *tmp_pool;
/* The username changed; we need to write a new packet. */
proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &service);
proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &method);
proxy_ssh_msg_read_bool(pkt->pool, &buf, &buflen, &with_password);
proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &password);
tmp_pool = make_sub_pool(auth_pool);
user = pstrdup(tmp_pool, new_user);
service = pstrdup(tmp_pool, service);
password = pstrdup(tmp_pool, password);
destroy_pool(pkt->pool);
(void) pr_table_remove(session.notes, "mod_auth.orig-user", NULL);
if (pr_table_add_dup(session.notes, "mod_auth.orig-user", user, 0) < 0 &&
errno != EEXIST) {
pr_log_debug(DEBUG3, "error stashing 'mod_auth.orig-user' in "
"session.notes: %s", strerror(errno));
}
pkt = proxy_ssh_packet_create(auth_pool);
res = write_userauth_password(pkt, user, service, password);
if (res < 0) {
xerrno = errno;
pr_memscrub(password, strlen(password));
destroy_pool(pkt->pool);
destroy_pool(tmp_pool);
errno = xerrno;
return -1;
}
res = proxy_ssh_packet_write(proxy_sess->backend_ctrl_conn, pkt);
if (res < 0) {
xerrno = errno;
pr_memscrub(password, strlen(password));
destroy_pool(pkt->pool);
destroy_pool(tmp_pool);
errno = xerrno;
return -1;
}
pr_memscrub(password, strlen(password));
destroy_pool(tmp_pool);
}
destroy_pool(pkt->pool);
while (TRUE) {
char msg_type;
pr_signals_handle();
pkt = read_auth_packet(auth_pool, proxy_sess);
if (pkt == NULL) {
return -1;
}
msg_type = proxy_ssh_packet_peek_msg_type(pkt);
/* Handle the password-specific message types here. */
if (msg_type == PROXY_SSH_MSG_USER_AUTH_PASSWD) {
proxy_ssh_packet_log_cmd(pkt, FALSE);
res = proxy_ssh_packet_proxied(proxy_sess, pkt, FALSE);
destroy_pool(pkt->pool);
if (res < 0) {
return -1;
}
continue;
}
res = process_auth_packet(pkt, proxy_sess);
if (res < 0) {
destroy_pool(pkt->pool);
/* Invalid protocol sequence */
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"received unexpected %s packet during SSH authentication, failing",
proxy_ssh_packet_get_msg_type_desc(msg_type));
errno = ENOSYS;
return -1;
}
if (res == 0) {
continue;
}
/* If we reach here, it should be for USER_AUTH_SUCCESS/FAILURE packets, to
* send to the frontend client.
*/
proxy_ssh_packet_log_cmd(pkt, FALSE);
if (msg_type == PROXY_SSH_MSG_USER_AUTH_SUCCESS) {
success = TRUE;
}
break;
}
res = proxy_ssh_packet_proxied(proxy_sess, pkt, FALSE);
if (res < 0) {
xerrno = errno;
destroy_pool(pkt->pool);
errno = xerrno;
return -1;
}
destroy_pool(pkt->pool);
return success;
}
static int write_pk_ok(struct proxy_ssh_packet *pkt, const char *publickey_algo,
unsigned char *publickey_blob, uint32_t publickey_bloblen) {
unsigned char *buf, *ptr;
uint32_t bufsz, buflen, len = 0;
/* XXX Is this buffer large enough? Too large? */
bufsz = buflen = 8192;
ptr = buf = palloc(pkt->pool, bufsz);
len += proxy_ssh_msg_write_byte(&buf, &buflen,
PROXY_SSH_MSG_USER_AUTH_PK_OK);
len += proxy_ssh_msg_write_string(&buf, &buflen, pstrdup(pkt->pool,
publickey_algo));
len += proxy_ssh_msg_write_data(&buf, &buflen, publickey_blob,
publickey_bloblen, TRUE);
pkt->payload = ptr;
pkt->payload_len = len;
return 0;
}
/* We will use "hostbased" authentication to the backend, but we still need to
* fulfill the "publickey" authentication protocol to the frontend client.
*/
static int handle_userauth_publickey(struct proxy_ssh_packet *pkt,
const struct proxy_session *proxy_sess) {
int res, xerrno, success = FALSE, with_signature = FALSE;
unsigned char *buf, *buf2, *publickey_blob;
uint32_t buflen, publickey_bloblen;
char *orig_user, *new_user = NULL, *user, *service, *method, *publickey_algo;
pool *tmp_pool;
/* We cannot send this "publickey" USER_AUTH_REQUEST packet to the backend
* server as-is, since the signed data involves the *frontend* session ID --
* which the backend server will not have.
*
* If this publickey request contains the signature, we will send our
* hostbased request to the backend server, and return SUCCESS. Otherwise,
* we will respond to the frontend client, asking them to send the
* signature.
*/
buf = pkt->payload;
buflen = pkt->payload_len;
/* Skip past the message type. */
buf += sizeof(char);
buflen -= sizeof(char);
proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &orig_user);
res = dispatch_user_cmd(pkt->pool, orig_user, &new_user);
if (res < 0) {
destroy_pool(pkt->pool);
return -1;
}
proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &service);
proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &method);
proxy_ssh_msg_read_bool(pkt->pool, &buf, &buflen, &with_signature);
proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &publickey_algo);
proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &publickey_bloblen);
proxy_ssh_msg_read_data(pkt->pool, &buf, &buflen, publickey_bloblen,
&publickey_blob);
tmp_pool = make_sub_pool(auth_pool);
user = pstrdup(tmp_pool, new_user != NULL ? new_user : orig_user);
service = pstrdup(tmp_pool, service);
publickey_algo = pstrdup(tmp_pool, publickey_algo);
buf2 = palloc(tmp_pool, publickey_bloblen);
memcpy(buf2, publickey_blob, publickey_bloblen);
publickey_blob = buf2;
destroy_pool(pkt->pool);
(void) pr_table_remove(session.notes, "mod_auth.orig-user", NULL);
if (pr_table_add_dup(session.notes, "mod_auth.orig-user", user, 0) < 0 &&
errno != EEXIST) {
pr_log_debug(DEBUG3, "error stashing 'mod_auth.orig-user' in "
"session.notes: %s", strerror(errno));
}
if (proxy_ssh_keys_have_hostkey(PROXY_SSH_KEY_UNKNOWN) != 0) {
unsigned char *ptr;
uint32_t bufsz, len = 0;
const char *methods;
/* We have no configured hostkeys, thus cannot use "hostbased"
* authentication; return FAILURE to the frontend client.
*/
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to handle client-requested publickey authentication: "
"no ProxySFTPHostKeys configured");
pr_trace_msg(trace_channel, 9,
"writing USER_AUTH_FAILURE message to client");
pkt = proxy_ssh_packet_create(auth_pool);
bufsz = buflen = 1024;
ptr = buf = palloc(pkt->pool, bufsz);
len += proxy_ssh_msg_write_byte(&buf, &buflen,
PROXY_SSH_MSG_USER_AUTH_FAILURE);
methods = "password,keyboard-interactive,hostbased";
len += proxy_ssh_msg_write_string(&buf, &buflen, methods);
len += proxy_ssh_msg_write_bool(&buf, &buflen, FALSE);
pkt->payload = ptr;
pkt->payload_len = len;
res = proxy_ssh_packet_write_frontend(proxy_sess->frontend_ctrl_conn, pkt);
if (res < 0 &&
errno != ENOSYS) {
destroy_pool(pkt->pool);
return -1;
}
return 0;
}
if (with_signature == TRUE) {
pr_trace_msg(trace_channel, 9,
"publickey request includes signature, writing USER_AUTH_REQUEST "
"hostbased message to server");
pkt = proxy_ssh_packet_create(auth_pool);
res = write_userauth_hostbased(pkt, user, service);
if (res < 0) {
xerrno = errno;
destroy_pool(pkt->pool);
destroy_pool(tmp_pool);
errno = xerrno;
return -1;
}
res = proxy_ssh_packet_write(proxy_sess->backend_ctrl_conn, pkt);
if (res < 0) {
xerrno = errno;
destroy_pool(pkt->pool);
destroy_pool(tmp_pool);
errno = xerrno;
return -1;
}
} else {
pr_trace_msg(trace_channel, 9,
"publickey request does not include signature, writing USER_AUTH_PK_OK "
"message to client");
pkt = proxy_ssh_packet_create(auth_pool);
res = write_pk_ok(pkt, publickey_algo, publickey_blob, publickey_bloblen);
if (res < 0) {
xerrno = errno;
destroy_pool(pkt->pool);
destroy_pool(tmp_pool);
errno = xerrno;
return -1;
}
res = proxy_ssh_packet_write_frontend(proxy_sess->frontend_ctrl_conn, pkt);
if (res < 0 &&
errno != ENOSYS) {
xerrno = errno;
destroy_pool(pkt->pool);
destroy_pool(tmp_pool);
errno = xerrno;
return -1;
}
destroy_pool(pkt->pool);
destroy_pool(tmp_pool);
return 0;
}
destroy_pool(pkt->pool);
destroy_pool(tmp_pool);
while (TRUE) {
char msg_type;
pr_signals_handle();
pkt = read_auth_packet(auth_pool, proxy_sess);
if (pkt == NULL) {
return -1;
}
msg_type = proxy_ssh_packet_peek_msg_type(pkt);
/* Handle the publickey-specific message types, if any, here. */
res = process_auth_packet(pkt, proxy_sess);
if (res < 0) {
destroy_pool(pkt->pool);
/* Invalid protocol sequence */
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"received unexpected %s packet during SSH authentication, failing",
proxy_ssh_packet_get_msg_type_desc(msg_type));
errno = ENOSYS;
return -1;
}
if (res == 0) {
continue;
}
/* If we reach here, it should be for USER_AUTH_SUCCESS/FAILURE packets, to
* send to the frontend client.
*/
proxy_ssh_packet_log_cmd(pkt, FALSE);
if (msg_type == PROXY_SSH_MSG_USER_AUTH_SUCCESS) {
success = TRUE;
}
break;
}
res = proxy_ssh_packet_proxied(proxy_sess, pkt, FALSE);
if (res < 0) {
xerrno = errno;
destroy_pool(pkt->pool);
errno = xerrno;
return -1;
}
destroy_pool(pkt->pool);
return success;
}
int proxy_ssh_auth_handle(struct proxy_ssh_packet *pkt,
const struct proxy_session *proxy_sess) {
char msg_type, *user = NULL, *service = NULL, *method = NULL;
unsigned char *buf = NULL;
uint32_t buflen = 0;
int success = FALSE;
msg_type = proxy_ssh_packet_peek_msg_type(pkt);
buf = pkt->payload;
buflen = pkt->payload_len;
/* Skip past the message type. */
buf += sizeof(char);
buflen -= sizeof(char);
if (msg_type == PROXY_SSH_MSG_USER_AUTH_REQUEST) {
uint32_t len;
len = proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &user);
len = proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &service);
len = proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &method);
pr_trace_msg(trace_channel, 10,
"auth requested for user '%s', service '%s', using method '%s'", user,
service, method);
if (strcmp(method, "none") == 0) {
success = handle_userauth_none(pkt, proxy_sess);
} else if (strcmp(method, "hostbased") == 0) {
success = handle_userauth_hostbased(pkt, proxy_sess);
} else if (strcmp(method, "keyboard-interactive") == 0) {
success = handle_userauth_kbdint(pkt, proxy_sess);
} else if (strcmp(method, "password") == 0) {
success = handle_userauth_password(pkt, proxy_sess);
} else if (strcmp(method, "publickey") == 0) {
success = handle_userauth_publickey(pkt, proxy_sess);
} else {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to handle SSH_MSG_USER_AUTH_REQUEST message: "
"unknown/unsupported method '%s' requested", method);
errno = EINVAL;
return -1;
}
} else if (msg_type == PROXY_SSH_MSG_USER_AUTH_INFO_RESP) {
pr_trace_msg(trace_channel, 17,
"handling USER_AUTH_INFO_RESPONSE");
success = handle_userauth_kbdint(pkt, proxy_sess);
}
if (success == TRUE) {
int res;
const char *orig_user;
(void) pr_timer_remove(PR_TIMER_LOGIN, ANY_MODULE);
if (session.auth_mech == NULL) {
orig_user = pr_table_get(session.notes, "mod_auth.orig-user", NULL);
res = proxy_session_setup_env(proxy_pool, orig_user, 0);
if (res < 0) {
errno = EINVAL;
return -1;
}
}
/* We call the compression init routines here as well, in case the
* server selected "delayed" compression.
*/
proxy_ssh_compress_init_read(PROXY_SSH_COMPRESS_FL_AUTHENTICATED);
proxy_ssh_compress_init_write(PROXY_SSH_COMPRESS_FL_AUTHENTICATED);
}
/* If mod_sftp has already authenticated the client (as for PerUser/PerGroup
* connect policies), then we need not dispatch USER/PASS commands again.
*/
if (session.auth_mech == NULL) {
dispatch_pass_cmd(proxy_pool, success);
}
return success;
}
int proxy_ssh_auth_init(pool *p) {
if (auth_pool == NULL) {
auth_pool = make_sub_pool(p);
pr_pool_tag(auth_pool, "Proxy SSH Auth Pool");
}
return 0;
}
int proxy_ssh_auth_sess_init(pool *p, const struct proxy_session *proxy_sess) {
/* Currently unused. */
(void) p;
(void) proxy_sess;
return 0;
}
int proxy_ssh_auth_set_frontend_success_handle(pool *p,
int (*success_handle)(pool *p, const char *user)) {
const char *hook_symbol;
cmdtable *sftp_cmdtab;
cmd_rec *cmd;
modret_t *result;
hook_symbol = "sftp_set_auth_success_handler";
sftp_cmdtab = pr_stash_get_symbol2(PR_SYM_HOOK, hook_symbol, NULL, NULL,
NULL);
if (sftp_cmdtab == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to find SFTP hook symbol '%s'", hook_symbol);
errno = ENOENT;
return -1;
}
cmd = pr_cmd_alloc(p, 1, NULL);
cmd->argv[0] = (void *) success_handle;
result = pr_module_call(sftp_cmdtab->m, sftp_cmdtab->handler, cmd);
if (result == NULL ||
MODRET_ISERROR(result)) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error setting Proxy SSH Auth success handler");
errno = EPERM;
return -1;
}
return 0;
}
#endif /* PR_USE_OPENSSL */
proftpd-mod_proxy-0.9.5/lib/proxy/ssh/bcrypt.c 0000664 0000000 0000000 00000015007 14757370167 0021440 0 ustar 00root root 0000000 0000000 /*
* Copyright (c) 2013 Ted Unangst
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include
#include
#include
#include "openbsd-blowfish.h"
#include "proxy/ssh/bcrypt.h"
#include "proxy/ssh/crypto.h"
#if defined(PR_USE_OPENSSL)
#include
#define MINIMUM(a,b) (((a) < (b)) ? (a) : (b))
/*
* pkcs #5 pbkdf2 implementation using the "bcrypt" hash
*
* The bcrypt hash function is derived from the bcrypt password hashing
* function with the following modifications:
* 1. The input password and salt are preprocessed with SHA512.
* 2. The output length is expanded to 256 bits.
* 3. Subsequently the magic string to be encrypted is lengthened and modifed
* to "OxychromaticBlowfishSwatDynamite"
* 4. The hash function is defined to perform 64 rounds of initial state
* expansion. (More rounds are performed by iterating the hash.)
*
* Note that this implementation pulls the SHA512 operations into the caller
* as a performance optimization.
*
* One modification from official pbkdf2. Instead of outputting key material
* linearly, we mix it. pbkdf2 has a known weakness where if one uses it to
* generate (e.g.) 512 bits of key material for use as two 256 bit keys, an
* attacker can merely run once through the outer loop, but the user
* always runs it twice. Shuffling output bytes requires computing the
* entirety of the key material to assemble any subkey. This is something a
* wise caller could do; we just do it for you.
*/
#define BCRYPT_WORDS 8
#define BCRYPT_HASHSIZE (BCRYPT_WORDS * 4)
static void
bcrypt_hash(uint8_t *sha2pass, uint8_t *sha2salt, uint8_t *out)
{
blf_ctx state;
uint8_t ciphertext[BCRYPT_HASHSIZE] =
"OxychromaticBlowfishSwatDynamite";
uint32_t cdata[BCRYPT_WORDS];
int i;
uint16_t j;
size_t shalen = SHA512_DIGEST_LENGTH;
/* key expansion */
Blowfish_initstate(&state);
Blowfish_expandstate(&state, sha2salt, shalen, sha2pass, shalen);
for (i = 0; i < 64; i++) {
Blowfish_expand0state(&state, sha2salt, shalen);
Blowfish_expand0state(&state, sha2pass, shalen);
}
/* encryption */
j = 0;
for (i = 0; i < BCRYPT_WORDS; i++)
cdata[i] = Blowfish_stream2word(ciphertext, sizeof(ciphertext),
&j);
for (i = 0; i < 64; i++)
blf_enc(&state, cdata, sizeof(cdata) / sizeof(uint64_t));
/* copy out */
for (i = 0; i < BCRYPT_WORDS; i++) {
out[4 * i + 3] = (cdata[i] >> 24) & 0xff;
out[4 * i + 2] = (cdata[i] >> 16) & 0xff;
out[4 * i + 1] = (cdata[i] >> 8) & 0xff;
out[4 * i + 0] = cdata[i] & 0xff;
}
/* zap */
pr_memscrub(ciphertext, sizeof(ciphertext));
pr_memscrub(cdata, sizeof(cdata));
pr_memscrub(&state, sizeof(state));
}
static int
bcrypt_pbkdf(const char *pass, size_t passlen, const uint8_t *salt, size_t saltlen,
uint8_t *key, size_t keylen, unsigned int rounds)
{
SHA512_CTX ctx;
uint8_t sha2pass[SHA512_DIGEST_LENGTH];
uint8_t sha2salt[SHA512_DIGEST_LENGTH];
uint8_t out[BCRYPT_HASHSIZE];
uint8_t tmpout[BCRYPT_HASHSIZE];
uint8_t countsalt[4];
size_t i, j, amt, stride;
uint32_t count;
size_t origkeylen = keylen;
/* nothing crazy */
if (rounds < 1)
return -1;
if (passlen == 0 || saltlen == 0 || keylen == 0 ||
keylen > sizeof(out) * sizeof(out))
return -1;
stride = (keylen + sizeof(out) - 1) / sizeof(out);
amt = (keylen + stride - 1) / stride;
/* collapse password */
SHA512_Init(&ctx);
SHA512_Update(&ctx, pass, passlen);
SHA512_Final(sha2pass, &ctx);
/* generate key, sizeof(out) at a time */
for (count = 1; keylen > 0; count++) {
countsalt[0] = (count >> 24) & 0xff;
countsalt[1] = (count >> 16) & 0xff;
countsalt[2] = (count >> 8) & 0xff;
countsalt[3] = count & 0xff;
/* first round, salt is salt */
SHA512_Init(&ctx);
SHA512_Update(&ctx, salt, saltlen);
SHA512_Update(&ctx, countsalt, sizeof(countsalt));
SHA512_Final(sha2salt, &ctx);
bcrypt_hash(sha2pass, sha2salt, tmpout);
memcpy(out, tmpout, sizeof(out));
for (i = 1; i < rounds; i++) {
/* subsequent rounds, salt is previous output */
SHA512_Init(&ctx);
SHA512_Update(&ctx, tmpout, sizeof(tmpout));
SHA512_Final(sha2salt, &ctx);
bcrypt_hash(sha2pass, sha2salt, tmpout);
for (j = 0; j < sizeof(out); j++)
out[j] ^= tmpout[j];
}
/*
* pbkdf2 deviation: output the key material non-linearly.
*/
amt = MINIMUM(amt, keylen);
for (i = 0; i < amt; i++) {
size_t dest = i * stride + (count - 1);
if (dest >= origkeylen)
break;
key[dest] = out[i];
}
keylen -= i;
}
/* zap */
pr_memscrub(&ctx, sizeof(ctx));
pr_memscrub(out, sizeof(out));
return 0;
}
static const char *trace_channel = "proxy.ssh.bcrypt";
int proxy_ssh_bcrypt_pbkdf2(pool *p, const char *passphrase,
size_t passphrase_len, unsigned char *salt, uint32_t salt_len,
uint32_t rounds, unsigned char *key, uint32_t key_len) {
int res = 0;
if (p == NULL ||
passphrase == NULL ||
salt == NULL) {
errno = EINVAL;
return -1;
}
if (rounds < 1) {
pr_trace_msg(trace_channel, 4, "invalid rounds (%lu) for bcrypt KDF",
(unsigned long) rounds);
errno = EINVAL;
return -1;
}
if (passphrase_len == 0 ||
salt_len == 0 ||
key_len == 0) {
pr_trace_msg(trace_channel, 4,
"invalid bcrypt KDF data: passphrase (%lu bytes), salt (%lu bytes), "
"or key (%lu bytes)", (unsigned long) passphrase_len,
(unsigned long) salt_len, (unsigned long) key_len);
errno = EINVAL;
return -1;
}
if (key_len < PROXY_SSH_BCRYPT_DIGEST_LEN) {
pr_trace_msg(trace_channel, 4,
"invalid bcrypt KDF data: key (%lu bytes) too short; need at "
"least %lu bytes", (unsigned long) key_len,
(unsigned long) PROXY_SSH_BCRYPT_DIGEST_LEN);
errno = EINVAL;
return -1;
}
res = bcrypt_pbkdf(passphrase, passphrase_len, salt, salt_len, key, key_len,
rounds);
if (res < 0) {
errno = EINVAL;
return -1;
}
return 0;
}
#endif /* PR_USE_OPENSSL */
proftpd-mod_proxy-0.9.5/lib/proxy/ssh/cipher.c 0000664 0000000 0000000 00000103267 14757370167 0021415 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH ciphers
* Copyright (c) 2021-2022 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "proxy/ssh/packet.h"
#include "proxy/ssh/msg.h"
#include "proxy/ssh/crypto.h"
#include "proxy/ssh/cipher.h"
#include "proxy/ssh/session.h"
#include "proxy/ssh/interop.h"
#if defined(PR_USE_OPENSSL)
#include
struct proxy_ssh_cipher {
pool *pool;
const char *algo;
const EVP_CIPHER *cipher;
unsigned char *iv;
uint32_t iv_len;
unsigned char *key;
uint32_t key_len;
uint32_t auth_len;
size_t discard_len;
};
/* We need to keep the old ciphers around, so that we can handle N
* arbitrary packets to/from the client using the old keys, as during rekeying.
* Thus we have two read cipher contexts, two write cipher contexts.
* The cipher idx variable indicates which of the ciphers is currently in use.
*/
static struct proxy_ssh_cipher read_ciphers[2] = {
{ NULL, NULL, NULL, NULL, 0, NULL, 0, 0, 0 },
{ NULL, NULL, NULL, NULL, 0, NULL, 0, 0, 0 }
};
static EVP_CIPHER_CTX *read_ctxs[2];
static struct proxy_ssh_cipher write_ciphers[2] = {
{ NULL, NULL, NULL, NULL, 0, NULL, 0, 0, 0 },
{ NULL, NULL, NULL, NULL, 0, NULL, 0, 0, 0 }
};
static EVP_CIPHER_CTX *write_ctxs[2];
#define PROXY_SSH_CIPHER_DEFAULT_BLOCK_SZ 8
static size_t read_blockszs[2] = {
PROXY_SSH_CIPHER_DEFAULT_BLOCK_SZ,
PROXY_SSH_CIPHER_DEFAULT_BLOCK_SZ,
};
static size_t write_blockszs[2] = {
PROXY_SSH_CIPHER_DEFAULT_BLOCK_SZ,
PROXY_SSH_CIPHER_DEFAULT_BLOCK_SZ,
};
static unsigned int read_cipher_idx = 0;
static unsigned int write_cipher_idx = 0;
static const char *trace_channel = "proxy.ssh.cipher";
static void clear_cipher(struct proxy_ssh_cipher *);
static unsigned int get_next_read_index(void) {
if (read_cipher_idx == 1) {
return 0;
}
return 1;
}
static unsigned int get_next_write_index(void) {
if (write_cipher_idx == 1) {
return 0;
}
return 1;
}
static void switch_read_cipher(void) {
/* First, clear the context of the existing read cipher, if any. */
if (read_ciphers[read_cipher_idx].key != NULL) {
clear_cipher(&(read_ciphers[read_cipher_idx]));
#if OPENSSL_VERSION_NUMBER < 0x10100000L || \
defined(HAVE_LIBRESSL)
if (EVP_CIPHER_CTX_cleanup(read_ctxs[read_cipher_idx]) != 1) {
#else
if (EVP_CIPHER_CTX_reset(read_ctxs[read_cipher_idx]) != 1) {
#endif /* OpenSSL-1.1.x and later */
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error clearing cipher context: %s", proxy_ssh_crypto_get_errors());
}
read_blockszs[read_cipher_idx] = PROXY_SSH_CIPHER_DEFAULT_BLOCK_SZ;
/* Now we can switch the index. */
if (read_cipher_idx == 1) {
read_cipher_idx = 0;
return;
}
read_cipher_idx = 1;
}
}
static void switch_write_cipher(void) {
/* First, clear the context of the existing read cipher, if any. */
if (write_ciphers[write_cipher_idx].key != NULL) {
clear_cipher(&(write_ciphers[write_cipher_idx]));
#if OPENSSL_VERSION_NUMBER < 0x10100000L || \
defined(HAVE_LIBRESSL)
if (EVP_CIPHER_CTX_cleanup(write_ctxs[write_cipher_idx]) != 1) {
#else
if (EVP_CIPHER_CTX_reset(write_ctxs[write_cipher_idx]) != 1) {
#endif /* OpenSSL-1.1.x and later */
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error clearing cipher context: %s", proxy_ssh_crypto_get_errors());
}
write_blockszs[write_cipher_idx] = PROXY_SSH_CIPHER_DEFAULT_BLOCK_SZ;
/* Now we can switch the index. */
if (write_cipher_idx == 1) {
write_cipher_idx = 0;
return;
}
write_cipher_idx = 1;
}
}
static void clear_cipher(struct proxy_ssh_cipher *cipher) {
if (cipher->iv != NULL) {
pr_memscrub(cipher->iv, cipher->iv_len);
free(cipher->iv);
cipher->iv = NULL;
cipher->iv_len = 0;
}
if (cipher->key != NULL) {
pr_memscrub(cipher->key, cipher->key_len);
free(cipher->key);
cipher->key = NULL;
cipher->key_len = 0;
}
cipher->cipher = NULL;
cipher->algo = NULL;
}
static int set_cipher_iv(struct proxy_ssh_cipher *cipher, const EVP_MD *md,
const unsigned char *k, uint32_t klen, const char *h, uint32_t hlen,
char letter, const unsigned char *id, uint32_t id_len) {
EVP_MD_CTX *ctx;
unsigned char *iv = NULL;
size_t cipher_iv_len = 0, iv_sz = 0;
uint32_t iv_len = 0;
if (strcmp(cipher->algo, "none") == 0) {
cipher->iv = iv;
cipher->iv_len = iv_len;
return 0;
}
/* Some ciphers do not use IVs; handle this case. */
cipher_iv_len = EVP_CIPHER_iv_length(cipher->cipher);
if (cipher_iv_len != 0) {
iv_sz = proxy_ssh_crypto_get_size(cipher_iv_len, EVP_MD_size(md));
} else {
iv_sz = EVP_MD_size(md);
}
if (iv_sz == 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to determine IV length for cipher '%s'", cipher->algo);
errno = EINVAL;
return -1;
}
iv = malloc(iv_sz);
if (iv == NULL) {
pr_log_pri(PR_LOG_ALERT, MOD_PROXY_VERSION ": Out of memory!");
_exit(1);
}
ctx = EVP_MD_CTX_create();
if (EVP_DigestInit(ctx, md) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to initialize SSH MD context for '%s': %s", EVP_MD_name(md),
proxy_ssh_crypto_get_errors());
free(iv);
errno = EINVAL;
return -1;
}
if (proxy_ssh_interop_supports_feature(PROXY_SSH_FEAT_CIPHER_USE_K)) {
EVP_DigestUpdate(ctx, k, klen);
}
if (EVP_DigestUpdate(ctx, h, hlen) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to update SSH MD context for '%s': %s", EVP_MD_name(md),
proxy_ssh_crypto_get_errors());
free(iv);
errno = EINVAL;
return -1;
}
EVP_DigestUpdate(ctx, &letter, sizeof(letter));
EVP_DigestUpdate(ctx, (char *) id, id_len);
if (EVP_DigestFinal(ctx, iv, &iv_len) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to finish SSH MD context for '%s': %s", EVP_MD_name(md),
proxy_ssh_crypto_get_errors());
free(iv);
errno = EINVAL;
return -1;
}
EVP_MD_CTX_destroy(ctx);
/* If we need more, keep hashing, as per RFC, until we have enough
* material.
*/
while (iv_sz > iv_len) {
uint32_t len = iv_len;
pr_signals_handle();
ctx = EVP_MD_CTX_create();
EVP_DigestInit(ctx, md);
if (proxy_ssh_interop_supports_feature(PROXY_SSH_FEAT_CIPHER_USE_K)) {
EVP_DigestUpdate(ctx, k, klen);
}
EVP_DigestUpdate(ctx, h, hlen);
EVP_DigestUpdate(ctx, iv, len);
EVP_DigestFinal(ctx, iv + len, &len);
EVP_MD_CTX_destroy(ctx);
iv_len += len;
}
cipher->iv = iv;
cipher->iv_len = iv_len;
return 0;
}
static int set_cipher_key(struct proxy_ssh_cipher *cipher, const EVP_MD *md,
const unsigned char *k, uint32_t klen, const char *h, uint32_t hlen,
char letter, const unsigned char *id, uint32_t id_len) {
EVP_MD_CTX *ctx;
unsigned char *key = NULL;
size_t key_sz = 0;
uint32_t key_len = 0;
if (strcmp(cipher->algo, "none") == 0) {
cipher->key = key;
cipher->key_len = key_len;
return 0;
}
key_sz = proxy_ssh_crypto_get_size(cipher->key_len > 0 ?
cipher->key_len : (size_t) EVP_CIPHER_key_length(cipher->cipher),
EVP_MD_size(md));
if (key_sz == 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to determine key length for cipher '%s'", cipher->algo);
errno = EINVAL;
return -1;
}
pr_trace_msg(trace_channel, 19, "setting key (%lu bytes) for cipher %s",
(unsigned long) key_sz, cipher->algo);
key = malloc(key_sz);
if (key == NULL) {
pr_log_pri(PR_LOG_ALERT, MOD_PROXY_VERSION ": Out of memory!");
_exit(1);
}
ctx = EVP_MD_CTX_create();
if (EVP_DigestInit(ctx, md) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to initialize SSH MD context for '%s': %s", EVP_MD_name(md),
proxy_ssh_crypto_get_errors());
free(key);
errno = EINVAL;
return -1;
}
if (EVP_DigestUpdate(ctx, k, klen) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to update SSH MD context for '%s': %s", EVP_MD_name(md),
proxy_ssh_crypto_get_errors());
free(key);
errno = EINVAL;
return -1;
}
EVP_DigestUpdate(ctx, h, hlen);
EVP_DigestUpdate(ctx, &letter, sizeof(letter));
EVP_DigestUpdate(ctx, (char *) id, id_len);
if (EVP_DigestFinal(ctx, key, &key_len) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to finish SSH MD context for '%s': %s", EVP_MD_name(md),
proxy_ssh_crypto_get_errors());
free(key);
errno = EINVAL;
return -1;
}
EVP_MD_CTX_destroy(ctx);
pr_trace_msg(trace_channel, 19, "hashed data to produce key (%lu bytes)",
(unsigned long) key_len);
/* If we need more, keep hashing, as per RFC, until we have enough
* material.
*/
while (key_sz > key_len) {
uint32_t len = key_len;
pr_signals_handle();
ctx = EVP_MD_CTX_create();
EVP_DigestInit(ctx, md);
EVP_DigestUpdate(ctx, k, klen);
EVP_DigestUpdate(ctx, h, hlen);
EVP_DigestUpdate(ctx, key, len);
EVP_DigestFinal(ctx, key + len, &len);
EVP_MD_CTX_destroy(ctx);
key_len += len;
}
cipher->key = key;
return 0;
}
/* If the chosen cipher requires that we discard some of the initial bytes of
* the cipher stream, then do so. (This is mostly for any RC4 ciphers.)
*/
static int set_cipher_discarded(struct proxy_ssh_cipher *cipher,
EVP_CIPHER_CTX *pctx) {
unsigned char *garbage_in, *garbage_out;
if (cipher->discard_len == 0) {
return 0;
}
garbage_in = malloc(cipher->discard_len);
if (garbage_in == NULL) {
pr_log_pri(PR_LOG_ALERT, MOD_PROXY_VERSION ": Out of memory!");
_exit(1);
}
garbage_out = malloc(cipher->discard_len);
if (garbage_out == NULL) {
free(garbage_in);
pr_log_pri(PR_LOG_ALERT, MOD_PROXY_VERSION ": Out of memory!");
_exit(1);
}
if (EVP_Cipher(pctx, garbage_out, garbage_in,
cipher->discard_len) != 1) {
free(garbage_in);
pr_memscrub(garbage_out, cipher->discard_len);
free(garbage_out);
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error ciphering discard data: %s", proxy_ssh_crypto_get_errors());
return -1;
}
pr_trace_msg(trace_channel, 19, "discarded %lu bytes of cipher data",
(unsigned long) cipher->discard_len);
free(garbage_in);
pr_memscrub(garbage_out, cipher->discard_len);
free(garbage_out);
return 0;
}
/* These accessors to get the authenticated data length for the read, write
* ciphers are used during packet IO, and thus do not return the AAD lengths
* until those ciphers are keyed.
*
* However, during KEX, there are times when we want to know the ADD lengths
* after the algorithms are selected, but before they are keyed. Thus for
* those cases, we have the accessor variants.
*/
size_t proxy_ssh_cipher_get_read_auth_size2(void) {
return read_ciphers[read_cipher_idx].auth_len;
}
size_t proxy_ssh_cipher_get_read_auth_size(void) {
/* Do not indicate the read cipher authentication tag size until the
* cipher has been keyed.
*/
if (read_ciphers[read_cipher_idx].key != NULL) {
return proxy_ssh_cipher_get_read_auth_size2();
}
return 0;
}
size_t proxy_ssh_cipher_get_write_auth_size2(void) {
return write_ciphers[write_cipher_idx].auth_len;
}
size_t proxy_ssh_cipher_get_write_auth_size(void) {
/* Do not indicate the write cipher authentication tag size until the
* cipher has been keyed.
*/
if (write_ciphers[write_cipher_idx].key != NULL) {
return proxy_ssh_cipher_get_write_auth_size2();
}
return 0;
}
size_t proxy_ssh_cipher_get_read_block_size(void) {
return read_blockszs[read_cipher_idx];
}
size_t proxy_ssh_cipher_get_write_block_size(void) {
return write_blockszs[write_cipher_idx];
}
void proxy_ssh_cipher_set_read_block_size(size_t blocksz) {
if (blocksz > read_blockszs[read_cipher_idx]) {
read_blockszs[read_cipher_idx] = blocksz;
}
}
void proxy_ssh_cipher_set_write_block_size(size_t blocksz) {
if (blocksz > write_blockszs[write_cipher_idx]) {
write_blockszs[write_cipher_idx] = blocksz;
}
}
const char *proxy_ssh_cipher_get_read_algo(void) {
if (read_ciphers[read_cipher_idx].key != NULL ||
strcmp(read_ciphers[read_cipher_idx].algo, "none") == 0) {
return read_ciphers[read_cipher_idx].algo;
}
return NULL;
}
int proxy_ssh_cipher_set_read_algo(pool *p, const char *algo) {
unsigned int idx = read_cipher_idx;
size_t key_len = 0, auth_len = 0, discard_len = 0;
if (read_ciphers[idx].key != NULL) {
/* If we have an existing key, it means that we are currently rekeying. */
idx = get_next_read_index();
}
read_ciphers[idx].cipher = proxy_ssh_crypto_get_cipher(algo, &key_len,
&auth_len, &discard_len);
if (read_ciphers[idx].cipher == NULL) {
return -1;
}
if (key_len > 0) {
pr_trace_msg(trace_channel, 19,
"setting read key for cipher %s: key len = %lu", algo,
(unsigned long) key_len);
}
if (auth_len > 0) {
pr_trace_msg(trace_channel, 19,
"setting read key for cipher %s: auth len = %lu", algo,
(unsigned long) auth_len);
}
if (discard_len > 0) {
pr_trace_msg(trace_channel, 19,
"setting read key for cipher %s: discard len = %lu", algo,
(unsigned long) discard_len);
}
/* Note that we use a new pool, each time the algorithm is set (which
* happens during key exchange) to prevent undue memory growth for
* long-lived sessions with many rekeys.
*/
if (read_ciphers[idx].pool != NULL) {
destroy_pool(read_ciphers[idx].pool);
}
read_ciphers[idx].pool = make_sub_pool(p);
pr_pool_tag(read_ciphers[idx].pool, "Proxy SFTP cipher read pool");
read_ciphers[idx].algo = pstrdup(read_ciphers[idx].pool, algo);
read_ciphers[idx].key_len = (uint32_t) key_len;
read_ciphers[idx].auth_len = (uint32_t) auth_len;
read_ciphers[idx].discard_len = discard_len;
return 0;
}
int proxy_ssh_cipher_set_read_key(pool *p, const EVP_MD *md,
const unsigned char *k, uint32_t klen, const char *h, uint32_t hlen,
int role) {
const unsigned char *id = NULL;
char letter;
uint32_t id_len;
int key_len, auth_len;
struct proxy_ssh_cipher *cipher;
EVP_CIPHER_CTX *pctx;
/* Currently unused. */
(void) p;
switch_read_cipher();
cipher = &(read_ciphers[read_cipher_idx]);
pctx = read_ctxs[read_cipher_idx];
id_len = proxy_ssh_session_get_id(&id);
/* The letters used depend on the role; see:
* https://tools.ietf.org/html/rfc4253#section-7.2
*
* If we are the CLIENT, then we use the letters for the "server to client"
* flows, since we are READING from the server.
*/
/* client-to-server IV: HASH(K || H || "A" || session_id)
* server-to-client IV: HASH(K || H || "B" || session_id)
*/
letter = (role == PROXY_SSH_ROLE_CLIENT ? 'B' : 'A');
if (set_cipher_iv(cipher, md, k, klen, h, hlen, letter, id, id_len) < 0) {
return -1;
}
/* client-to-server key: HASH(K || H || "C" || session_id)
* server-to-client key: HASH(K || H || "D" || session_id)
*/
letter = (role == PROXY_SSH_ROLE_CLIENT ? 'D' : 'C');
if (set_cipher_key(cipher, md, k, klen, h, hlen, letter, id, id_len) < 0) {
return -1;
}
#if OPENSSL_VERSION_NUMBER < 0x10100000L || \
defined(HAVE_LIBRESSL)
EVP_CIPHER_CTX_init(pctx);
#else
EVP_CIPHER_CTX_reset(pctx);
#endif /* prior to OpenSSL-1.1.0 */
#if defined(PR_USE_OPENSSL_EVP_CIPHERINIT_EX)
if (EVP_CipherInit_ex(pctx, cipher->cipher, NULL, NULL,
cipher->iv, 0) != 1) {
#else
if (EVP_CipherInit(pctx, cipher->cipher, NULL, cipher->iv, 0) != 1) {
#endif /* PR_USE_OPENSSL_EVP_CIPHERINIT_EX */
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error initializing %s cipher for decryption: %s", cipher->algo,
proxy_ssh_crypto_get_errors());
return -1;
}
auth_len = (int) cipher->auth_len;
if (auth_len > 0) {
#if defined(EVP_CTRL_GCM_SET_IV_FIXED)
if (EVP_CIPHER_CTX_ctrl(pctx, EVP_CTRL_GCM_SET_IV_FIXED, -1,
cipher->iv) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error configuring %s cipher for decryption: %s", cipher->algo,
proxy_ssh_crypto_get_errors());
return -1;
}
#endif /* EVP_CTRL_GCM_SET_IV_FIXED */
pr_trace_msg(trace_channel, 19,
"set auth length (%d) for %s cipher for decryption", auth_len,
cipher->algo);
}
/* Next, set the key length. */
key_len = (int) cipher->key_len;
if (key_len > 0) {
if (EVP_CIPHER_CTX_set_key_length(pctx, key_len) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error setting key length (%d bytes) for %s cipher for decryption: %s",
key_len, cipher->algo, proxy_ssh_crypto_get_errors());
return -1;
}
pr_trace_msg(trace_channel, 19,
"set key length (%d) for %s cipher for decryption", key_len,
cipher->algo);
}
#if defined(PR_USE_OPENSSL_EVP_CIPHERINIT_EX)
if (EVP_CipherInit_ex(pctx, NULL, NULL, cipher->key, NULL, -1) != 1) {
#else
if (EVP_CipherInit(pctx, NULL, cipher->key, NULL, -1) != 1) {
#endif /* PR_USE_OPENSSL_EVP_CIPHERINIT_EX */
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error re-initializing %s cipher for decryption: %s", cipher->algo,
proxy_ssh_crypto_get_errors());
return -1;
}
if (set_cipher_discarded(cipher, pctx) < 0) {
return -1;
}
if (strcmp(cipher->algo, "aes128-ctr") == 0 ||
strcmp(cipher->algo, "aes128-gcm@openssh.com") == 0 ||
strcmp(cipher->algo, "aes192-ctr") == 0 ||
strcmp(cipher->algo, "aes256-ctr") == 0 ||
strcmp(cipher->algo, "aes256-gcm@openssh.com") == 0) {
/* For some reason, OpenSSL returns 8 for the AES CTR/GCM block size (even
* though the AES block size is 16, per RFC 5647), but OpenSSH wants 16.
*/
proxy_ssh_cipher_set_read_block_size(16);
} else {
proxy_ssh_cipher_set_read_block_size(EVP_CIPHER_block_size(cipher->cipher));
}
return 0;
}
int proxy_ssh_cipher_read_data(struct proxy_ssh_packet *pkt,
unsigned char *data, uint32_t data_len, unsigned char **buf,
uint32_t *buflen) {
struct proxy_ssh_cipher *cipher;
EVP_CIPHER_CTX *pctx;
size_t auth_len, read_blocksz;
uint32_t output_buflen;
cipher = &(read_ciphers[read_cipher_idx]);
pctx = read_ctxs[read_cipher_idx];
read_blocksz = read_blockszs[read_cipher_idx];
auth_len = proxy_ssh_cipher_get_read_auth_size();
output_buflen = *buflen;
if (cipher->key != NULL) {
int res;
unsigned char *ptr = NULL, *buf2 = NULL;
if (*buf == NULL) {
size_t bufsz;
/* Allocate a buffer that's large enough. */
bufsz = (data_len + read_blocksz - 1);
ptr = buf2 = pcalloc(pkt->pool, bufsz);
} else {
ptr = buf2 = *buf;
}
if (pkt->packet_len == 0) {
if (auth_len > 0) {
#if defined(EVP_CTRL_GCM_IV_GEN)
unsigned char prev_iv[1];
/* Increment the IV. */
if (EVP_CIPHER_CTX_ctrl(pctx, EVP_CTRL_GCM_IV_GEN, 1, prev_iv) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error incrementing %s IV data for server: %s", cipher->algo,
proxy_ssh_crypto_get_errors());
errno = EIO;
return -1;
}
#endif
}
if (pkt->aad_len > 0 &&
pkt->aad == NULL) {
pkt->aad = pcalloc(pkt->pool, pkt->aad_len);
memcpy(pkt->aad, data, pkt->aad_len);
memcpy(ptr, data, pkt->aad_len);
/* Save room at the start of the output buffer `ptr` for the AAD
* bytes.
*/
buf2 += pkt->aad_len;
data += pkt->aad_len;
data_len -= pkt->aad_len;
output_buflen -= pkt->aad_len;
if (auth_len > 0) {
if (EVP_Cipher(pctx, NULL, pkt->aad, pkt->aad_len) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error setting %s AAD data for server: %s", cipher->algo,
proxy_ssh_crypto_get_errors());
errno = EIO;
return -1;
}
}
}
}
if (output_buflen % read_blocksz != 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"bad input length for decryption (%u bytes, %lu AAD bytes, "
"%u block size)", output_buflen, (unsigned long) pkt->aad_len,
(unsigned int) read_blocksz);
return -1;
}
if (pkt->packet_len > 0 &&
auth_len > 0) {
unsigned char *tag_data = NULL;
uint32_t tag_datalen = auth_len;
/* The authentication tag appears after the unencrypted AAD bytes, and
* the encrypted payload bytes.
*/
tag_data = data + (data_len - auth_len);
#if defined(EVP_CTRL_GCM_GET_TAG)
if (EVP_CIPHER_CTX_ctrl(pctx, EVP_CTRL_GCM_SET_TAG, tag_datalen,
tag_data) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error setting %s authentication tag for server: %s", cipher->algo,
proxy_ssh_crypto_get_errors());
errno = EIO;
return -1;
}
#endif
data_len -= auth_len;
}
res = EVP_Cipher(pctx, buf2, data, data_len);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error decrypting %s data from server: %s", cipher->algo,
proxy_ssh_crypto_get_errors());
return -1;
}
if (pkt->packet_len > 0) {
*buflen = data_len;
} else {
/* If we don't know the packet length yet, it means we need to allow for
* the processing of the AAD bytes.
*/
*buflen = pkt->aad_len + data_len;
}
*buf = ptr;
if (pkt->packet_len > 0 &&
auth_len > 0) {
/* Verify the authentication tag, but only if we have the full packet. */
if (EVP_Cipher(pctx, NULL, NULL, 0) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error verifying %s authentication tag for server: %s", cipher->algo,
proxy_ssh_crypto_get_errors());
errno = EIO;
return -1;
}
}
return 0;
}
*buf = data;
*buflen = data_len;
return 0;
}
const char *proxy_ssh_cipher_get_write_algo(void) {
if (write_ciphers[write_cipher_idx].key != NULL ||
strcmp(write_ciphers[write_cipher_idx].algo, "none") == 0) {
return write_ciphers[write_cipher_idx].algo;
}
return NULL;
}
int proxy_ssh_cipher_set_write_algo(pool *p, const char *algo) {
unsigned int idx = write_cipher_idx;
size_t key_len = 0, auth_len = 0, discard_len = 0;
if (write_ciphers[idx].key != NULL) {
/* If we have an existing key, it means that we are currently rekeying. */
idx = get_next_write_index();
}
write_ciphers[idx].cipher = proxy_ssh_crypto_get_cipher(algo, &key_len,
&auth_len, &discard_len);
if (write_ciphers[idx].cipher == NULL) {
return -1;
}
if (key_len > 0) {
pr_trace_msg(trace_channel, 19,
"setting write key for cipher %s: key len = %lu", algo,
(unsigned long) key_len);
}
if (auth_len > 0) {
pr_trace_msg(trace_channel, 19,
"setting write key for cipher %s: auth len = %lu", algo,
(unsigned long) auth_len);
}
if (discard_len > 0) {
pr_trace_msg(trace_channel, 19,
"setting write key for cipher %s: discard len = %lu", algo,
(unsigned long) discard_len);
}
/* Note that we use a new pool, each time the algorithm is set (which
* happens during key exchange) to prevent undue memory growth for
* long-lived sessions with many rekeys.
*/
if (write_ciphers[idx].pool != NULL) {
destroy_pool(write_ciphers[idx].pool);
}
write_ciphers[idx].pool = make_sub_pool(p);
pr_pool_tag(write_ciphers[idx].pool, "Proxy SFTP cipher write pool");
write_ciphers[idx].algo = pstrdup(write_ciphers[idx].pool, algo);
write_ciphers[idx].key_len = (uint32_t) key_len;
write_ciphers[idx].auth_len = (uint32_t) auth_len;
write_ciphers[idx].discard_len = discard_len;
return 0;
}
int proxy_ssh_cipher_set_write_key(pool *p, const EVP_MD *md,
const unsigned char *k, uint32_t klen, const char *h, uint32_t hlen,
int role) {
const unsigned char *id = NULL;
char letter;
uint32_t id_len;
int key_len, auth_len;
struct proxy_ssh_cipher *cipher;
EVP_CIPHER_CTX *pctx;
/* Currently unused. */
(void) p;
switch_write_cipher();
cipher = &(write_ciphers[write_cipher_idx]);
pctx = write_ctxs[write_cipher_idx];
id_len = proxy_ssh_session_get_id(&id);
/* The letters used depend on the role; see:
* https://tools.ietf.org/html/rfc4253#section-7.2
*
* If we are the CLIENT, then we use the letters for the "client to server"
* flows, since we are WRITING to the server.
*/
/* client-to-server IV: HASH(K || H || "A" || session_id)
* server-to-client IV: HASH(K || H || "B" || session_id)
*/
letter = (role == PROXY_SSH_ROLE_CLIENT ? 'A' : 'B');
if (set_cipher_iv(cipher, md, k, klen, h, hlen, letter, id, id_len) < 0) {
return -1;
}
/* client-to-server key: HASH(K || H || "C" || session_id)
* server-to-client key: HASH(K || H || "D" || session_id)
*/
letter = (role == PROXY_SSH_ROLE_CLIENT ? 'C' : 'D');
if (set_cipher_key(cipher, md, k, klen, h, hlen, letter, id, id_len) < 0) {
return -1;
}
#if OPENSSL_VERSION_NUMBER < 0x10100000L || \
defined(HAVE_LIBRESSL)
EVP_CIPHER_CTX_init(pctx);
#else
EVP_CIPHER_CTX_reset(pctx);
#endif
#if defined(PR_USE_OPENSSL_EVP_CIPHERINIT_EX)
if (EVP_CipherInit_ex(pctx, cipher->cipher, NULL, NULL,
cipher->iv, 1) != 1) {
#else
if (EVP_CipherInit(pctx, cipher->cipher, NULL, cipher->iv, 1) != 1) {
#endif /* PR_USE_OPENSSL_EVP_CIPHERINIT_EX */
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error initializing %s cipher for encryption: %s", cipher->algo,
proxy_ssh_crypto_get_errors());
return -1;
}
auth_len = (int) cipher->auth_len;
if (auth_len > 0) {
#if defined(EVP_CTRL_GCM_SET_IV_FIXED)
if (EVP_CIPHER_CTX_ctrl(pctx, EVP_CTRL_GCM_SET_IV_FIXED, -1,
cipher->iv) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error configuring %s cipher for encryption: %s", cipher->algo,
proxy_ssh_crypto_get_errors());
return -1;
}
#endif /* EVP_CTRL_GCM_SET_IV_FIXED */
pr_trace_msg(trace_channel, 19,
"set auth length (%d) for %s cipher for encryption", auth_len,
cipher->algo);
}
/* Next, set the key length. */
key_len = (int) cipher->key_len;
if (key_len > 0) {
if (EVP_CIPHER_CTX_set_key_length(pctx, key_len) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error setting key length (%d bytes) for %s cipher for decryption: %s",
key_len, cipher->algo, proxy_ssh_crypto_get_errors());
return -1;
}
pr_trace_msg(trace_channel, 19,
"set key length (%d) for %s cipher for encryption", key_len,
cipher->algo);
}
#if defined(PR_USE_OPENSSL_EVP_CIPHERINIT_EX)
if (EVP_CipherInit_ex(pctx, NULL, NULL, cipher->key, NULL, -1) != 1) {
#else
if (EVP_CipherInit(pctx, NULL, cipher->key, NULL, -1) != 1) {
#endif /* PR_USE_OPENSSL_EVP_CIPHERINIT_EX */
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error re-initializing %s cipher for encryption: %s", cipher->algo,
proxy_ssh_crypto_get_errors());
return -1;
}
if (set_cipher_discarded(cipher, pctx) < 0) {
return -1;
}
if (strcmp(cipher->algo, "aes128-ctr") == 0 ||
strcmp(cipher->algo, "aes128-gcm@openssh.com") == 0 ||
strcmp(cipher->algo, "aes192-ctr") == 0 ||
strcmp(cipher->algo, "aes256-ctr") == 0 ||
strcmp(cipher->algo, "aes256-gcm@openssh.com") == 0) {
/* For some reason, OpenSSL returns 8 for the AES CTR/GCM block size (even
* though the AES block size is 16, per RFC 5647), but OpenSSH wants 16.
*/
proxy_ssh_cipher_set_write_block_size(16);
} else {
proxy_ssh_cipher_set_write_block_size(EVP_CIPHER_block_size(cipher->cipher));
}
pr_trace_msg(trace_channel, 19,
"set block size (%d) for %s cipher for encryption",
(int) proxy_ssh_cipher_get_write_block_size(), cipher->algo);
return 0;
}
int proxy_ssh_cipher_write_data(struct proxy_ssh_packet *pkt,
unsigned char *buf, size_t *buflen) {
struct proxy_ssh_cipher *cipher;
EVP_CIPHER_CTX *pctx;
size_t auth_len = 0;
cipher = &(write_ciphers[write_cipher_idx]);
pctx = write_ctxs[write_cipher_idx];
auth_len = proxy_ssh_cipher_get_write_auth_size();
if (cipher->key != NULL) {
int res;
unsigned char *data, *ptr;
uint32_t datalen, datasz, len = 0;
/* Always leave a little extra room in the buffer. */
datasz = sizeof(uint32_t) + pkt->packet_len + 64;
if (pkt->aad_len > 0) {
/* Packet length is not encrypted for authentication encryption, or
* Encrypt-Then-MAC modes.
*/
datasz -= pkt->aad_len;
/* And, for ETM modes, we may need a little more space. */
datasz += proxy_ssh_cipher_get_write_block_size();
}
datalen = datasz;
ptr = data = palloc(pkt->pool, datasz);
if (auth_len > 0) {
#if defined(EVP_CTRL_GCM_IV_GEN)
unsigned char prev_iv[1];
/* Increment the IV. */
if (EVP_CIPHER_CTX_ctrl(pctx, EVP_CTRL_GCM_IV_GEN, 1, prev_iv) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error incrementing %s IV data for server: %s", cipher->algo,
proxy_ssh_crypto_get_errors());
errno = EIO;
return -1;
}
#endif
}
if (pkt->aad_len > 0 &&
pkt->aad == NULL) {
uint32_t packet_len;
packet_len = htonl(pkt->packet_len);
pkt->aad = pcalloc(pkt->pool, pkt->aad_len);
memcpy(pkt->aad, &packet_len, pkt->aad_len);
if (auth_len > 0) {
if (EVP_Cipher(pctx, NULL, pkt->aad, pkt->aad_len) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error setting %s AAD (%lu bytes) for server: %s", cipher->algo,
(unsigned long) pkt->aad_len, proxy_ssh_crypto_get_errors());
errno = EIO;
return -1;
}
}
} else {
len += proxy_ssh_msg_write_int(&data, &datalen, pkt->packet_len);
}
len += proxy_ssh_msg_write_byte(&data, &datalen, pkt->padding_len);
len += proxy_ssh_msg_write_data(&data, &datalen, pkt->payload,
pkt->payload_len, FALSE);
len += proxy_ssh_msg_write_data(&data, &datalen, pkt->padding,
pkt->padding_len, FALSE);
res = EVP_Cipher(pctx, buf, ptr, len);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error encrypting %s data for server: %s", cipher->algo,
proxy_ssh_crypto_get_errors());
errno = EIO;
return -1;
}
*buflen = len;
#ifdef SFTP_DEBUG_PACKET
{
unsigned int i;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"encrypted packet data (len %lu):", (unsigned long) *buflen);
for (i = 0; i < *buflen;) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
" %02x%02x %02x%02x %02x%02x %02x%02x",
((unsigned char *) buf)[i], ((unsigned char *) buf)[i+1],
((unsigned char *) buf)[i+2], ((unsigned char *) buf)[i+3],
((unsigned char *) buf)[i+4], ((unsigned char *) buf)[i+5],
((unsigned char *) buf)[i+6], ((unsigned char *) buf)[i+7]);
i += 8;
}
}
#endif
if (auth_len > 0) {
unsigned char *tag_data = NULL;
uint32_t tag_datalen = 0;
if (EVP_Cipher(pctx, NULL, NULL, 0) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error generating %s authentication tag for server: %s", cipher->algo,
proxy_ssh_crypto_get_errors());
errno = EIO;
return -1;
}
tag_datalen = auth_len;
tag_data = pcalloc(pkt->pool, tag_datalen);
#if defined(EVP_CTRL_GCM_GET_TAG)
if (EVP_CIPHER_CTX_ctrl(pctx, EVP_CTRL_GCM_GET_TAG, tag_datalen,
tag_data) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error getting %s authentication tag for server: %s", cipher->algo,
proxy_ssh_crypto_get_errors());
errno = EIO;
return -1;
}
#endif
pkt->mac_len = tag_datalen;
pkt->mac = tag_data;
}
return 0;
}
*buflen = 0;
return 0;
}
#if OPENSSL_VERSION_NUMBER < 0x1000000fL
/* In older versions of OpenSSL, there was not a way to dynamically allocate
* an EVP_CIPHER_CTX object. Thus we have these static objects for those
* older versions.
*/
static EVP_CIPHER_CTX read_ctx1, read_ctx2;
static EVP_CIPHER_CTX write_ctx1, write_ctx2;
#endif /* prior to OpenSSL-1.0.0 */
int proxy_ssh_cipher_init(void) {
#if OPENSSL_VERSION_NUMBER < 0x1000000fL
read_ctxs[0] = &read_ctx1;
read_ctxs[1] = &read_ctx2;
write_ctxs[0] = &write_ctx1;
write_ctxs[1] = &write_ctx2;
#else
read_ctxs[0] = EVP_CIPHER_CTX_new();
read_ctxs[1] = EVP_CIPHER_CTX_new();
write_ctxs[0] = EVP_CIPHER_CTX_new();
write_ctxs[1] = EVP_CIPHER_CTX_new();
#endif /* OpenSSL-1.0.0 and later */
return 0;
}
int proxy_ssh_cipher_free(void) {
#if OPENSSL_VERSION_NUMBER >= 0x1000000fL
EVP_CIPHER_CTX_free(read_ctxs[0]);
EVP_CIPHER_CTX_free(read_ctxs[1]);
EVP_CIPHER_CTX_free(write_ctxs[0]);
EVP_CIPHER_CTX_free(write_ctxs[1]);
#endif /* OpenSSL-1.0.0 and later */
return 0;
}
#endif /* PR_USE_OPENSSL */
proftpd-mod_proxy-0.9.5/lib/proxy/ssh/compress.c 0000664 0000000 0000000 00000034423 14757370167 0021773 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH compression
* Copyright (c) 2021-2022 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "proxy/ssh/msg.h"
#include "proxy/ssh/packet.h"
#include "proxy/ssh/crypto.h"
#include "proxy/ssh/compress.h"
#if defined(PR_USE_OPENSSL)
#ifdef HAVE_ZLIB_H
#include
static const char *trace_channel = "proxy.ssh.compress";
struct proxy_ssh_compress {
int use_zlib;
int stream_ready;
};
/* We need to keep the old compression contexts around, so that we can handle
* N arbitrary packets to/from the client using the old contexts, as during
* rekeying. Thus we have two read compression contexts, two write compression
* contexts. The compression idx variable indicates which of the contexts is
* currently in use.
*/
static struct proxy_ssh_compress read_compresses[] = {
{ 0, FALSE },
{ 0, FALSE }
};
static z_stream read_streams[2];
static struct proxy_ssh_compress write_compresses[] = {
{ 0, FALSE },
{ 0, FALSE }
};
static z_stream write_streams[2];
static unsigned int read_comp_idx = 0;
static unsigned int write_comp_idx = 0;
static unsigned int get_next_read_index(void) {
if (read_comp_idx == 1) {
return 0;
}
return 1;
}
static unsigned int get_next_write_index(void) {
if (write_comp_idx == 1) {
return 0;
}
return 1;
}
static void switch_read_compress(int flags) {
struct proxy_ssh_compress *comp;
z_stream *stream;
comp = &(read_compresses[read_comp_idx]);
stream = &(read_streams[read_comp_idx]);
/* First we can free up the read stream, kept from rekeying. */
if (comp->use_zlib == flags &&
comp->stream_ready == TRUE) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"done decompressing data: decompressed %" PR_LU " bytes to %" PR_LU
" bytes of data (%.2f)", (pr_off_t) stream->total_in,
(pr_off_t) stream->total_out,
stream->total_in == 0 ? 0.0 :
(float) stream->total_out / stream->total_in);
inflateEnd(stream);
comp->use_zlib = 0;
comp->stream_ready = FALSE;
/* Now we can switch the index. */
if (read_comp_idx == 1) {
read_comp_idx = 0;
return;
}
read_comp_idx = 1;
}
}
static void switch_write_compress(int flags) {
struct proxy_ssh_compress *comp;
z_stream *stream;
comp = &(write_compresses[write_comp_idx]);
stream = &(write_streams[write_comp_idx]);
/* First we can free up the write stream, kept from rekeying. */
if (comp->use_zlib == flags &&
comp->stream_ready == TRUE) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"done compressing data: compressed %" PR_LU " bytes to %" PR_LU
" bytes of data (%.2f)", (pr_off_t) stream->total_in,
(pr_off_t) stream->total_out,
stream->total_in == 0 ? 0.0 :
(float) stream->total_out / stream->total_in);
deflateEnd(stream);
comp->use_zlib = 0;
comp->stream_ready = FALSE;
/* Now we can switch the index. */
if (write_comp_idx == 1) {
write_comp_idx = 0;
return;
}
write_comp_idx = 1;
}
}
const char *proxy_ssh_compress_get_read_algo(void) {
struct proxy_ssh_compress *comp;
comp = &(read_compresses[read_comp_idx]);
if (comp->use_zlib != 0) {
if (comp->use_zlib == PROXY_SSH_COMPRESS_FL_NEW_KEY) {
return "zlib";
}
if (comp->use_zlib == PROXY_SSH_COMPRESS_FL_AUTHENTICATED) {
return "zlib@openssh.com";
}
}
return "none";
}
int proxy_ssh_compress_set_read_algo(pool *p, const char *algo) {
unsigned int idx = read_comp_idx;
/* Currently unused. */
(void) p;
if (read_compresses[idx].stream_ready) {
/* If we have an existing stream, it means that we are currently
* rekeying.
*/
idx = get_next_read_index();
}
if (strcmp(algo, "zlib@openssh.com") == 0) {
read_compresses[idx].use_zlib = PROXY_SSH_COMPRESS_FL_AUTHENTICATED;
return 0;
}
if (strcmp(algo, "zlib") == 0) {
read_compresses[idx].use_zlib = PROXY_SSH_COMPRESS_FL_NEW_KEY;
return 0;
}
if (strcmp(algo, "none") == 0) {
return 0;
}
errno = EINVAL;
return -1;
}
int proxy_ssh_compress_init_read(int flags) {
struct proxy_ssh_compress *comp;
z_stream *stream;
switch_read_compress(flags);
comp = &(read_compresses[read_comp_idx]);
stream = &(read_streams[read_comp_idx]);
if (comp->use_zlib == flags &&
comp->stream_ready == FALSE) {
int zres;
zres = inflateInit(stream);
if (zres != Z_OK) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error preparing decompression stream (%d)", zres);
}
comp->stream_ready = TRUE;
}
return 0;
}
int proxy_ssh_compress_read_data(struct proxy_ssh_packet *pkt) {
struct proxy_ssh_compress *comp;
z_stream *stream;
comp = &(read_compresses[read_comp_idx]);
stream = &(read_streams[read_comp_idx]);
if (comp->use_zlib != 0 &&
comp->stream_ready == TRUE) {
unsigned char buf[16384], *input;
char *payload;
uint32_t input_len, payload_len = 0, payload_sz;
pool *sub_pool;
int zres;
if (pkt->payload_len == 0) {
return 0;
}
sub_pool = make_sub_pool(pkt->pool);
/* Use a copy of the payload, rather than the actual payload itself,
* as zlib may alter the payload contents and then encounter an error.
*/
input_len = pkt->payload_len;
input = palloc(sub_pool, input_len);
memcpy(input, pkt->payload, input_len);
/* Try to guess at how big the uncompressed data will be. Optimistic
* estimate, for now, will be a factor of 8.
*/
payload_sz = input_len * 8;
payload = palloc(sub_pool, payload_sz);
stream->next_in = input;
stream->avail_in = input_len;
while (TRUE) {
size_t copy_len = 0;
pr_signals_handle();
stream->next_out = buf;
stream->avail_out = sizeof(buf);
zres = inflate(stream, Z_SYNC_FLUSH);
switch (zres) {
case Z_OK:
copy_len = sizeof(buf) - stream->avail_out;
/* Allocate more space for the data if necessary. */
if ((payload_len + copy_len) > payload_sz) {
uint32_t new_sz;
char *tmp;
pr_signals_handle();
new_sz = payload_sz;
while ((payload_len + copy_len) > new_sz) {
pr_signals_handle();
/* Keep increasing the size until it is large enough. */
new_sz += payload_sz;
}
pr_trace_msg(trace_channel, 20,
"allocating larger payload size (%lu bytes) for "
"inflated data (%lu bytes) plus existing payload %lu bytes",
(unsigned long) new_sz, (unsigned long) copy_len,
(unsigned long) payload_len);
tmp = palloc(sub_pool, new_sz);
memcpy(tmp, payload, payload_len);
payload = tmp;
payload_sz = new_sz;
}
if (copy_len > 0) {
memcpy(payload + payload_len, buf, copy_len);
payload_len += copy_len;
pr_trace_msg(trace_channel, 20,
"inflated %lu bytes to %lu bytes",
(unsigned long) input_len, (unsigned long) copy_len);
}
continue;
case Z_BUF_ERROR:
break;
default:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unhandled zlib error (%d) while decompressing", zres);
destroy_pool(sub_pool);
return -1;
}
break;
}
/* Make sure that pkt->payload has enough room for the uncompressed data.
* If not, allocate a larger buffer.
*/
if (pkt->payload_len < payload_len) {
pkt->payload = palloc(pkt->pool, payload_len);
}
memcpy(pkt->payload, payload, payload_len);
pkt->payload_len = payload_len;
pr_trace_msg(trace_channel, 20,
"finished inflating (payload len = %lu bytes)",
(unsigned long) payload_len);
destroy_pool(sub_pool);
}
return 0;
}
const char *proxy_ssh_compress_get_write_algo(void) {
struct proxy_ssh_compress *comp;
comp = &(write_compresses[write_comp_idx]);
if (comp->use_zlib != 0) {
if (comp->use_zlib == PROXY_SSH_COMPRESS_FL_NEW_KEY) {
return "zlib";
}
if (comp->use_zlib == PROXY_SSH_COMPRESS_FL_AUTHENTICATED) {
return "zlib@openssh.com";
}
}
return "none";
}
int proxy_ssh_compress_set_write_algo(pool *p, const char *algo) {
unsigned int idx = write_comp_idx;
/* Currently unused. */
(void) p;
if (write_compresses[idx].stream_ready) {
/* If we have an existing stream, it means that we are currently
* rekeying.
*/
idx = get_next_write_index();
}
if (strcmp(algo, "zlib@openssh.com") == 0) {
write_compresses[idx].use_zlib = PROXY_SSH_COMPRESS_FL_AUTHENTICATED;
return 0;
}
if (strcmp(algo, "zlib") == 0) {
write_compresses[idx].use_zlib = PROXY_SSH_COMPRESS_FL_NEW_KEY;
return 0;
}
if (strcmp(algo, "none") == 0) {
return 0;
}
errno = EINVAL;
return -1;
}
int proxy_ssh_compress_init_write(int flags) {
struct proxy_ssh_compress *comp;
z_stream *stream;
switch_write_compress(flags);
comp = &(write_compresses[write_comp_idx]);
stream = &(write_streams[write_comp_idx]);
if (comp->use_zlib == flags &&
comp->stream_ready == FALSE) {
int zres;
zres = deflateInit(stream, Z_DEFAULT_COMPRESSION);
if (zres != Z_OK) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error preparing compression stream (%d)", zres);
}
comp->stream_ready = TRUE;
}
return 0;
}
int proxy_ssh_compress_write_data(struct proxy_ssh_packet *pkt) {
struct proxy_ssh_compress *comp;
z_stream *stream;
comp = &(write_compresses[write_comp_idx]);
stream = &(write_streams[write_comp_idx]);
if (comp->use_zlib != 0 &&
comp->stream_ready == TRUE) {
unsigned char buf[16384], *input;
char *payload;
uint32_t input_len, payload_len = 0, payload_sz;
pool *sub_pool;
int zres;
if (pkt->payload_len == 0) {
return 0;
}
sub_pool = make_sub_pool(pkt->pool);
/* Use a copy of the payload, rather than the actual payload itself,
* as zlib may alter the payload contents and then encounter an error.
*/
input_len = pkt->payload_len;
input = palloc(sub_pool, input_len);
memcpy(input, pkt->payload, input_len);
/* Try to guess at how small the compressed data will be. Optimistic
* estimate, for now, will be a factor of 2, with a minimum of 1K.
*/
payload_sz = 1024;
if ((input_len * 2) > payload_sz) {
payload_sz = input_len * 2;
}
payload = palloc(sub_pool, payload_sz);
stream->next_in = input;
stream->avail_in = input_len;
stream->avail_out = 0;
while (stream->avail_out == 0) {
size_t copy_len = 0;
pr_signals_handle();
stream->next_out = buf;
stream->avail_out = sizeof(buf);
zres = deflate(stream, Z_SYNC_FLUSH);
if (zres == Z_OK) {
copy_len = sizeof(buf) - stream->avail_out;
/* Allocate more space for the data if necessary. */
if ((payload_len + copy_len) > payload_sz) {
uint32_t new_sz;
char *tmp;
new_sz = payload_sz;
while ((payload_len + copy_len) > new_sz) {
pr_signals_handle();
/* Keep increasing the size until it is large enough. */
new_sz += payload_sz;
}
pr_trace_msg(trace_channel, 20,
"allocating larger payload size (%lu bytes) for "
"deflated data (%lu bytes) plus existing payload %lu bytes",
(unsigned long) new_sz, (unsigned long) copy_len,
(unsigned long) payload_len);
tmp = palloc(sub_pool, new_sz);
memcpy(tmp, payload, payload_len);
payload = tmp;
payload_sz = new_sz;
}
memcpy(payload + payload_len, buf, copy_len);
payload_len += copy_len;
pr_trace_msg(trace_channel, 20,
"deflated %lu bytes to %lu bytes",
(unsigned long) input_len, (unsigned long) copy_len);
} else {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unhandled zlib error (%d) while compressing", zres);
destroy_pool(sub_pool);
errno = EIO;
return -1;
}
}
if (payload_len > 0) {
if (pkt->payload_len < payload_len) {
pkt->payload = palloc(pkt->pool, payload_len);
}
memcpy(pkt->payload, payload, payload_len);
pkt->payload_len = payload_len;
pr_trace_msg(trace_channel, 20,
"finished deflating (payload len = %lu bytes)",
(unsigned long) payload_len);
}
destroy_pool(sub_pool);
}
return 0;
}
#else
int proxy_ssh_compress_init_read(int flags) {
return 0;
}
const char *proxy_ssh_compress_get_read_algo(void) {
return "none";
}
int proxy_ssh_compress_set_read_algo(pool *p, const char *algo) {
(void) p;
if (strcmp(algo, "none") == 0) {
return 0;
}
errno = EINVAL;
return -1;
}
int proxy_ssh_compress_read_data(struct proxy_ssh_packet *pkt) {
return 0;
}
int proxy_ssh_compress_init_write(int flags) {
return 0;
}
const char *proxy_ssh_compress_get_write_algo(void) {
return "none";
}
int proxy_ssh_compress_set_write_algo(pool *p, const char *algo) {
(void) p;
if (strcmp(algo, "none") == 0) {
return 0;
}
errno = EINVAL;
return -1;
}
int proxy_ssh_compress_write_data(struct proxy_ssh_packet *pkt) {
return 0;
}
#endif /* !HAVE_ZLIB_H */
#endif /* PR_USE_OPENSSL */
proftpd-mod_proxy-0.9.5/lib/proxy/ssh/crypto.c 0000664 0000000 0000000 00000120170 14757370167 0021453 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH crypto
* Copyright (c) 2021-2024 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "proxy/ssh/crypto.h"
#include "proxy/ssh/umac.h"
#if defined(PR_USE_OPENSSL)
#include
#if !defined(OPENSSL_NO_BF)
# include
#endif /* !OPENSSL_NO_BF */
#if !defined(OPENSSL_NO_DES)
# include
#endif /* !OPENSSL_NO_DES */
#include
#if OPENSSL_VERSION_NUMBER > 0x000907000L && \
OPENSSL_VERSION_NUMBER < 0x10100000L && \
defined(PR_USE_OPENSSL_ENGINE)
# include
static const char *crypto_engine = NULL;
#endif
struct proxy_ssh_cipher {
const char *name;
const char *openssl_name;
/* Used mostly for AEAD algorithms like GCM. */
size_t auth_len;
/* Used mostly for the RC4/ArcFour algorithms, for mitigating attacks
* based on the first N bytes of the keystream.
*/
size_t discard_len;
const EVP_CIPHER *(*get_type)(void);
/* Is this cipher enabled by default? If FALSE, then this cipher must
* be explicitly requested via SFTPCiphers.
*/
int enabled;
/* Is this cipher usable when FIPS is enabled? If FALSE, then this
* cipher must NOT be advertised to clients in the KEXINIT.
*/
int fips_allowed;
};
/* Currently, OpenSSL does NOT support AES CTR modes (not sure why).
* Until then, we have to provide our own CTR code, for some of the ciphers
* recommended by RFC4344.
*
* And according to:
*
* http://www.cpni.gov.uk/Docs/Vulnerability_Advisory_SSH.txt
*
* it is highly recommended to use CTR mode ciphers, rather than CBC mode,
* in order to avoid leaking plaintext.
*/
static struct proxy_ssh_cipher ciphers[] = {
/* The handling of NULL openssl_name and get_type fields is done in
* proxy_ssh_crypto_get_cipher(), as special cases.
*/
{ "aes256-ctr", NULL, 0, 0, NULL, TRUE, TRUE },
{ "aes192-ctr", NULL, 0, 0, NULL, TRUE, TRUE },
{ "aes128-ctr", NULL, 0, 0, NULL, TRUE, TRUE },
#if defined(HAVE_EVP_AES_256_GCM_OPENSSL)
{ "aes256-gcm@openssh.com", "aes-256-gcm", 16, 0, EVP_aes_256_gcm, TRUE, TRUE },
{ "aes128-gcm@openssh.com", "aes-128-gcm", 16, 0, EVP_aes_128_gcm, TRUE, TRUE },
#endif
#if !defined(HAVE_AES_CRIPPLED_OPENSSL)
{ "aes256-cbc", "aes-256-cbc", 0, 0, EVP_aes_256_cbc, TRUE, TRUE },
{ "aes192-cbc", "aes-192-cbc", 0, 0, EVP_aes_192_cbc, TRUE, TRUE },
#endif /* !HAVE_AES_CRIPPLED_OPENSSL */
{ "aes128-cbc", "aes-128-cbc", 0, 0, EVP_aes_128_cbc, TRUE, TRUE },
#if !defined(OPENSSL_NO_BF)
# if OPENSSL_VERSION_NUMBER < 0x30000000L
{ "blowfish-ctr", NULL, 0, 0, NULL, FALSE, FALSE },
# endif /* Prior to OpenSSL 3.x */
{ "blowfish-cbc", "bf-cbc", 0, 0, EVP_bf_cbc, FALSE, FALSE },
#endif /* !OPENSSL_NO_BF */
#if !defined(OPENSSL_NO_CAST)
{ "cast128-cbc", "cast5-cbc", 0, 0, EVP_cast5_cbc, TRUE, FALSE },
#endif /* !OPENSSL_NO_CAST */
#if !defined(OPENSSL_NO_RC4)
{ "arcfour256", "rc4", 0, 1536, EVP_rc4, FALSE, FALSE },
{ "arcfour128", "rc4", 0, 1536, EVP_rc4, FALSE, FALSE },
#endif /* !OPENSSL_NO_RC4 */
#if !defined(OPENSSL_NO_DES)
# if OPENSSL_VERSION_NUMBER < 0x30000000L
{ "3des-ctr", NULL, 0, 0, NULL, TRUE, TRUE },
# endif /* Prior to OpenSSL 3.x */
{ "3des-cbc", "des-ede3-cbc", 0, 0, EVP_des_ede3_cbc, TRUE, TRUE },
#endif /* !OPENSSL_NO_DES */
{ "none", "null", 0, 0, EVP_enc_null, FALSE, TRUE },
{ NULL, NULL, 0, 0, NULL, FALSE, FALSE }
};
struct proxy_ssh_digest {
const char *name;
const char *openssl_name;
const EVP_MD *(*get_type)(void);
uint32_t mac_len;
/* Is this MAC enabled by default? If FALSE, then this MAC must be
* explicitly requested via SFTPDigests.
*/
int enabled;
/* Is this MAC usable when FIPS is enabled? If FALSE, then this digest must
* NOT be advertised to clients in the KEXINIT.
*/
int fips_allowed;
};
static struct proxy_ssh_digest digests[] = {
/* The handling of NULL openssl_name and get_type fields is done in
* proxy_ssh_crypto_get_digest(), as special cases.
*/
#if defined(HAVE_SHA256_OPENSSL)
{ "hmac-sha2-256", "sha256", EVP_sha256, 0, TRUE, TRUE },
{ "hmac-sha2-256-etm@openssh.com", "sha256", EVP_sha256, 0, TRUE, TRUE },
#endif /* SHA256 support in OpenSSL */
#if defined(HAVE_SHA512_OPENSSL)
{ "hmac-sha2-512", "sha512", EVP_sha512, 0, TRUE, TRUE },
{ "hmac-sha2-512-etm@openssh.com", "sha512", EVP_sha512, 0, TRUE, TRUE },
#endif /* SHA512 support in OpenSSL */
{ "hmac-sha1", "sha1", EVP_sha1, 0, TRUE, TRUE },
{ "hmac-sha1-etm@openssh.com", "sha1",EVP_sha1, 0, TRUE, TRUE },
{ "hmac-sha1-96", "sha1", EVP_sha1, 12, TRUE, TRUE },
{ "hmac-sha1-96-etm@openssh.com", "sha1", EVP_sha1, 12, TRUE, TRUE },
{ "hmac-md5", "md5", EVP_md5, 0, FALSE, FALSE },
{ "hmac-md5-etm@openssh.com", "md5", EVP_md5, 0, FALSE, FALSE },
{ "hmac-md5-96", "md5", EVP_md5, 12, FALSE, FALSE },
{ "hmac-md5-96-etm@openssh.com", "md5",EVP_md5, 12, FALSE, FALSE },
#if !defined(OPENSSL_NO_RIPEMD)
{ "hmac-ripemd160", "rmd160", EVP_ripemd160, 0, FALSE, FALSE },
#endif /* !OPENSSL_NO_RIPEMD */
#if OPENSSL_VERSION_NUMBER > 0x000907000L
{ "umac-64@openssh.com", NULL, NULL, 8, TRUE, FALSE },
{ "umac-64-etm@openssh.com", NULL, NULL, 8, TRUE, FALSE },
{ "umac-128@openssh.com", NULL, NULL, 16, TRUE, FALSE },
{ "umac-128-etm@openssh.com", NULL, NULL, 16, TRUE, FALSE },
#endif /* OpenSSL-0.9.7 or later */
{ "none", "null", EVP_md_null, 0, FALSE, TRUE },
{ NULL, NULL, NULL, 0, FALSE, FALSE }
};
static const char *trace_channel = "proxy.ssh.crypto";
#if OPENSSL_VERSION_NUMBER < 0x30000000L
static void ctr_incr(unsigned char *ctr, size_t len) {
register int i;
if (len == 0) {
return;
}
for (i = len - 1; i >= 0; i--) {
/* If we haven't overflowed, we're done. */
if (++ctr[i]) {
return;
}
}
}
#endif /* Prior to OpenSSL 3.x */
#if !defined(OPENSSL_NO_BF) && \
OPENSSL_VERSION_NUMBER < 0x30000000L
/* Blowfish CTR mode implementation */
struct bf_ctr_ex {
BF_KEY key;
unsigned char counter[BF_BLOCK];
};
static int init_bf_ctr(EVP_CIPHER_CTX *ctx, const unsigned char *key,
const unsigned char *iv, int enc) {
struct bf_ctr_ex *bce;
bce = EVP_CIPHER_CTX_get_app_data(ctx);
if (bce == NULL) {
/* Allocate our data structure. */
bce = calloc(1, sizeof(struct bf_ctr_ex));
if (bce == NULL) {
pr_log_pri(PR_LOG_ALERT, MOD_PROXY_VERSION ": Out of memory!");
_exit(1);
}
EVP_CIPHER_CTX_set_app_data(ctx, bce);
}
if (key != NULL) {
int key_len;
# if OPENSSL_VERSION_NUMBER == 0x0090805fL
/* OpenSSL 0.9.8e had a bug where EVP_CIPHER_CTX_key_length() returned
* the cipher key length rather than the context key length.
*/
key_len = ctx->key_len;
# else
key_len = EVP_CIPHER_CTX_key_length(ctx);
# endif
BF_set_key(&(bce->key), key_len, key);
}
if (iv != NULL) {
memcpy(bce->counter, iv, BF_BLOCK);
}
return 1;
}
static int cleanup_bf_ctr(EVP_CIPHER_CTX *ctx) {
struct bf_ctr_ex *bce;
bce = EVP_CIPHER_CTX_get_app_data(ctx);
if (bce != NULL) {
pr_memscrub(bce, sizeof(struct bf_ctr_ex));
free(bce);
EVP_CIPHER_CTX_set_app_data(ctx, NULL);
}
return 1;
}
static int do_bf_ctr(EVP_CIPHER_CTX *ctx, unsigned char *dst,
const unsigned char *src, size_t len) {
struct bf_ctr_ex *bce;
unsigned int n;
unsigned char buf[BF_BLOCK];
if (len == 0)
return 1;
bce = EVP_CIPHER_CTX_get_app_data(ctx);
if (bce == NULL)
return 0;
n = 0;
while ((len--) > 0) {
pr_signals_handle();
if (n == 0) {
BF_LONG ctr[2];
/* Ideally, we would not be using htonl/ntohl here, and the following
* code would be as simple as:
*
* memcpy(buf, bce->counter, BF_BLOCK);
* BF_encrypt((BF_LONG *) buf, &(bce->key));
*
* However, the above is susceptible to endianness issues. The only
* client that I could find which implements the blowfish-ctr cipher,
* PuTTy, uses its own big-endian Blowfish implementation. So the
* above code will work with PuTTy, but only on big-endian machines.
* For little-endian machines, we need to handle the endianness
* ourselves. Whee.
*/
memcpy(&(ctr[0]), bce->counter, sizeof(BF_LONG));
memcpy(&(ctr[1]), bce->counter + sizeof(BF_LONG), sizeof(BF_LONG));
/* Convert to big-endian values before encrypting the counter... */
ctr[0] = htonl(ctr[0]);
ctr[1] = htonl(ctr[1]);
BF_encrypt(ctr, &(bce->key));
/* ...and convert back to little-endian before XOR'ing the counter in. */
ctr[0] = ntohl(ctr[0]);
ctr[1] = ntohl(ctr[1]);
memcpy(buf, ctr, BF_BLOCK);
ctr_incr(bce->counter, BF_BLOCK);
}
*(dst++) = *(src++) ^ buf[n];
n = (n + 1) % BF_BLOCK;
}
return 1;
}
static const EVP_CIPHER *get_bf_ctr_cipher(void) {
EVP_CIPHER *cipher;
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
/* XXX TODO: At some point, we also need to call EVP_CIPHER_meth_free() on
* this, to avoid a resource leak.
*/
cipher = EVP_CIPHER_meth_new(NID_bf_cbc, BF_BLOCK, 32);
EVP_CIPHER_meth_set_iv_length(cipher, BF_BLOCK);
EVP_CIPHER_meth_set_init(cipher, init_bf_ctr);
EVP_CIPHER_meth_set_cleanup(cipher, cleanup_bf_ctr);
EVP_CIPHER_meth_set_do_cipher(cipher, do_bf_ctr);
EVP_CIPHER_meth_set_flags(cipher, EVP_CIPH_CBC_MODE|EVP_CIPH_VARIABLE_LENGTH|EVP_CIPH_ALWAYS_CALL_INIT|EVP_CIPH_CUSTOM_IV);
#else
static EVP_CIPHER bf_ctr_cipher;
memset(&bf_ctr_cipher, 0, sizeof(EVP_CIPHER));
bf_ctr_cipher.nid = NID_bf_cbc;
bf_ctr_cipher.block_size = BF_BLOCK;
bf_ctr_cipher.iv_len = BF_BLOCK;
bf_ctr_cipher.key_len = 32;
bf_ctr_cipher.init = init_bf_ctr;
bf_ctr_cipher.cleanup = cleanup_bf_ctr;
bf_ctr_cipher.do_cipher = do_bf_ctr;
bf_ctr_cipher.flags = EVP_CIPH_CBC_MODE|EVP_CIPH_VARIABLE_LENGTH|EVP_CIPH_ALWAYS_CALL_INIT|EVP_CIPH_CUSTOM_IV;
cipher = &bf_ctr_cipher;
#endif /* prior to OpenSSL-1.1.0 */
return cipher;
}
#endif /* !OPENSSL_NO_BF and OpenSSL prior to 3.x */
#if !defined(OPENSSL_NO_DES) && \
OPENSSL_VERSION_NUMBER < 0x30000000L
/* 3DES CTR mode implementation */
struct des3_ctr_ex {
DES_key_schedule sched[3];
unsigned char counter[8];
int big_endian;
};
static uint32_t byteswap32(uint32_t in) {
uint32_t out;
out = (((in & 0x000000ff) << 24) |
((in & 0x0000ff00) << 8) |
((in & 0x00ff0000) >> 8) |
((in & 0xff000000) >> 24));
return out;
}
static int init_des3_ctr(EVP_CIPHER_CTX *ctx, const unsigned char *key,
const unsigned char *iv, int enc) {
struct des3_ctr_ex *dce;
dce = EVP_CIPHER_CTX_get_app_data(ctx);
if (dce == NULL) {
/* Allocate our data structure. */
dce = calloc(1, sizeof(struct des3_ctr_ex));
if (dce == NULL) {
pr_log_pri(PR_LOG_ALERT, MOD_PROXY_VERSION ": Out of memory!");
_exit(1);
}
/* Simple test to see if we're on a big- or little-endian machine:
* on big-endian machines, the ntohl() et al will be no-ops.
*/
dce->big_endian = (ntohl(1234) == 1234);
EVP_CIPHER_CTX_set_app_data(ctx, dce);
}
if (key != NULL) {
register unsigned int i;
unsigned char *ptr;
ptr = (unsigned char *) key;
for (i = 0; i < 3; i++) {
DES_cblock material[8];
memcpy(material, ptr, 8);
ptr += 8;
DES_set_key_unchecked(material, &(dce->sched[i]));
}
}
if (iv != NULL) {
memcpy(dce->counter, iv, 8);
}
return 1;
}
static int cleanup_des3_ctr(EVP_CIPHER_CTX *ctx) {
struct des3_ctr_ex *dce;
dce = EVP_CIPHER_CTX_get_app_data(ctx);
if (dce != NULL) {
pr_memscrub(dce, sizeof(struct des3_ctr_ex));
free(dce);
EVP_CIPHER_CTX_set_app_data(ctx, NULL);
}
return 1;
}
static int do_des3_ctr(EVP_CIPHER_CTX *ctx, unsigned char *dst,
const unsigned char *src, size_t len) {
struct des3_ctr_ex *dce;
unsigned int n;
unsigned char buf[8];
if (len == 0) {
return 1;
}
dce = EVP_CIPHER_CTX_get_app_data(ctx);
if (dce == NULL) {
return 0;
}
n = 0;
while ((len--) > 0) {
pr_signals_handle();
if (n == 0) {
DES_LONG ctr[2];
memcpy(&(ctr[0]), dce->counter, sizeof(DES_LONG));
memcpy(&(ctr[1]), dce->counter + sizeof(DES_LONG), sizeof(DES_LONG));
if (dce->big_endian) {
/* If we are on a big-endian machine, we need to initialize the counter
* using little-endian values, since that is what OpenSSL's
* DES_encryptX() functions expect.
*/
ctr[0] = byteswap32(ctr[0]);
ctr[1] = byteswap32(ctr[1]);
}
DES_encrypt3(ctr, &(dce->sched[0]), &(dce->sched[1]), &(dce->sched[2]));
if (dce->big_endian) {
ctr[0] = byteswap32(ctr[0]);
ctr[1] = byteswap32(ctr[1]);
}
memcpy(buf, ctr, 8);
ctr_incr(dce->counter, 8);
}
*(dst++) = *(src++) ^ buf[n];
n = (n + 1) % 8;
}
return 1;
}
static const EVP_CIPHER *get_des3_ctr_cipher(void) {
EVP_CIPHER *cipher;
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
unsigned long flags;
/* XXX TODO: At some point, we also need to call EVP_CIPHER_meth_free() on
* this, to avoid a resource leak.
*/
cipher = EVP_CIPHER_meth_new(NID_des_ede3_ecb, 8, 24);
EVP_CIPHER_meth_set_iv_length(cipher, 8);
EVP_CIPHER_meth_set_init(cipher, init_des3_ctr);
EVP_CIPHER_meth_set_cleanup(cipher, cleanup_des3_ctr);
EVP_CIPHER_meth_set_do_cipher(cipher, do_des3_ctr);
flags = EVP_CIPH_CBC_MODE|EVP_CIPH_VARIABLE_LENGTH|EVP_CIPH_ALWAYS_CALL_INIT|EVP_CIPH_CUSTOM_IV;
#ifdef OPENSSL_FIPS
flags |= EVP_CIPH_FLAG_FIPS;
#endif /* OPENSSL_FIPS */
EVP_CIPHER_meth_set_flags(cipher, flags);
#else
static EVP_CIPHER des3_ctr_cipher;
memset(&des3_ctr_cipher, 0, sizeof(EVP_CIPHER));
des3_ctr_cipher.nid = NID_des_ede3_ecb;
des3_ctr_cipher.block_size = 8;
des3_ctr_cipher.iv_len = 8;
des3_ctr_cipher.key_len = 24;
des3_ctr_cipher.init = init_des3_ctr;
des3_ctr_cipher.cleanup = cleanup_des3_ctr;
des3_ctr_cipher.do_cipher = do_des3_ctr;
des3_ctr_cipher.flags = EVP_CIPH_CBC_MODE|EVP_CIPH_VARIABLE_LENGTH|EVP_CIPH_ALWAYS_CALL_INIT|EVP_CIPH_CUSTOM_IV;
#ifdef OPENSSL_FIPS
des3_ctr_cipher.flags |= EVP_CIPH_FLAG_FIPS;
#endif /* OPENSSL_FIPS */
cipher = &des3_ctr_cipher;
#endif /* prior to OpenSSL-1.1.0 */
return cipher;
}
#endif /* !OPENSSL_NO_DES and OpenSSL prior to 3.x */
#if !defined(HAVE_EVP_AES_128_CTR_OPENSSL) && \
!defined(HAVE_EVP_AES_192_CTR_OPENSSL) && \
!defined(HAVE_EVP_AES_256_CTR_OPENSSL)
/* AES CTR mode implementation */
struct aes_ctr_ex {
AES_KEY key;
unsigned char counter[AES_BLOCK_SIZE];
unsigned char enc_counter[AES_BLOCK_SIZE];
unsigned int num;
};
static int init_aes_ctr(EVP_CIPHER_CTX *ctx, const unsigned char *key,
const unsigned char *iv, int enc) {
struct aes_ctr_ex *ace;
ace = EVP_CIPHER_CTX_get_app_data(ctx);
if (ace == NULL) {
/* Allocate our data structure. */
ace = calloc(1, sizeof(struct aes_ctr_ex));
if (ace == NULL) {
pr_log_pri(PR_LOG_ALERT, MOD_PROXY_VERSION ": Out of memory!");
_exit(1);
}
EVP_CIPHER_CTX_set_app_data(ctx, ace);
}
if (key != NULL) {
int nbits;
nbits = EVP_CIPHER_CTX_key_length(ctx) * 8;
AES_set_encrypt_key(key, nbits, &(ace->key));
}
if (iv != NULL) {
memcpy(ace->counter, iv, AES_BLOCK_SIZE);
}
return 1;
}
static int cleanup_aes_ctr(EVP_CIPHER_CTX *ctx) {
struct aes_ctr_ex *ace;
ace = EVP_CIPHER_CTX_get_app_data(ctx);
if (ace != NULL) {
pr_memscrub(ace, sizeof(struct aes_ctr_ex));
free(ace);
EVP_CIPHER_CTX_set_app_data(ctx, NULL);
}
return 1;
}
static int do_aes_ctr(EVP_CIPHER_CTX *ctx, unsigned char *dst,
const unsigned char *src, size_t len) {
struct aes_ctr_ex *ace;
# if OPENSSL_VERSION_NUMBER <= 0x0090704fL || \
OPENSSL_VERSION_NUMBER >= 0x10100000L
unsigned int n;
unsigned char buf[AES_BLOCK_SIZE];
# endif
if (len == 0) {
return 1;
}
ace = EVP_CIPHER_CTX_get_app_data(ctx);
if (ace == NULL) {
return 0;
}
# if OPENSSL_VERSION_NUMBER <= 0x0090704fL || \
OPENSSL_VERSION_NUMBER >= 0x10100000L
/* In OpenSSL-0.9.7d and earlier, the AES CTR code did not properly handle
* the IV as big-endian; this would cause the dreaded "Incorrect MAC
* received on packet" error when using clients e.g. PuTTy. To see
* the difference in OpenSSL, you have do manually do:
*
* diff -u openssl-0.9.7d/crypto/aes/aes_ctr.c \
* openssl-0.9.7e/crypto/aes/aes_ctr.c
*
* This change is not documented in OpenSSL's CHANGES file. Sigh.
*
* And in OpenSSL-1.1.0 and later, the AES CTR code was removed entirely.
*
* Thus for these versions, we have to use our own AES CTR code.
*/
n = 0;
while ((len--) > 0) {
pr_signals_handle();
if (n == 0) {
AES_encrypt(ace->counter, buf, &(ace->key));
ctr_incr(ace->counter, AES_BLOCK_SIZE);
}
*(dst++) = *(src++) ^ buf[n];
n = (n + 1) % AES_BLOCK_SIZE;
}
return 1;
# else
/* Thin wrapper around AES_ctr128_encrypt(). */
AES_ctr128_encrypt(src, dst, len, &(ace->key), ace->counter, ace->enc_counter,
&(ace->num));
# endif
return 1;
}
static int get_aes_ctr_cipher_nid(int key_len) {
int nid;
#ifdef OPENSSL_FIPS
/* Set the NID depending on the key len. */
switch (key_len) {
case 16:
nid = NID_aes_128_cbc;
break;
case 24:
nid = NID_aes_192_cbc;
break;
case 32:
nid = NID_aes_256_cbc;
break;
default:
nid = NID_undef;
break;
}
#else
/* Setting this nid member to something other than NID_undef causes
* interesting problems on an OpenSolaris system, using the provided
* OpenSSL installation's pkcs11 engine via:
*
* ProxySSHCryptoDevice pkcs11
*
* for the mod_sftp config. I'm not sure why; I need to look into this
* issue more.
*
* For posterity, the issues seen when using the above config are
* described below. After sending the NEWKEYS request, mod_sftp
* would log the following, upon receiving the next message from sftp(1):
*
* : SSH2 packet len = 1500737511 bytes
* : SSH2 packet padding len = 95 bytes
* : SSH2 packet payload len = 1500737415 bytes
* : payload len (1500737415 bytes) exceeds max payload len (262144), ignoring payload
* client sent buggy/malicious packet payload length, ignoring
*
* and sftp(1), for its side, would report:
*
* debug1: send SSH2_MSG_SERVICE_REQUEST
* Disconnecting: Bad packet length.
* debug1: Calling cleanup 0x807cc14(0x0)
* Couldn't read packet: Error 0
*/
nid = NID_undef;
#endif /* OPENSSL_FIPS */
return nid;
}
static const EVP_CIPHER *get_aes_ctr_cipher(int key_len) {
EVP_CIPHER *cipher;
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
unsigned long flags;
/* XXX TODO: At some point, we also need to call EVP_CIPHER_meth_free() on
* this, to avoid a resource leak.
*/
cipher = EVP_CIPHER_meth_new(get_aes_ctr_cipher_nid(key_len), AES_BLOCK_SIZE,
key_len);
EVP_CIPHER_meth_set_iv_length(cipher, AES_BLOCK_SIZE);
EVP_CIPHER_meth_set_init(cipher, init_aes_ctr);
EVP_CIPHER_meth_set_cleanup(cipher, cleanup_aes_ctr);
EVP_CIPHER_meth_set_do_cipher(cipher, do_aes_ctr);
flags = EVP_CIPH_CBC_MODE|EVP_CIPH_VARIABLE_LENGTH|EVP_CIPH_ALWAYS_CALL_INIT|EVP_CIPH_CUSTOM_IV;
#ifdef OPENSSL_FIPS
flags |= EVP_CIPH_FLAG_FIPS;
#endif /* OPENSSL_FIPS */
EVP_CIPHER_meth_set_flags(cipher, flags);
#else
static EVP_CIPHER aes_ctr_cipher;
memset(&aes_ctr_cipher, 0, sizeof(EVP_CIPHER));
aes_ctr_cipher.nid = get_aes_ctr_cipher_nid(key_len);
aes_ctr_cipher.block_size = AES_BLOCK_SIZE;
aes_ctr_cipher.iv_len = AES_BLOCK_SIZE;
aes_ctr_cipher.key_len = key_len;
aes_ctr_cipher.init = init_aes_ctr;
aes_ctr_cipher.cleanup = cleanup_aes_ctr;
aes_ctr_cipher.do_cipher = do_aes_ctr;
aes_ctr_cipher.flags = EVP_CIPH_CBC_MODE|EVP_CIPH_VARIABLE_LENGTH|EVP_CIPH_ALWAYS_CALL_INIT|EVP_CIPH_CUSTOM_IV;
# ifdef OPENSSL_FIPS
aes_ctr_cipher.flags |= EVP_CIPH_FLAG_FIPS;
# endif /* OPENSSL_FIPS */
cipher = &aes_ctr_cipher;
#endif /* prior to OpenSSL-1.1.0 */
return cipher;
}
#endif /* OpenSSL implements AES CTR modes */
static int update_umac64(EVP_MD_CTX *ctx, const void *data, size_t len) {
int res;
void *md_data;
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
md_data = EVP_MD_CTX_md_data(ctx);
#else
md_data = ctx->md_data;
#endif /* prior to OpenSSL-1.1.0 */
if (md_data == NULL) {
struct umac_ctx *umac;
void **ptr;
umac = proxy_ssh_umac_new((unsigned char *) data);
if (umac == NULL) {
return 0;
}
ptr = &md_data;
*ptr = umac;
return 1;
}
res = proxy_ssh_umac_update(md_data, (unsigned char *) data, (long) len);
return res;
}
static int update_umac128(EVP_MD_CTX *ctx, const void *data, size_t len) {
int res;
void *md_data;
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
md_data = EVP_MD_CTX_md_data(ctx);
#else
md_data = ctx->md_data;
#endif /* prior to OpenSSL-1.1.0 */
if (md_data == NULL) {
struct umac_ctx *umac;
void **ptr;
umac = proxy_ssh_umac128_new((unsigned char *) data);
if (umac == NULL) {
return 0;
}
ptr = &md_data;
*ptr = umac;
return 1;
}
res = proxy_ssh_umac128_update(md_data, (unsigned char *) data, (long) len);
return res;
}
static int final_umac64(EVP_MD_CTX *ctx, unsigned char *md) {
unsigned char nonce[8];
int res;
void *md_data;
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
md_data = EVP_MD_CTX_md_data(ctx);
#else
md_data = ctx->md_data;
#endif /* prior to OpenSSL-1.1.0 */
res = proxy_ssh_umac_final(md_data, md, nonce);
return res;
}
static int final_umac128(EVP_MD_CTX *ctx, unsigned char *md) {
unsigned char nonce[8];
int res;
void *md_data;
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
md_data = EVP_MD_CTX_md_data(ctx);
#else
md_data = ctx->md_data;
#endif /* prior to OpenSSL-1.1.0 */
res = proxy_ssh_umac128_final(md_data, md, nonce);
return res;
}
static int delete_umac64(EVP_MD_CTX *ctx) {
struct umac_ctx *umac;
void *md_data, **ptr;
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
md_data = EVP_MD_CTX_md_data(ctx);
#else
md_data = ctx->md_data;
#endif /* prior to OpenSSL-1.1.0 */
umac = md_data;
proxy_ssh_umac_delete(umac);
ptr = &md_data;
*ptr = NULL;
return 1;
}
static int delete_umac128(EVP_MD_CTX *ctx) {
struct umac_ctx *umac;
void *md_data, **ptr;
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
md_data = EVP_MD_CTX_md_data(ctx);
#else
md_data = ctx->md_data;
#endif /* prior to OpenSSL-1.1.0 */
umac = md_data;
proxy_ssh_umac128_delete(umac);
ptr = &md_data;
*ptr = NULL;
return 1;
}
static const EVP_MD *get_umac64_digest(void) {
EVP_MD *md;
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
/* XXX TODO: At some point, we also need to call EVP_MD_meth_free() on
* this, to avoid a resource leak.
*/
md = EVP_MD_meth_new(NID_undef, NID_undef);
EVP_MD_meth_set_input_blocksize(md, 32);
EVP_MD_meth_set_result_size(md, 8);
EVP_MD_meth_set_flags(md, 0UL);
EVP_MD_meth_set_update(md, update_umac64);
EVP_MD_meth_set_final(md, final_umac64);
EVP_MD_meth_set_cleanup(md, delete_umac64);
#else
static EVP_MD umac64_digest;
memset(&umac64_digest, 0, sizeof(EVP_MD));
umac64_digest.type = NID_undef;
umac64_digest.pkey_type = NID_undef;
umac64_digest.md_size = 8;
umac64_digest.flags = 0UL;
umac64_digest.update = update_umac64;
umac64_digest.final = final_umac64;
umac64_digest.cleanup = delete_umac64;
umac64_digest.block_size = 32;
md = &umac64_digest;
#endif /* prior to OpenSSL-1.1.0 */
return md;
}
static const EVP_MD *get_umac128_digest(void) {
EVP_MD *md;
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
/* XXX TODO: At some point, we also need to call EVP_MD_meth_free() on
* this, to avoid a resource leak.
*/
md = EVP_MD_meth_new(NID_undef, NID_undef);
EVP_MD_meth_set_input_blocksize(md, 64);
EVP_MD_meth_set_result_size(md, 16);
EVP_MD_meth_set_flags(md, 0UL);
EVP_MD_meth_set_update(md, update_umac128);
EVP_MD_meth_set_final(md, final_umac128);
EVP_MD_meth_set_cleanup(md, delete_umac128);
#else
static EVP_MD umac128_digest;
memset(&umac128_digest, 0, sizeof(EVP_MD));
umac128_digest.type = NID_undef;
umac128_digest.pkey_type = NID_undef;
umac128_digest.md_size = 16;
umac128_digest.flags = 0UL;
umac128_digest.update = update_umac128;
umac128_digest.final = final_umac128;
umac128_digest.cleanup = delete_umac128;
umac128_digest.block_size = 64;
md = &umac128_digest;
#endif /* prior to OpenSSL-1.1.0 */
return md;
}
const EVP_CIPHER *proxy_ssh_crypto_get_cipher(const char *name, size_t *key_len,
size_t *auth_len, size_t *discard_len) {
register unsigned int i;
for (i = 0; ciphers[i].name; i++) {
if (strcmp(ciphers[i].name, name) == 0) {
const EVP_CIPHER *cipher;
if (strcmp(name, "blowfish-ctr") == 0) {
#if !defined(OPENSSL_NO_BF) && \
OPENSSL_VERSION_NUMBER < 0x30000000L
cipher = get_bf_ctr_cipher();
#else
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"'%s' cipher unsupported", name);
errno = ENOENT;
return NULL;
#endif /* !OPENSSL_NO_BF and OpenSSL prior to 3.x */
#if OPENSSL_VERSION_NUMBER > 0x000907000L
} else if (strcmp(name, "3des-ctr") == 0) {
# if !defined(OPENSSL_NO_DES) && \
OPENSSL_VERSION_NUMBER < 0x30000000L
cipher = get_des3_ctr_cipher();
# else
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"'%s' cipher unsupported", name);
errno = ENOENT;
return NULL;
# endif /* !OPENSSL_NO_DES and OpenSSL prior to 3.x */
} else if (strcmp(name, "aes256-ctr") == 0) {
# if defined(HAVE_EVP_AES_256_CTR_OPENSSL)
cipher = EVP_aes_256_ctr();
# else
cipher = get_aes_ctr_cipher(32);
# endif /* HAVE_EVP_AES_256_CTR_OPENSSL */
} else if (strcmp(name, "aes192-ctr") == 0) {
# if defined(HAVE_EVP_AES_192_CTR_OPENSSL)
cipher = EVP_aes_192_ctr();
# else
cipher = get_aes_ctr_cipher(24);
# endif /* HAVE_EVP_AES_192_CTR_OPENSSL */
} else if (strcmp(name, "aes128-ctr") == 0) {
# if defined(HAVE_EVP_AES_128_CTR_OPENSSL)
cipher = EVP_aes_128_ctr();
# else
cipher = get_aes_ctr_cipher(16);
# endif /* HAVE_EVP_AES_128_CTR_OPENSSL */
#endif /* OpenSSL older than 0.9.7 */
} else {
cipher = ciphers[i].get_type();
}
if (key_len != NULL) {
if (strcmp(name, "arcfour256") != 0) {
*key_len = 0;
} else {
/* The arcfour256 cipher is special-cased here in order to use
* a longer key (32 bytes), rather than the normal 16 bytes for the
* RC4 cipher.
*/
*key_len = 32;
}
}
if (auth_len != NULL) {
*auth_len = ciphers[i].auth_len;
}
if (discard_len != NULL) {
*discard_len = ciphers[i].discard_len;
}
return cipher;
}
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"no cipher matching '%s' found", name);
errno = ENOENT;
return NULL;
}
const EVP_MD *proxy_ssh_crypto_get_digest(const char *name, uint32_t *mac_len) {
register unsigned int i;
if (name == NULL) {
errno = EINVAL;
return NULL;
}
for (i = 0; digests[i].name; i++) {
if (strcmp(digests[i].name, name) == 0) {
const EVP_MD *digest = NULL;
#if OPENSSL_VERSION_NUMBER > 0x000907000L
if (strcmp(name, "umac-64@openssh.com") == 0 ||
strcmp(name, "umac-64-etm@openssh.com") == 0) {
digest = get_umac64_digest();
} else if (strcmp(name, "umac-128@openssh.com") == 0 ||
strcmp(name, "umac-128-etm@openssh.com") == 0) {
digest = get_umac128_digest();
#else
if (FALSE) {
#endif /* OpenSSL older than 0.9.7 */
} else {
digest = digests[i].get_type();
}
if (mac_len != NULL) {
*mac_len = digests[i].mac_len;
}
return digest;
}
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"no digest matching '%s' found", name);
return NULL;
}
const char *proxy_ssh_crypto_get_kexinit_cipher_list(pool *p) {
char *res = "";
config_rec *c;
/* Make sure that OpenSSL can use these ciphers. For example, in FIPS mode,
* some ciphers cannot be used. So we should not advertise ciphers that we
* know we cannot use.
*/
c = find_config(main_server->conf, CONF_PARAM, "ProxySFTPCiphers", FALSE);
if (c) {
register unsigned int i;
for (i = 0; i < c->argc; i++) {
register unsigned int j;
for (j = 0; ciphers[j].name; j++) {
if (strcmp(c->argv[i], ciphers[j].name) == 0) {
#ifdef OPENSSL_FIPS
if (FIPS_mode()) {
/* If FIPS mode is enabled, check whether the cipher is allowed
* for use.
*/
if (ciphers[j].fips_allowed == FALSE) {
pr_trace_msg(trace_channel, 5,
"cipher '%s' is disabled in FIPS mode, skipping",
ciphers[j].name);
continue;
}
}
#endif /* OPENSSL_FIPS */
if (strcmp(c->argv[i], "none") != 0) {
if (EVP_get_cipherbyname(ciphers[j].openssl_name) != NULL) {
res = pstrcat(p, res, *res ? "," : "",
pstrdup(p, ciphers[j].name), NULL);
} else {
/* The CTR modes are special cases. */
if (strcmp(ciphers[j].name, "blowfish-ctr") == 0 ||
strcmp(ciphers[j].name, "3des-ctr") == 0 ||
strcmp(ciphers[j].name, "aes256-ctr") == 0 ||
strcmp(ciphers[j].name, "aes192-ctr") == 0 ||
strcmp(ciphers[j].name, "aes128-ctr") == 0
#if defined(HAVE_EVP_AES_256_GCM_OPENSSL)
|| strcmp(ciphers[j].name, "aes128-gcm@openssh.com") == 0 ||
strcmp(ciphers[j].name, "aes256-gcm@openssh.com") == 0
#endif
) {
res = pstrcat(p, res, *res ? "," : "",
pstrdup(p, ciphers[j].name), NULL);
} else {
pr_trace_msg(trace_channel, 3,
"unable to use '%s' cipher: Unsupported by OpenSSL",
ciphers[j].name);
}
}
} else {
res = pstrcat(p, res, *res ? "," : "",
pstrdup(p, ciphers[j].name), NULL);
}
}
}
}
} else {
register unsigned int i;
for (i = 0; ciphers[i].name; i++) {
if (ciphers[i].enabled) {
#ifdef OPENSSL_FIPS
if (FIPS_mode()) {
/* If FIPS mode is enabled, check whether the cipher is allowed
* for use.
*/
if (ciphers[i].fips_allowed == FALSE) {
pr_trace_msg(trace_channel, 5,
"cipher '%s' is disabled in FIPS mode, skipping",
ciphers[i].name);
continue;
}
}
#endif /* OPENSSL_FIPS */
if (strcmp(ciphers[i].name, "none") != 0) {
if (EVP_get_cipherbyname(ciphers[i].openssl_name) != NULL) {
res = pstrcat(p, res, *res ? "," : "",
pstrdup(p, ciphers[i].name), NULL);
} else {
/* The CTR modes are special cases. */
if (strcmp(ciphers[i].name, "blowfish-ctr") == 0 ||
strcmp(ciphers[i].name, "3des-ctr") == 0 ||
strcmp(ciphers[i].name, "aes256-ctr") == 0 ||
strcmp(ciphers[i].name, "aes192-ctr") == 0 ||
strcmp(ciphers[i].name, "aes128-ctr") == 0
#if defined(HAVE_EVP_AES_256_GCM_OPENSSL)
|| strcmp(ciphers[i].name, "aes128-gcm@openssh.com") == 0 ||
strcmp(ciphers[i].name, "aes256-gcm@openssh.com") == 0
#endif
) {
res = pstrcat(p, res, *res ? "," : "",
pstrdup(p, ciphers[i].name), NULL);
} else {
pr_trace_msg(trace_channel, 3,
"unable to use '%s' cipher: Unsupported by OpenSSL",
ciphers[i].name);
}
}
} else {
res = pstrcat(p, res, *res ? "," : "",
pstrdup(p, ciphers[i].name), NULL);
}
} else {
pr_trace_msg(trace_channel, 3, "unable to use '%s' cipher: "
"Must be explicitly requested via ProxySFTPCiphers", ciphers[i].name);
}
}
}
return res;
}
const char *proxy_ssh_crypto_get_kexinit_digest_list(pool *p) {
char *res = "";
config_rec *c;
/* Make sure that OpenSSL can use these digests. For example, in FIPS
* mode, some digests cannot be used. So we should not advertise digests
* that we know we cannot use.
*/
c = find_config(main_server->conf, CONF_PARAM, "ProxySFTPDigests", FALSE);
if (c != NULL) {
register unsigned int i;
for (i = 0; i < c->argc; i++) {
register unsigned int j;
for (j = 0; digests[j].name; j++) {
if (strcmp(c->argv[i], digests[j].name) == 0) {
#ifdef OPENSSL_FIPS
if (FIPS_mode()) {
/* If FIPS mode is enabled, check whether the MAC is allowed
* for use.
*/
if (digests[j].fips_allowed == FALSE) {
pr_trace_msg(trace_channel, 5,
"digest '%s' is disabled in FIPS mode, skipping",
digests[j].name);
continue;
}
}
#endif /* OPENSSL_FIPS */
if (strcmp(c->argv[i], "none") != 0) {
if (digests[j].openssl_name != NULL &&
EVP_get_digestbyname(digests[j].openssl_name) != NULL) {
res = pstrcat(p, res, *res ? "," : "",
pstrdup(p, digests[j].name), NULL);
} else {
/* The umac-64/umac-128 digests are special cases. */
if (strcmp(digests[j].name, "umac-64@openssh.com") == 0 ||
strcmp(digests[j].name, "umac-64-etm@openssh.com") == 0 ||
strcmp(digests[j].name, "umac-128@openssh.com") == 0 ||
strcmp(digests[j].name, "umac-128-etm@openssh.com") == 0) {
res = pstrcat(p, res, *res ? "," : "",
pstrdup(p, digests[j].name), NULL);
} else {
pr_trace_msg(trace_channel, 3,
"unable to use '%s' digest: Unsupported by OpenSSL",
digests[j].name);
}
}
} else {
res = pstrcat(p, res, *res ? "," : "",
pstrdup(p, digests[j].name), NULL);
}
}
}
}
} else {
register unsigned int i;
for (i = 0; digests[i].name; i++) {
if (digests[i].enabled) {
#ifdef OPENSSL_FIPS
if (FIPS_mode()) {
/* If FIPS mode is enabled, check whether the digest is allowed
* for use.
*/
if (digests[i].fips_allowed == FALSE) {
pr_trace_msg(trace_channel, 5,
"digest '%s' is disabled in FIPS mode, skipping",
digests[i].name);
continue;
}
}
#endif /* OPENSSL_FIPS */
if (strcmp(digests[i].name, "none") != 0) {
if (digests[i].openssl_name != NULL &&
EVP_get_digestbyname(digests[i].openssl_name) != NULL) {
res = pstrcat(p, res, *res ? "," : "",
pstrdup(p, digests[i].name), NULL);
} else {
/* The umac-64/umac-128 digests are special cases. */
if (strcmp(digests[i].name, "umac-64@openssh.com") == 0 ||
strcmp(digests[i].name, "umac-64-etm@openssh.com") == 0 ||
strcmp(digests[i].name, "umac-128@openssh.com") == 0 ||
strcmp(digests[i].name, "umac-128-etm@openssh.com") == 0) {
res = pstrcat(p, res, *res ? "," : "",
pstrdup(p, digests[i].name), NULL);
} else {
pr_trace_msg(trace_channel, 3,
"unable to use '%s' digest: Unsupported by OpenSSL",
digests[i].name);
}
}
} else {
res = pstrcat(p, res, *res ? "," : "",
pstrdup(p, digests[i].name), NULL);
}
} else {
pr_trace_msg(trace_channel, 3, "unable to use '%s' digest: "
"Must be explicitly requested via ProxySFTPDigests", digests[i].name);
}
}
}
return res;
}
const char *proxy_ssh_crypto_get_errors(void) {
unsigned int count = 0;
unsigned long error_code;
BIO *bio = NULL;
char *data = NULL;
long datalen;
const char *error_data = NULL, *str = "(unknown)";
int error_flags = 0;
/* Use ERR_print_errors() and a memory BIO to build up a string with
* all of the error messages from the error queue.
*/
error_code = ERR_get_error_line_data(NULL, NULL, &error_data, &error_flags);
if (error_code) {
bio = BIO_new(BIO_s_mem());
}
while (error_code) {
if (error_flags & ERR_TXT_STRING) {
BIO_printf(bio, "\n (%u) %s [%s]", ++count,
ERR_error_string(error_code, NULL), error_data);
} else {
BIO_printf(bio, "\n (%u) %s", ++count,
ERR_error_string(error_code, NULL));
}
error_data = NULL;
error_flags = 0;
error_code = ERR_get_error_line_data(NULL, NULL, &error_data, &error_flags);
}
datalen = BIO_get_mem_data(bio, &data);
if (data != NULL) {
data[datalen] = '\0';
str = pstrdup(proxy_pool, data);
}
if (bio != NULL) {
BIO_free(bio);
}
return str;
}
/* Try to find the best multiple/block size which accommodates the two given
* sizes by rounding up.
*/
size_t proxy_ssh_crypto_get_size(size_t first, size_t second) {
#ifdef roundup
return roundup(first, second);
#else
return (((first + (second - 1)) / second) * second);
#endif /* !roundup */
}
void proxy_ssh_crypto_free(int flags) {
/* XXX TODO! */
/* Only call EVP_cleanup() et al if other OpenSSL-using modules are not
* present. If we called EVP_cleanup() here during a restart,
* and other modules want to use OpenSSL, we may be depriving those modules
* of OpenSSL functionality.
*
* At the moment, the modules known to use OpenSSL are mod_ldap, mod_radius,
* mod_sftp, mod_sql, and mod_sql_passwd, and mod_tls.
*/
if (pr_module_get("mod_auth_otp.c") == NULL &&
pr_module_get("mod_digest.c") == NULL &&
pr_module_get("mod_ldap.c") == NULL &&
pr_module_get("mod_radius.c") == NULL &&
pr_module_get("mod_sftp.c") == NULL &&
pr_module_get("mod_sql.c") == NULL &&
pr_module_get("mod_sql_passwd.c") == NULL &&
pr_module_get("mod_tls.c") == NULL) {
#if OPENSSL_VERSION_NUMBER > 0x000907000L && \
OPENSSL_VERSION_NUMBER < 0x10100000L && \
defined(PR_USE_OPENSSL_ENGINE)
if (crypto_engine != NULL) {
ENGINE_cleanup();
crypto_engine = NULL;
}
#endif
#if OPENSSL_VERSION_NUMBER >= 0x10000001L
/* The ERR_remove_state(0) usage is deprecated due to thread ID
* differences among platforms; see the OpenSSL-1.0.0 CHANGES file
* for details. So for new enough OpenSSL installations, use the
* proper way to clear the error queue state.
*/
# if OPENSSL_VERSION_NUMBER < 0x10100000L
ERR_remove_thread_state(NULL);
# endif /* prior to OpenSSL-1.1.x */
#else
ERR_remove_state(0);
#endif /* OpenSSL prior to 1.0.0-beta1 */
#if OPENSSL_VERSION_NUMBER < 0x10100000L
ERR_free_strings();
EVP_cleanup();
RAND_cleanup();
#endif /* prior to OpenSSL-1.1.x */
}
}
#endif /* PR_USE_OPENSSL */
proftpd-mod_proxy-0.9.5/lib/proxy/ssh/db.c 0000664 0000000 0000000 00000030257 14757370167 0020526 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH database implementation
* Copyright (c) 2021 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "proxy/db.h"
#include "proxy/ssh.h"
#include "proxy/ssh/db.h"
#if defined(PR_USE_OPENSSL)
extern xaset_t *server_list;
static const char *trace_channel = "proxy.ssh.db";
#define PROXY_SSH_DB_SCHEMA_NAME "proxy_ssh"
#define PROXY_SSH_DB_SCHEMA_VERSION 1
static unsigned long db_opts = 0UL;
static int ssh_db_add_hostkey(pool *p, void *dsh, unsigned int vhost_id,
const char *backend_uri, const char *algo,
const unsigned char *hostkey_data, uint32_t hostkey_datalen) {
int res, xerrno = 0;
struct proxy_dbh *dbh;
const char *stmt, *errstr = NULL;
array_header *results;
dbh = dsh;
stmt = "INSERT INTO proxy_ssh_hostkeys (vhost_id, backend_uri, algo, hostkey) VALUES (?, ?, ?, ?);";
res = proxy_db_prepare_stmt(p, dbh, stmt);
if (res < 0) {
xerrno = errno;
(void) pr_log_debug(DEBUG3, MOD_PROXY_VERSION
": error preparing statement '%s': %s", stmt, strerror(xerrno));
errno = xerrno;
return -1;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT,
(void *) &vhost_id, 0);
if (res < 0) {
return -1;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT,
(void *) backend_uri, -1);
if (res < 0) {
return -1;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 3, PROXY_DB_BIND_TYPE_TEXT,
(void *) algo, -1);
if (res < 0) {
return -1;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 4, PROXY_DB_BIND_TYPE_BLOB,
(void *) hostkey_data, (int) hostkey_datalen);
if (res < 0) {
return -1;
}
results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr);
if (results == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr ? errstr : strerror(errno));
errno = EPERM;
return -1;
}
return 0;
}
static const unsigned char *ssh_db_get_hostkey(pool *p, void *dsh,
unsigned int vhost_id, const char *backend_uri, const char **algo,
uint32_t *hostkey_datalen) {
int res, xerrno;
struct proxy_dbh *dbh;
const char *stmt, *errstr = NULL;
array_header *results;
const unsigned char *hostkey_data = NULL;
dbh = dsh;
stmt = "SELECT algo, hostkey FROM proxy_ssh_hostkeys WHERE vhost_id = ? AND backend_uri = ?;";
res = proxy_db_prepare_stmt(p, dbh, stmt);
if (res < 0) {
xerrno = errno;
(void) pr_log_debug(DEBUG3, MOD_PROXY_VERSION
": error preparing statement '%s': %s", stmt, strerror(xerrno));
errno = xerrno;
return NULL;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT,
(void *) &vhost_id, 0);
if (res < 0) {
return NULL;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT,
(void *) backend_uri, -1);
if (res < 0) {
return NULL;
}
results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr);
if (results == NULL ||
results->nelts == 0) {
errno = ENOENT;
return NULL;
}
/* We expect 3 items: one for the algo, one for the hostkey BLOB, and one for
* BLOB length.
*/
if (results->nelts != 3) {
pr_log_debug(DEBUG3, MOD_PROXY_VERSION
": expected 3 results from statement '%s', got %d", stmt,
results->nelts);
errno = EINVAL;
return NULL;
}
*algo = ((char **) results->elts)[0];
hostkey_data = (const unsigned char *) ((char **) results->elts)[1];
*hostkey_datalen = atoi(((char **) results->elts)[2]);
pr_trace_msg(trace_channel, 19,
"retrieved hostkey (algo '%s', %lu bytes) for vhost ID %u, URI '%s'",
*algo, (unsigned long) *hostkey_datalen, vhost_id, backend_uri);
return hostkey_data;
}
static int ssh_db_update_hostkey(pool *p, void *dsh, unsigned int vhost_id,
const char *backend_uri, const char *algo,
const unsigned char *hostkey_data, uint32_t hostkey_datalen) {
int res, xerrno = 0;
struct proxy_dbh *dbh;
const char *stmt, *errstr = NULL;
array_header *results;
dbh = dsh;
stmt = "UPDATE proxy_ssh_hostkeys SET algo = ?, hostkey = ? WHERE vhost_id = ? AND backend_uri = ?;";
res = proxy_db_prepare_stmt(p, dbh, stmt);
if (res < 0) {
xerrno = errno;
(void) pr_log_debug(DEBUG3, MOD_PROXY_VERSION
": error preparing statement '%s': %s", stmt, strerror(xerrno));
errno = xerrno;
return -1;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_TEXT,
(void *) algo, -1);
if (res < 0) {
return -1;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_BLOB,
(void *) hostkey_data, (int) hostkey_datalen);
if (res < 0) {
return -1;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 3, PROXY_DB_BIND_TYPE_INT,
(void *) &vhost_id, 0);
if (res < 0) {
return -1;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 4, PROXY_DB_BIND_TYPE_TEXT,
(void *) backend_uri, -1);
if (res < 0) {
return -1;
}
results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr);
if (results == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr ? errstr : strerror(errno));
errno = EPERM;
return -1;
}
return 0;
}
/* Initialization routines */
static int ssh_db_add_schema(pool *p, void *dbh, const char *db_path) {
int res;
const char *stmt, *errstr = NULL;
/* CREATE TABLE proxy_ssh_vhosts (
* vhost_id INTEGER NOT NULL PRIMARY KEY,
* vhost_name TEXT NOT NULL
* );
*/
stmt = "CREATE TABLE IF NOT EXISTS proxy_ssh_vhosts (vhost_id INTEGER NOT NULL PRIMARY KEY, vhost_name TEXT NOT NULL);";
res = proxy_db_exec_stmt(p, dbh, stmt, &errstr);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr);
errno = EPERM;
return -1;
}
/* CREATE TABLE proxy_ssh_hostkeys (
* backend_uri STRING NOT NULL PRIMARY KEY,
* vhost_id INTEGER NOT NULL,
* algo TEXT NOT NULL,
* hostkey BLOB NOT NULL,
* FOREIGN KEY (vhost_id) REFERENCES proxy_ssh_vhosts (vhost_id)
* );
*/
stmt = "CREATE TABLE IF NOT EXISTS proxy_ssh_hostkeys (backend_uri STRING NOT NULL PRIMARY KEY, vhost_id INTEGER NOT NULL, algo TEXT NOT NULL, hostkey BLOB NOT NULL, FOREIGN KEY (vhost_id) REFERENCES proxy_ssh_hosts (vhost_id));";
res = proxy_db_exec_stmt(p, dbh, stmt, &errstr);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr);
errno = EPERM;
return -1;
}
/* Note that we deliberately do NOT truncate the hostkeys table. */
return 0;
}
static int ssh_truncate_db_tables(pool *p, void *dbh) {
int res;
const char *stmt, *errstr = NULL;
stmt = "DELETE FROM proxy_ssh_vhosts;";
res = proxy_db_exec_stmt(p, dbh, stmt, &errstr);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr);
errno = EPERM;
return -1;
}
/* Note that we deliberately do NOT truncate the hostkeys table. */
return 0;
}
static int ssh_db_add_vhost(pool *p, void *dbh, server_rec *s) {
int res, xerrno = 0;
const char *stmt, *errstr = NULL;
array_header *results;
stmt = "INSERT INTO proxy_ssh_vhosts (vhost_id, vhost_name) VALUES (?, ?);";
res = proxy_db_prepare_stmt(p, dbh, stmt);
if (res < 0) {
xerrno = errno;
(void) pr_log_debug(DEBUG3, MOD_PROXY_VERSION
": error preparing statement '%s': %s", stmt, strerror(xerrno));
errno = xerrno;
return -1;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT,
(void *) &(s->sid), 0);
if (res < 0) {
return -1;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT,
(void *) s->ServerName, -1);
if (res < 0) {
return -1;
}
results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr);
if (results == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr ? errstr : strerror(errno));
errno = EPERM;
return -1;
}
return 0;
}
static int ssh_db_init(pool *p, const char *tables_path, int flags) {
int db_flags, res, xerrno = 0;
server_rec *s;
struct proxy_dbh *dbh = NULL;
const char *db_path = NULL;
if (tables_path == NULL) {
errno = EINVAL;
return -1;
}
db_path = pdircat(p, tables_path, "proxy-ssh.db", NULL);
db_flags = PROXY_DB_OPEN_FL_SCHEMA_VERSION_CHECK|PROXY_DB_OPEN_FL_INTEGRITY_CHECK|PROXY_DB_OPEN_FL_VACUUM;
if (flags & PROXY_DB_OPEN_FL_SKIP_VACUUM) {
/* If the caller needs us to skip the vacuum, we will. */
db_flags &= ~PROXY_DB_OPEN_FL_VACUUM;
}
PRIVS_ROOT
dbh = proxy_db_open_with_version(p, db_path, PROXY_SSH_DB_SCHEMA_NAME,
PROXY_SSH_DB_SCHEMA_VERSION, db_flags);
xerrno = errno;
PRIVS_RELINQUISH
if (dbh == NULL) {
(void) pr_log_pri(PR_LOG_NOTICE, MOD_PROXY_VERSION
": error opening database '%s' for schema '%s', version %u: %s",
db_path, PROXY_SSH_DB_SCHEMA_NAME, PROXY_SSH_DB_SCHEMA_VERSION,
strerror(xerrno));
errno = xerrno;
return -1;
}
res = ssh_db_add_schema(p, dbh, db_path);
if (res < 0) {
xerrno = errno;
(void) pr_log_debug(DEBUG0, MOD_PROXY_VERSION
": error creating schema in database '%s' for '%s': %s", db_path,
PROXY_SSH_DB_SCHEMA_NAME, strerror(xerrno));
(void) proxy_db_close(p, dbh);
errno = xerrno;
return -1;
}
res = ssh_truncate_db_tables(p, dbh);
if (res < 0) {
xerrno = errno;
(void) proxy_db_close(p, dbh);
errno = xerrno;
return -1;
}
for (s = (server_rec *) server_list->xas_list; s; s = s->next) {
res = ssh_db_add_vhost(p, dbh, s);
if (res < 0) {
xerrno = errno;
(void) pr_log_debug(DEBUG0, MOD_PROXY_VERSION
": error adding database entry for server '%s' in '%s': %s",
s->ServerName, PROXY_SSH_DB_SCHEMA_NAME, strerror(xerrno));
(void) proxy_db_close(p, dbh);
errno = xerrno;
return -1;
}
}
(void) proxy_db_close(p, dbh);
return 0;
}
static int ssh_db_close(pool *p, void *dbh) {
if (dbh != NULL) {
if (proxy_db_close(p, dbh) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error closing %s database: %s", PROXY_SSH_DB_SCHEMA_NAME,
strerror(errno));
}
}
return 0;
}
static void *ssh_db_open(pool *p, const char *tables_dir, unsigned long opts) {
int xerrno = 0;
struct proxy_dbh *dbh;
const char *db_path;
db_path = pdircat(p, tables_dir, "proxy-ssh.db", NULL);
PRIVS_ROOT
dbh = proxy_db_open_with_version(p, db_path, PROXY_SSH_DB_SCHEMA_NAME,
PROXY_SSH_DB_SCHEMA_VERSION, 0);
xerrno = errno;
PRIVS_RELINQUISH
if (dbh == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error opening database '%s' for schema '%s', version %u: %s",
db_path, PROXY_SSH_DB_SCHEMA_NAME, PROXY_SSH_DB_SCHEMA_VERSION,
strerror(xerrno));
errno = xerrno;
return NULL;
}
db_opts = opts;
return dbh;
}
int proxy_ssh_db_as_datastore(struct proxy_ssh_datastore *ds, void *ds_data,
size_t ds_datasz) {
if (ds == NULL) {
errno = EINVAL;
return -1;
}
(void) ds_data;
(void) ds_datasz;
ds->hostkey_add = ssh_db_add_hostkey;
ds->hostkey_get = ssh_db_get_hostkey;
ds->hostkey_update = ssh_db_update_hostkey;
ds->init = ssh_db_init;
ds->open = ssh_db_open;
ds->close = ssh_db_close;
return 0;
}
#endif /* PR_USE_OPENSSL */
proftpd-mod_proxy-0.9.5/lib/proxy/ssh/disconnect.c 0000664 0000000 0000000 00000012062 14757370167 0022264 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH disconnects
* Copyright (c) 2021 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "proxy/ssh/ssh2.h"
#include "proxy/ssh/msg.h"
#include "proxy/ssh/packet.h"
#include "proxy/ssh/disconnect.h"
#if defined(PR_USE_OPENSSL)
struct disconnect_reason {
uint32_t code;
const char *explain;
const char *lang;
};
static struct disconnect_reason explanations[] = {
{ PROXY_SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT, "Host not allowed to connect", NULL },
{ PROXY_SSH_DISCONNECT_PROTOCOL_ERROR, "Protocol error", NULL },
{ PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, "Key exchange failed", NULL },
{ PROXY_SSH_DISCONNECT_MAC_ERROR, "MAC error", NULL },
{ PROXY_SSH_DISCONNECT_COMPRESSION_ERROR, "Compression error", NULL },
{ PROXY_SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, "Requested service not available", NULL },
{ PROXY_SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED, "Protocol version not supported", NULL },
{ PROXY_SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE, "Host key not verifiable", NULL },
{ PROXY_SSH_DISCONNECT_CONNECTION_LOST, "Connection lost", NULL },
{ PROXY_SSH_DISCONNECT_BY_APPLICATION, "Application disconnected", NULL },
{ PROXY_SSH_DISCONNECT_TOO_MANY_CONNECTIONS, "Too many connections", NULL },
{ PROXY_SSH_DISCONNECT_AUTH_CANCELLED_BY_USER, "Authentication cancelled by user", NULL },
{ PROXY_SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, "No other authentication mechanisms available", NULL },
{ PROXY_SSH_DISCONNECT_ILLEGAL_USER_NAME, "Illegal user name", NULL },
{ 0, NULL, NULL }
};
static const char *trace_channel = "proxy.ssh.disconnect";
const char *proxy_ssh_disconnect_get_text(uint32_t reason_code) {
register unsigned int i;
for (i = 0; explanations[i].explain; i++) {
if (explanations[i].code == reason_code) {
return explanations[i].explain;
}
}
errno = ENOENT;
return NULL;
}
void proxy_ssh_disconnect_send(pool *p, conn_t *conn, uint32_t reason,
const char *explain, const char *file, int lineno, const char *func) {
struct proxy_ssh_packet *pkt;
const char *lang = "en-US";
unsigned char *buf, *ptr;
uint32_t buflen, bufsz, len = 0;
/* Send the server a DISCONNECT mesg. */
pkt = proxy_ssh_packet_create(p);
buflen = bufsz = 1024;
ptr = buf = palloc(pkt->pool, bufsz);
if (explain == NULL) {
register unsigned int i;
for (i = 0; explanations[i].explain; i++) {
if (explanations[i].code == reason) {
explain = explanations[i].explain;
lang = explanations[i].lang;
if (lang == NULL) {
lang = "en-US";
}
break;
}
}
if (explain == NULL) {
explain = "Unknown reason";
}
} else {
lang = "en-US";
}
if (strlen(func) > 0) {
pr_trace_msg(trace_channel, 9, "disconnecting (%s) [at %s:%d:%s()]",
explain, file, lineno, func);
} else {
pr_trace_msg(trace_channel, 9, "disconnecting (%s) [at %s:%d]", explain,
file, lineno);
}
len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_DISCONNECT);
len += proxy_ssh_msg_write_int(&buf, &buflen, reason);
len += proxy_ssh_msg_write_string(&buf, &buflen, explain);
len += proxy_ssh_msg_write_string(&buf, &buflen, lang);
pkt->payload = ptr;
pkt->payload_len = len;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"disconnecting %s (%s)", pr_netaddr_get_ipstr(conn->remote_addr), explain);
/* Explicitly set a short poll timeout of 2 secs. */
proxy_ssh_packet_set_poll_timeout(2, 0);
if (proxy_ssh_packet_write(conn, pkt) < 0) {
int xerrno = errno;
pr_trace_msg(trace_channel, 12,
"error writing DISCONNECT message: %s", strerror(xerrno));
}
destroy_pool(pkt->pool);
}
void proxy_ssh_disconnect_conn(conn_t *conn, uint32_t reason,
const char *explain, const char *file, int lineno, const char *func) {
proxy_ssh_disconnect_send(proxy_pool, conn, reason, explain, file, lineno,
func);
#if defined(PR_DEVEL_COREDUMP)
pr_session_end(PR_SESS_END_FL_NOEXIT);
abort();
#else
pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION, NULL);
#endif /* PR_DEVEL_COREDUMP */
}
#endif /* PR_USE_OPENSSL */
proftpd-mod_proxy-0.9.5/lib/proxy/ssh/interop.c 0000664 0000000 0000000 00000023277 14757370167 0021625 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH interoperability
* Copyright (c) 2021-2022 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "proxy/ssh/ssh2.h"
#include "proxy/ssh/disconnect.h"
#include "proxy/ssh/interop.h"
#if defined(PR_USE_OPENSSL)
/* By default, each server is assumed to support all of the features in
* which we are interested.
*/
static unsigned int default_flags =
PROXY_SSH_FEAT_IGNORE_MSG |
PROXY_SSH_FEAT_MAC_LEN |
PROXY_SSH_FEAT_CIPHER_USE_K |
PROXY_SSH_FEAT_REKEYING |
PROXY_SSH_FEAT_USERAUTH_BANNER |
PROXY_SSH_FEAT_HAVE_PUBKEY_ALGO |
PROXY_SSH_FEAT_SERVICE_IN_HOST_SIG |
PROXY_SSH_FEAT_SERVICE_IN_PUBKEY_SIG |
PROXY_SSH_FEAT_HAVE_PUBKEY_ALGO_IN_DSA_SIG |
PROXY_SSH_FEAT_NO_DATA_WHILE_REKEYING |
PROXY_SSH_FEAT_DH_NEW_GEX;
struct proxy_ssh_version_pattern {
const char *pattern;
int disabled_flags;
pr_regex_t *pre;
};
static struct proxy_ssh_version_pattern known_versions[] = {
{ "^OpenSSH-2\\.0.*|"
"^OpenSSH-2\\.1.*|"
"^OpenSSH_2\\.1.*|"
"^OpenSSH_2\\.2.*|"
"^OpenSSH_2\\.3\\.0.*", PROXY_SSH_FEAT_USERAUTH_BANNER|
PROXY_SSH_FEAT_REKEYING|
PROXY_SSH_FEAT_DH_NEW_GEX, NULL },
{ "^OpenSSH_2\\.3\\..*|"
"^OpenSSH_2\\.5\\.0p1.*|"
"^OpenSSH_2\\.5\\.1p1.*|"
"^OpenSSH_2\\.5\\.0.*|"
"^OpenSSH_2\\.5\\.1.*|"
"^OpenSSH_2\\.5\\.2.*|"
"^OpenSSH_2\\.5\\.3.*", PROXY_SSH_FEAT_REKEYING|
PROXY_SSH_FEAT_DH_NEW_GEX, NULL },
{ "^OpenSSH.*", 0, NULL },
{ ".*J2SSH_Maverick.*", PROXY_SSH_FEAT_REKEYING, NULL },
{ ".*MindTerm.*", 0, NULL },
{ "^Sun_SSH_1\\.0.*", PROXY_SSH_FEAT_REKEYING, NULL },
{ "^2\\.1\\.0.*|"
"^2\\.1 .*", PROXY_SSH_FEAT_HAVE_PUBKEY_ALGO_IN_DSA_SIG|
PROXY_SSH_FEAT_SERVICE_IN_HOST_SIG|
PROXY_SSH_FEAT_MAC_LEN, NULL },
{ "^2\\.0\\.13.*|"
"^2\\.0\\.14.*|"
"^2\\.0\\.15.*|"
"^2\\.0\\.16.*|"
"^2\\.0\\.17.*|"
"^2\\.0\\.18.*|"
"^2\\.0\\.19.*", PROXY_SSH_FEAT_HAVE_PUBKEY_ALGO_IN_DSA_SIG|
PROXY_SSH_FEAT_SERVICE_IN_HOST_SIG|
PROXY_SSH_FEAT_SERVICE_IN_PUBKEY_SIG|
PROXY_SSH_FEAT_MAC_LEN, NULL },
{ "^2\\.0\\.11.*|"
"^2\\.0\\.12.*", PROXY_SSH_FEAT_HAVE_PUBKEY_ALGO_IN_DSA_SIG|
PROXY_SSH_FEAT_SERVICE_IN_PUBKEY_SIG|
PROXY_SSH_FEAT_HAVE_PUBKEY_ALGO|
PROXY_SSH_FEAT_MAC_LEN, NULL },
{ "^2\\.0\\..*", PROXY_SSH_FEAT_HAVE_PUBKEY_ALGO_IN_DSA_SIG|
PROXY_SSH_FEAT_SERVICE_IN_PUBKEY_SIG|
PROXY_SSH_FEAT_HAVE_PUBKEY_ALGO|
PROXY_SSH_FEAT_CIPHER_USE_K|
PROXY_SSH_FEAT_MAC_LEN, NULL },
{ "^2\\.2\\.0.*|"
"^2\\.3\\.0.*", PROXY_SSH_FEAT_MAC_LEN, NULL },
{ "^1\\.2\\.18.*|"
"^1\\.2\\.19.*|"
"^1\\.2\\.20.*|"
"^1\\.2\\.21.*|"
"^1\\.2\\.22.*|"
"^1\\.3\\.2.*|"
"^3\\.2\\.9.*", PROXY_SSH_FEAT_IGNORE_MSG, NULL },
{ ".*PuTTY.*|"
".*PUTTY.*|"
".*WinSCP.*", PROXY_SSH_FEAT_NO_DATA_WHILE_REKEYING, NULL },
{ NULL, 0, NULL },
};
static const char *trace_channel = "proxy.ssh.interop";
int proxy_ssh_interop_handle_version(pool *p,
const struct proxy_session *proxy_sess, const char *server_version) {
register unsigned int i;
size_t version_len;
const char *version = NULL;
char *ptr = NULL;
config_rec *c;
if (server_version == NULL) {
errno = EINVAL;
return -1;
}
version_len = strlen(server_version);
/* The version string MUST conform to the following, as per Section 4.2
* of RFC4253:
*
* SSH-protoversion-softwareversion [SP comments]
*
* The 'comments' field is optional. The 'protoversion' MUST be "2.0".
* The 'softwareversion' field MUST be printable ASCII characters and
* cannot contain SP or the '-' character.
*/
for (i = 0; i < version_len; i++) {
if (!PR_ISPRINT(server_version[i]) &&
server_version[i] != '-' &&
server_version[i] != ' ') {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"server-sent version contains non-printable or illegal characters, "
"disconnecting");
PROXY_SSH_DISCONNECT_CONN(proxy_sess->backend_ctrl_conn,
PROXY_SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED, NULL);
}
}
/* Skip past the leading "SSH-2.0-" (or "SSH-1.99-") to get the actual
* server info.
*/
if (strncmp(server_version, "SSH-2.0-", 8) == 0) {
version = pstrdup(p, server_version + 8);
} else if (strncmp(server_version, "SSH-1.99-", 9) == 0) {
version = pstrdup(p, server_version + 9);
} else {
/* An illegally formatted server version. How did it get here? */
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"server-sent version (%s) is illegally formmated, disconnecting",
server_version);
PROXY_SSH_DISCONNECT_CONN(proxy_sess->backend_ctrl_conn,
PROXY_SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED, NULL);
}
/* Look for the optional comments field in the received server version; if
* present, trim it out, so that we do not try to match on it.
*/
ptr = strchr(version, ' ');
if (ptr != NULL) {
pr_trace_msg(trace_channel, 11, "read server version with comments: '%s'",
version);
*ptr = '\0';
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"handling connection to SSH2 server '%s'", version);
pr_trace_msg(trace_channel, 5, "handling connection to SSH2 server '%s'",
version);
/* First matching pattern wins. */
for (i = 0; known_versions[i].pattern != NULL; i++) {
int res;
pr_signals_handle();
pr_trace_msg(trace_channel, 18,
"checking server version '%s' against regex '%s'", version,
known_versions[i].pattern);
res = pr_regexp_exec(known_versions[i].pre, version, 0, NULL, 0, 0, 0);
if (res == 0) {
pr_trace_msg(trace_channel, 18,
"server version '%s' matched against regex '%s'", version,
known_versions[i].pattern);
/* We have a match. */
default_flags &= ~(known_versions[i].disabled_flags);
break;
} else {
pr_trace_msg(trace_channel, 18,
"server version '%s' did not match regex '%s'", version,
known_versions[i].pattern);
}
}
/* Now iterate through any ProxySFTPServerMatch rules. */
c = find_config(main_server->conf, CONF_PARAM, "ProxySFTPServerMatch", FALSE);
while (c != NULL) {
int res;
char *pattern;
pr_regex_t *pre;
pr_signals_handle();
pattern = c->argv[0];
pre = c->argv[1];
pr_trace_msg(trace_channel, 18,
"checking server version '%s' against ProxySFTPServerMatch regex '%s'",
version, pattern);
res = pr_regexp_exec(pre, version, 0, NULL, 0, 0, 0);
if (res == 0) {
pr_table_t *tab;
const void *v;
/* We have a match. */
tab = c->argv[2];
/* Look for the following keys:
* pessimisticNewkeys
*/
v = pr_table_get(tab, "pessimisticNewkeys", NULL);
if (v != NULL) {
int pessimistic_newkeys;
pessimistic_newkeys = *((int *) v);
pr_trace_msg(trace_channel, 16,
"setting pessimistic NEWKEYS behavior to %s, as per "
"ProxySFTPServerMatch", pessimistic_newkeys ? "true" : "false");
if (pessimistic_newkeys == TRUE) {
default_flags |= PROXY_SSH_FEAT_PESSIMISTIC_NEWKEYS;
}
}
/* Once we're done, we can destroy the table. */
(void) pr_table_empty(tab);
(void) pr_table_free(tab);
c->argv[2] = NULL;
} else {
pr_trace_msg(trace_channel, 18,
"server version '%s' did not match ProxySFTPServerMatch regex '%s'",
version, pattern);
}
c = find_config_next(c, c->next, CONF_PARAM, "ProxySFTPServerMatch", FALSE);
}
return 0;
}
int proxy_ssh_interop_supports_feature(int feat_flag) {
if (!(default_flags & feat_flag)) {
return FALSE;
}
return TRUE;
}
int proxy_ssh_interop_init(void) {
register unsigned int i;
/* Compile the regexps for all of the known server versions, to save the
* time when connecting to a server.
*/
for (i = 0; known_versions[i].pattern != NULL; i++) {
pr_regex_t *pre;
int res;
pr_signals_handle();
pre = pr_regexp_alloc(&proxy_module);
res = pr_regexp_compile(pre, known_versions[i].pattern,
REG_EXTENDED|REG_NOSUB);
if (res != 0) {
char errmsg[256];
memset(errmsg, '\0', sizeof(errmsg));
pr_regexp_error(res, pre, errmsg, sizeof(errmsg));
pr_regexp_free(NULL, pre);
pr_log_debug(DEBUG0, MOD_PROXY_VERSION
": error compiling regex pattern '%s' (known_versions[%u]): %s",
known_versions[i].pattern, i, errmsg);
continue;
}
known_versions[i].pre = pre;
}
return 0;
}
int proxy_ssh_interop_free(void) {
register unsigned int i;
for (i = 0; known_versions[i].pattern != NULL; i++) {
if (known_versions[i].pre != NULL) {
pr_regexp_free(NULL, known_versions[i].pre);
known_versions[i].pre = NULL;
}
}
return 0;
}
#endif /* PR_USE_OPENSSL */
proftpd-mod_proxy-0.9.5/lib/proxy/ssh/kex.c 0000664 0000000 0000000 00000467251 14757370167 0020740 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH key exchange (kex)
* Copyright (c) 2021-2023 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "proxy/conn.h"
#include "proxy/ssh.h"
#include "proxy/ssh/ssh2.h"
#include "proxy/ssh/msg.h"
#include "proxy/ssh/packet.h"
#include "proxy/ssh/session.h"
#include "proxy/ssh/cipher.h"
#include "proxy/ssh/mac.h"
#include "proxy/ssh/compress.h"
#include "proxy/ssh/kex.h"
#include "proxy/ssh/keys.h"
#include "proxy/ssh/crypto.h"
#include "proxy/ssh/disconnect.h"
#include "proxy/ssh/interop.h"
#include "proxy/ssh/misc.h"
#if defined(PR_USE_OPENSSL)
# include
# include
# include
# include
# include
# if defined(PR_USE_OPENSSL_ECC)
# include
# include
# endif /* PR_USE_OPENSSL_ECC */
#if defined(PR_USE_SODIUM)
# include
# define CURVE25519_SIZE 32
#endif /* PR_USE_SODIUM */
#if defined(HAVE_X448_OPENSSL) && defined(HAVE_SHA512_OPENSSL)
# define CURVE448_SIZE 56
#endif /* HAVE_X448_OPENSSL and HAVE_SHA512_OPENSSL */
/* This needs to align/match with the SFTP_ROLE_CLIENT macro from mod_sftp.h,
* for now.
*/
#define PROXY_SSH_ROLE_CLIENT 2
/* Define the min/preferred/max DH group lengths we request; see RFC 4419. */
#define PROXY_SSH_DH_MIN_LEN 2048
#define PROXY_SSH_DH_MAX_LEN 8192
extern pr_response_t *resp_list, *resp_err_list;
/* For managing the kexinit process */
static pool *kex_pool = NULL;
/* For hostkey verification. */
static struct proxy_ssh_datastore *kex_ds = NULL;
static int kex_verify_hostkeys = FALSE;
struct proxy_ssh_kex_names {
const char *kex_algo;
const char *server_hostkey_algo;
const char *c2s_encrypt_algo;
const char *s2c_encrypt_algo;
const char *c2s_mac_algo;
const char *s2c_mac_algo;
const char *c2s_comp_algo;
const char *s2c_comp_algo;
const char *c2s_lang;
const char *s2c_lang;
};
struct proxy_ssh_kex {
pool *pool;
/* Versions */
const char *client_version;
const char *server_version;
/* KEXINIT lists from client */
struct proxy_ssh_kex_names *client_names;
/* KEXINIT lists from server. */
struct proxy_ssh_kex_names *server_names;
/* Session algorithms */
struct proxy_ssh_kex_names *session_names;
/* For constructing the session ID/hash */
unsigned char *client_kexinit_payload;
size_t client_kexinit_payload_len;
unsigned char *server_kexinit_payload;
size_t server_kexinit_payload_len;
int first_kex_follows;
/* Server-preferred hostkey type, based on algorithm:
*
* "ssh-dss" --> PROXY_SSH_KEY_DSA
* "ssh-rsa" --> PROXY_SSH_KEY_RSA
* "ecdsa-sha2-*" --> PROXY_SSH_KEY_ECDSA_*
* "ssh-ed25519" --> PROXY_SSH_KEY_ED25519
* "ssh-ed448" --> PROXY_SSH_KEY_ED448
* "rsa-sha2-256" --> PROXY_SSH_KEY_RSA_SHA256
* "rsa-sha2-512" --> PROXY_SSH_KEY_RSA_SHA512
*/
enum proxy_ssh_key_type_e use_hostkey_type;
/* Using DH group-exchange? */
int use_gex;
/* Using RSA key exchange? */
int use_kexrsa;
/* Using ECDH? */
int use_ecdh;
/* Using Curve25519? */
int use_curve25519;
/* Using Curve448? */
int use_curve448;
/* Using extension negotiations? */
int use_ext_info;
/* For generating the session ID */
DH *dh;
const BIGNUM *e;
const EVP_MD *hash;
const BIGNUM *k;
const char *h;
uint32_t hlen;
uint32_t dh_gex_min;
uint32_t dh_gex_pref;
uint32_t dh_gex_max;
RSA *rsa;
unsigned char *rsa_encrypted;
uint32_t rsa_encrypted_len;
#if defined(PR_USE_OPENSSL_ECC)
EC_KEY *ec;
EC_POINT *server_point;
#endif /* PR_USE_OPENSSL_ECC */
#if defined(PR_USE_SODIUM) && defined(HAVE_SHA256_OPENSSL)
unsigned char *client_curve25519_priv_key;
unsigned char *client_curve25519_pub_key;
unsigned char *server_curve25519_pub_key;
#endif /* PR_USE_SODIUM and HAVE_SHA256_OPENSSL */
#if defined(HAVE_X448_OPENSSL) && defined(HAVE_SHA512_OPENSSL)
unsigned char *client_curve448_priv_key;
unsigned char *client_curve448_pub_key;
unsigned char *server_curve448_pub_key;
#endif /* HAVE_X448_OPENSSL and HAVE_SHA512_OPENSSL */
};
static struct proxy_ssh_kex *kex_first_kex = NULL;
static struct proxy_ssh_kex *kex_rekey_kex = NULL;
static int kex_sent_kexinit = FALSE;
/* Using strict kex? Note that we maintain this value here, rather than
* in the proxy_ssh_kex struct, so that any "use strict KEX" flag set via the
* first KEXINIT is used through any subsequent KEXINITs.
*/
static int use_strict_kex = FALSE;
static int kex_done_first_kex = FALSE;
/* Diffie-Hellman group moduli */
static const char *dh_group1_str =
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74"
"020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437"
"4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF";
static const char *dh_group14_str =
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74"
"020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437"
"4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05"
"98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB"
"9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B"
"E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718"
"3995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF";
static const char *dh_group16_str =
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
"29024E088A67CC74020BBEA63B139B22514A08798E3404DD"
"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
"C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F"
"83655D23DCA3AD961C62F356208552BB9ED529077096966D"
"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B"
"E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"
"DE2BCBF6955817183995497CEA956AE515D2261898FA0510"
"15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64"
"ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7"
"ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B"
"F12FFA06D98A0864D87602733EC86A64521F2B18177B200C"
"BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31"
"43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7"
"88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA"
"2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6"
"287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED"
"1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9"
"93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199"
"FFFFFFFFFFFFFFFF";
static const char *dh_group18_str =
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
"29024E088A67CC74020BBEA63B139B22514A08798E3404DD"
"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
"C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F"
"83655D23DCA3AD961C62F356208552BB9ED529077096966D"
"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B"
"E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"
"DE2BCBF6955817183995497CEA956AE515D2261898FA0510"
"15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64"
"ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7"
"ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B"
"F12FFA06D98A0864D87602733EC86A64521F2B18177B200C"
"BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31"
"43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7"
"88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA"
"2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6"
"287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED"
"1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9"
"93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492"
"36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BD"
"F8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831"
"179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B"
"DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF"
"5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6"
"D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F3"
"23A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA"
"CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE328"
"06A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55C"
"DA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE"
"12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E4"
"38777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300"
"741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F568"
"3423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9"
"22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B"
"4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A"
"062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A36"
"4597E899A0255DC164F31CC50846851DF9AB48195DED7EA1"
"B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F92"
"4009438B481C6CD7889A002ED5EE382BC9190DA6FC026E47"
"9558E4475677E9AA9E3050E2765694DFC81F56E880B96E71"
"60C980DD98EDD3DFFFFFFFFFFFFFFFFF";
#define PROXY_SSH_DH_GROUP1_SHA1 1
#define PROXY_SSH_DH_GROUP14_SHA1 2
#define PROXY_SSH_DH_GEX_SHA1 3
#define PROXY_SSH_DH_GEX_SHA256 4
#define PROXY_SSH_KEXRSA_SHA1 5
#define PROXY_SSH_KEXRSA_SHA256 6
#define PROXY_SSH_ECDH_SHA256 7
#define PROXY_SSH_ECDH_SHA384 8
#define PROXY_SSH_ECDH_SHA512 9
#define PROXY_SSH_DH_GROUP14_SHA256 10
#define PROXY_SSH_DH_GROUP16_SHA512 11
#define PROXY_SSH_DH_GROUP18_SHA512 12
#define PROXY_SSH_KEXRSA_SHA1_SIZE 2048
#define PROXY_SSH_KEXRSA_SHA256_SIZE 3072
static const char *kex_client_version = NULL;
static const char *kex_server_version = NULL;
static unsigned char kex_digest_buf[EVP_MAX_MD_SIZE];
/* Necessary prototypes. */
static struct proxy_ssh_packet *read_kex_packet(pool *, struct proxy_ssh_kex *,
conn_t *, int, char *, unsigned int, ...);
static const char *trace_channel = "proxy.ssh.kex";
static int digest_data(struct proxy_ssh_kex *kex, unsigned char *buf,
uint32_t len, uint32_t *hlen) {
#if OPENSSL_VERSION_NUMBER < 0x10100000L || \
defined(HAVE_LIBRESSL)
EVP_MD_CTX ctx;
#endif /* prior to OpenSSL-1.1.0 */
EVP_MD_CTX *pctx;
#if OPENSSL_VERSION_NUMBER >= 0x10100000LL && \
!defined(HAVE_LIBRESSL)
pctx = EVP_MD_CTX_new();
#else
pctx = &ctx;
#endif /* OpenSSL-1.1.0 and later */
/* In OpenSSL 0.9.6, many of the EVP_Digest* functions returned void, not
* int. Without these ugly OpenSSL version preprocessor checks, the
* compiler will error out with "void value not ignored as it ought to be".
*/
#if OPENSSL_VERSION_NUMBER >= 0x000907000L
if (EVP_DigestInit(pctx, kex->hash) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error initializing message digest: %s", proxy_ssh_crypto_get_errors());
# if OPENSSL_VERSION_NUMBER >= 0x10100000LL && \
!defined(HAVE_LIBRESSL)
EVP_MD_CTX_free(pctx);
# endif /* OpenSSL-1.1.0 and later */
return -1;
}
#else
EVP_DigestInit(pctx, kex->hash);
#endif
#if OPENSSL_VERSION_NUMBER >= 0x000907000L
if (EVP_DigestUpdate(pctx, buf, len) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error updating message digest: %s", proxy_ssh_crypto_get_errors());
# if OPENSSL_VERSION_NUMBER >= 0x10100000LL && \
!defined(HAVE_LIBRESSL)
EVP_MD_CTX_free(pctx);
# endif /* OpenSSL-1.1.0 and later */
return -1;
}
#else
EVP_DigestUpdate(pctx, buf, len);
#endif
#if OPENSSL_VERSION_NUMBER >= 0x000907000L
if (EVP_DigestFinal(pctx, kex_digest_buf, hlen) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error finalizing message digest: %s", proxy_ssh_crypto_get_errors());
# if OPENSSL_VERSION_NUMBER >= 0x10100000LL && \
!defined(HAVE_LIBRESSL)
EVP_MD_CTX_free(pctx);
# endif /* OpenSSL-1.1.0 and later */
return -1;
}
#else
EVP_DigestFinal(pctx, kex_digest_buf, hlen);
#endif
#if OPENSSL_VERSION_NUMBER >= 0x10100000LL && \
!defined(HAVE_LIBRESSL)
EVP_MD_CTX_free(pctx);
#endif /* OpenSSL-1.1.0 and later */
return 0;
}
static const unsigned char *calculate_h(pool *p, struct proxy_ssh_kex *kex,
const unsigned char *hostkey_data, uint32_t hostkey_datalen,
const BIGNUM *server_pub_key, const BIGNUM *k, uint32_t *hlen) {
const BIGNUM *dh_pub_key;
unsigned char *buf, *ptr;
uint32_t buflen, bufsz, len = 0;
bufsz = buflen = 8192;
/* XXX Is this buffer large enough? Too large? */
ptr = buf = palloc(p, bufsz);
/* Write all of the data into the buffer in the SSH2 format, and hash it. */
/* First, the version strings */
len += proxy_ssh_msg_write_string(&buf, &buflen, kex->client_version);
len += proxy_ssh_msg_write_string(&buf, &buflen, kex->server_version);
/* Client's KEXINIT */
len += proxy_ssh_msg_write_int(&buf, &buflen,
kex->client_kexinit_payload_len + 1);
len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEXINIT);
len += proxy_ssh_msg_write_data(&buf, &buflen, kex->client_kexinit_payload,
kex->client_kexinit_payload_len, FALSE);
/* Server's KEXINIT */
len += proxy_ssh_msg_write_int(&buf, &buflen,
kex->server_kexinit_payload_len + 1);
len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEXINIT);
len += proxy_ssh_msg_write_data(&buf, &buflen, kex->server_kexinit_payload,
kex->server_kexinit_payload_len, FALSE);
/* Server hostkey data */
len += proxy_ssh_msg_write_data(&buf, &buflen, hostkey_data, hostkey_datalen,
TRUE);
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
DH_get0_key(kex->dh, &dh_pub_key, NULL);
#else
dh_pub_key = kex->dh->pub_key;
#endif /* prior to OpenSSL-1.1.0 */
/* Client's key */
len += proxy_ssh_msg_write_mpint(&buf, &buflen, dh_pub_key);
/* Server's key */
len += proxy_ssh_msg_write_mpint(&buf, &buflen, server_pub_key);
/* Shared secret */
len += proxy_ssh_msg_write_mpint(&buf, &buflen, k);
if (digest_data(kex, ptr, len, hlen) < 0) {
pr_memscrub(ptr, bufsz);
return NULL;
}
pr_memscrub(ptr, bufsz);
return kex_digest_buf;
}
static int verify_h(pool *p, struct proxy_ssh_kex *kex,
const unsigned char *key_data, uint32_t key_datalen,
const unsigned char *sig_data, uint32_t sig_datalen,
const unsigned char *h, uint32_t hlen) {
int res, xerrno;
const char *pubkey_algo = NULL;
switch (kex->use_hostkey_type) {
case PROXY_SSH_KEY_DSA:
pubkey_algo = "ssh-dss";
break;
case PROXY_SSH_KEY_RSA:
pubkey_algo = "ssh-rsa";
break;
#if defined(HAVE_SHA256_OPENSSL)
case PROXY_SSH_KEY_RSA_SHA256:
pubkey_algo = "rsa-sha2-256";
break;
#endif /* HAVE_SHA256_OPENSSL */
#if defined(HAVE_SHA512_OPENSSL)
case PROXY_SSH_KEY_RSA_SHA512:
pubkey_algo = "rsa-sha2-512";
break;
#endif /* HAVE_SHA512_OPENSSL */
#if defined(PR_USE_OPENSSL_ECC)
case PROXY_SSH_KEY_ECDSA_256:
pubkey_algo = "ecdsa-sha2-nistp256";
break;
case PROXY_SSH_KEY_ECDSA_384:
pubkey_algo = "ecdsa-sha2-nistp384";
break;
case PROXY_SSH_KEY_ECDSA_521:
pubkey_algo = "ecdsa-sha2-nistp521";
break;
#endif /* PR_USE_OPENSSL_ECC */
#if defined(PR_USE_SODIUM)
case PROXY_SSH_KEY_ED25519:
pubkey_algo = "ssh-ed25519";
break;
#endif /* PR_USE_SODIUM */
#if defined(HAVE_X448_OPENSSL)
case PROXY_SSH_KEY_ED448:
pubkey_algo = "ssh-ed448";
break;
#endif /* HAVE_X448_OPENSSL */
default:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to verify signed data: Unknown public key algorithm");
errno = EINVAL;
return -1;
}
res = proxy_ssh_keys_verify_signed_data(p, pubkey_algo,
(unsigned char *) key_data, key_datalen,
(unsigned char *) sig_data, sig_datalen,
(unsigned char *) h, hlen);
xerrno = errno;
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to verify server signature on H: %s", strerror(xerrno));
errno = xerrno;
}
return res;
}
static const unsigned char *calculate_gex_h(pool *p, struct proxy_ssh_kex *kex,
const unsigned char *hostkey_data, uint32_t hostkey_datalen,
const BIGNUM *server_pub_key, const BIGNUM *k, uint32_t *hlen) {
const BIGNUM *dh_p, *dh_g, *dh_pub_key;
unsigned char *buf, *ptr;
uint32_t buflen, bufsz, len = 0;
/* XXX Is this buffer large enough? Too large? */
bufsz = buflen = 8192;
ptr = buf = palloc(p, bufsz);
/* Write all of the data into the buffer in the SSH2 format, and hash it.
* The ordering of these fields is described in RFC4419.
*/
/* First, the version strings */
len += proxy_ssh_msg_write_string(&buf, &buflen, kex->client_version);
len += proxy_ssh_msg_write_string(&buf, &buflen, kex->server_version);
/* Client's KEXINIT */
len += proxy_ssh_msg_write_int(&buf, &buflen,
kex->client_kexinit_payload_len + 1);
len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEXINIT);
len += proxy_ssh_msg_write_data(&buf, &buflen, kex->client_kexinit_payload,
kex->client_kexinit_payload_len, FALSE);
/* Server's KEXINIT */
len += proxy_ssh_msg_write_int(&buf, &buflen,
kex->server_kexinit_payload_len + 1);
len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEXINIT);
len += proxy_ssh_msg_write_data(&buf, &buflen, kex->server_kexinit_payload,
kex->server_kexinit_payload_len, FALSE);
/* Hostkey data */
len += proxy_ssh_msg_write_data(&buf, &buflen, hostkey_data, hostkey_datalen,
TRUE);
if (kex->dh_gex_min == 0 ||
kex->dh_gex_max == 0) {
len += proxy_ssh_msg_write_int(&buf, &buflen, kex->dh_gex_pref);
} else {
len += proxy_ssh_msg_write_int(&buf, &buflen, kex->dh_gex_min);
len += proxy_ssh_msg_write_int(&buf, &buflen, kex->dh_gex_pref);
len += proxy_ssh_msg_write_int(&buf, &buflen, kex->dh_gex_max);
}
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
DH_get0_pqg(kex->dh, &dh_p, NULL, &dh_g);
#else
dh_p = kex->dh->p;
dh_g = kex->dh->g;
#endif /* prior to OpenSSL-1.1.0 */
len += proxy_ssh_msg_write_mpint(&buf, &buflen, dh_p);
len += proxy_ssh_msg_write_mpint(&buf, &buflen, dh_g);
/* Client's key */
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
DH_get0_key(kex->dh, &dh_pub_key, NULL);
#else
dh_pub_key = kex->dh->pub_key;
#endif /* prior to OpenSSL-1.1.0 */
len += proxy_ssh_msg_write_mpint(&buf, &buflen, dh_pub_key);
/* Server's key */
len += proxy_ssh_msg_write_mpint(&buf, &buflen, server_pub_key);
/* Shared secret */
len += proxy_ssh_msg_write_mpint(&buf, &buflen, k);
if (digest_data(kex, ptr, len, hlen) < 0) {
pr_memscrub(ptr, bufsz);
return NULL;
}
pr_memscrub(ptr, bufsz);
return kex_digest_buf;
}
static const unsigned char *calculate_kexrsa_h(pool *p,
struct proxy_ssh_kex *kex,
const unsigned char *hostkey_data, uint32_t hostkey_datalen,
const BIGNUM *k, uint32_t *hlen) {
const BIGNUM *rsa_e = NULL, *rsa_n = NULL;
unsigned char *buf, *ptr, *rsa_key, *rsa_data;
uint32_t buflen, bufsz, len = 0, rsa_datalen, rsa_keysz, rsa_keylen = 0;
/* XXX Is this buffer large enough? Too large? */
rsa_keysz = rsa_datalen = 4096;
rsa_key = rsa_data = palloc(p, rsa_keysz);
/* Write the transient RSA public key into its own buffer, to then be
* written in its entirety as an SSH2 string.
*/
rsa_keylen += proxy_ssh_msg_write_string(&rsa_data, &rsa_datalen, "ssh-rsa");
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
RSA_get0_key(kex->rsa, &rsa_n, &rsa_e, NULL);
#else
rsa_e = kex->rsa->e;
rsa_n = kex->rsa->n;
#endif /* prior to OpenSSL-1.1.0 */
rsa_keylen += proxy_ssh_msg_write_mpint(&rsa_data, &rsa_datalen, rsa_e);
rsa_keylen += proxy_ssh_msg_write_mpint(&rsa_data, &rsa_datalen, rsa_n);
bufsz = buflen = 4096;
/* XXX Is this buffer large enough? Too large? */
ptr = buf = palloc(p, bufsz);
/* Write all of the data into the buffer in the SSH2 format, and hash it. */
/* First, the version strings */
len += proxy_ssh_msg_write_string(&buf, &buflen, kex->client_version);
len += proxy_ssh_msg_write_string(&buf, &buflen, kex->server_version);
/* Client's KEXINIT */
len += proxy_ssh_msg_write_int(&buf, &buflen,
kex->client_kexinit_payload_len + 1);
len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEXINIT);
len += proxy_ssh_msg_write_data(&buf, &buflen, kex->client_kexinit_payload,
kex->client_kexinit_payload_len, FALSE);
/* Server's KEXINIT */
len += proxy_ssh_msg_write_int(&buf, &buflen,
kex->server_kexinit_payload_len + 1);
len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEXINIT);
len += proxy_ssh_msg_write_data(&buf, &buflen, kex->server_kexinit_payload,
kex->server_kexinit_payload_len, FALSE);
/* Hostkey data */
len += proxy_ssh_msg_write_data(&buf, &buflen, hostkey_data, hostkey_datalen,
TRUE);
/* Transient RSA public key */
len += proxy_ssh_msg_write_data(&buf, &buflen, rsa_key, rsa_keylen, TRUE);
/* RSA-encrypted secret */
len += proxy_ssh_msg_write_data(&buf, &buflen, kex->rsa_encrypted,
kex->rsa_encrypted_len, TRUE);
/* Shared secret. */
len += proxy_ssh_msg_write_mpint(&buf, &buflen, k);
if (digest_data(kex, ptr, len, hlen) < 0) {
pr_memscrub(ptr, bufsz);
return NULL;
}
pr_memscrub(rsa_key, rsa_keysz);
pr_memscrub(ptr, bufsz);
return kex_digest_buf;
}
#if defined(PR_USE_OPENSSL_ECC)
static const unsigned char *calculate_ecdh_h(pool *p, struct proxy_ssh_kex *kex,
const unsigned char *hostkey_data, uint32_t hostkey_datalen,
const BIGNUM *k, uint32_t *hlen) {
unsigned char *buf, *ptr;
uint32_t buflen, bufsz, len = 0;
bufsz = buflen = 4096;
/* XXX Is this buffer large enough? Too large? */
ptr = buf = palloc(p, bufsz);
/* Write all of the data into the buffer in the SSH2 format, and hash it.
* The ordering of these fields is described in RFC5656.
*/
/* First, the version strings */
len += proxy_ssh_msg_write_string(&buf, &buflen, kex->client_version);
len += proxy_ssh_msg_write_string(&buf, &buflen, kex->server_version);
/* Client's KEXINIT */
len += proxy_ssh_msg_write_int(&buf, &buflen,
kex->client_kexinit_payload_len + 1);
len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEXINIT);
len += proxy_ssh_msg_write_data(&buf, &buflen, kex->client_kexinit_payload,
kex->client_kexinit_payload_len, FALSE);
/* Server's KEXINIT */
len += proxy_ssh_msg_write_int(&buf, &buflen,
kex->server_kexinit_payload_len + 1);
len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEXINIT);
len += proxy_ssh_msg_write_data(&buf, &buflen, kex->server_kexinit_payload,
kex->server_kexinit_payload_len, FALSE);
/* Hostkey data */
len += proxy_ssh_msg_write_data(&buf, &buflen, hostkey_data, hostkey_datalen,
TRUE);
/* Client public key */
len += proxy_ssh_msg_write_ecpoint(&buf, &buflen, EC_KEY_get0_group(kex->ec),
EC_KEY_get0_public_key(kex->ec));
/* Server public key */
len += proxy_ssh_msg_write_ecpoint(&buf, &buflen, EC_KEY_get0_group(kex->ec),
kex->server_point);
/* Shared secret */
len += proxy_ssh_msg_write_mpint(&buf, &buflen, k);
if (digest_data(kex, ptr, len, hlen) < 0) {
pr_memscrub(ptr, bufsz);
return NULL;
}
pr_memscrub(ptr, bufsz);
return kex_digest_buf;
}
#endif /* PR_USE_OPENSSL_ECC */
/* Make sure that the DH key we're generating is good enough. */
static int have_good_dh(DH *dh, const BIGNUM *pub_key) {
register int i;
unsigned int nbits = 0;
const BIGNUM *dh_p = NULL;
BIGNUM *tmp;
if (dh == NULL ||
pub_key == NULL) {
errno = EINVAL;
return -1;
}
#if OPENSSL_VERSION_NUMBER >= 0x0090801fL
if (BN_is_negative(pub_key)) {
pr_trace_msg(trace_channel, 10,
"DH public keys cannot have negative numbers");
errno = EINVAL;
return -1;
}
#endif /* OpenSSL-0.9.8a or later */
if (BN_cmp(pub_key, BN_value_one()) != 1) {
pr_trace_msg(trace_channel, 10, "bad DH public key exponent (<= 1)");
errno = EINVAL;
return -1;
}
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
DH_get0_pqg(dh, &dh_p, NULL, NULL);
#else
dh_p = dh->p;
#endif /* prior to OpenSSL-1.1.0 */
tmp = BN_new();
if (!BN_sub(tmp, dh_p, BN_value_one()) ||
BN_cmp(pub_key, tmp) != -1) {
BN_clear_free(tmp);
pr_trace_msg(trace_channel, 10, "bad DH public key (>= p-1)");
errno = EINVAL;
return -1;
}
BN_clear_free(tmp);
for (i = 0; i <= BN_num_bits(pub_key); i++) {
if (BN_is_bit_set(pub_key, i)) {
nbits++;
}
}
/* The number of bits set in the public key must be greater than one.
* Otherwise, the public key will not hold up under scrutiny, not for
* our needs. (The OpenSSH client is picky about the DH public keys it
* will accept as well, so this is necessary to pass OpenSSH's requirements).
*/
if (nbits <= 1) {
errno = EINVAL;
return -1;
}
pr_trace_msg(trace_channel, 10, "good DH public key: %u bits set", nbits);
return 0;
}
static int get_dh_nbits(struct proxy_ssh_kex *kex) {
int dh_nbits = 0, dh_size = 0;
const char *algo;
const EVP_CIPHER *cipher;
const EVP_MD *digest;
algo = kex->session_names->c2s_encrypt_algo;
cipher = proxy_ssh_crypto_get_cipher(algo, NULL, NULL, NULL);
if (cipher != NULL) {
int block_size, key_len;
key_len = EVP_CIPHER_key_length(cipher);
if (strcmp(algo, "none") == 0 &&
key_len < 32) {
key_len = 32;
}
if (dh_size < key_len) {
dh_size = key_len;
pr_trace_msg(trace_channel, 19,
"set DH size to %d bytes, matching client-to-server '%s' cipher "
"key length", dh_size, algo);
}
block_size = EVP_CIPHER_block_size(cipher);
if (dh_size < block_size) {
dh_size = block_size;
pr_trace_msg(trace_channel, 19,
"set DH size to %d bytes, matching client-to-server '%s' cipher "
"block size", dh_size, algo);
}
}
algo = kex->session_names->s2c_encrypt_algo;
cipher = proxy_ssh_crypto_get_cipher(algo, NULL, NULL, NULL);
if (cipher != NULL) {
int block_size, key_len;
key_len = EVP_CIPHER_key_length(cipher);
if (strcmp(algo, "none") == 0 &&
key_len < 32) {
key_len = 32;
}
if (dh_size < key_len) {
dh_size = key_len;
pr_trace_msg(trace_channel, 19,
"set DH size to %d bytes, matching server-to-client '%s' cipher "
"key length", dh_size, algo);
}
block_size = EVP_CIPHER_block_size(cipher);
if (dh_size < block_size) {
dh_size = block_size;
pr_trace_msg(trace_channel, 19,
"set DH size to %d bytes, matching server-to-client '%s' cipher "
"block size", dh_size, algo);
}
}
algo = kex->session_names->c2s_mac_algo;
digest = proxy_ssh_crypto_get_digest(algo, NULL);
if (digest != NULL) {
int mac_len;
mac_len = EVP_MD_size(digest);
if (dh_size < mac_len) {
dh_size = mac_len;
pr_trace_msg(trace_channel, 19,
"set DH size to %d bytes, matching client-to-server '%s' digest size",
dh_size, algo);
}
}
algo = kex->session_names->s2c_mac_algo;
digest = proxy_ssh_crypto_get_digest(algo, NULL);
if (digest != NULL) {
int mac_len;
mac_len = EVP_MD_size(digest);
if (dh_size < mac_len) {
dh_size = mac_len;
pr_trace_msg(trace_channel, 19,
"set DH size to %d bytes, matching server-to-client '%s' digest size",
dh_size, algo);
}
}
/* We want to return bits, not bytes. */
dh_nbits = dh_size * 8;
pr_trace_msg(trace_channel, 8, "requesting DH size of %d bits", dh_nbits);
return dh_nbits;
}
static int create_dh(struct proxy_ssh_kex *kex, int type) {
unsigned int attempts = 0;
int dh_nbits;
DH *dh;
if (type != PROXY_SSH_DH_GROUP1_SHA1 &&
type != PROXY_SSH_DH_GROUP14_SHA1 &&
type != PROXY_SSH_DH_GROUP14_SHA256 &&
type != PROXY_SSH_DH_GROUP16_SHA512 &&
type != PROXY_SSH_DH_GROUP18_SHA512) {
errno = EINVAL;
return -1;
}
if (kex->dh != NULL) {
#if OPENSSL_VERSION_NUMBER < 0x10100000L
if (kex->dh->p != NULL) {
BN_clear_free(kex->dh->p);
kex->dh->p = NULL;
}
if (kex->dh->g != NULL) {
BN_clear_free(kex->dh->g);
kex->dh->g = NULL;
}
if (kex->dh->priv_key != NULL) {
BN_clear_free(kex->dh->priv_key);
kex->dh->priv_key = NULL;
}
if (kex->dh->pub_key != NULL) {
BN_clear_free(kex->dh->pub_key);
kex->dh->pub_key = NULL;
}
#endif /* prior to OpenSSL-1.1.0 */
DH_free(kex->dh);
kex->dh = NULL;
}
dh_nbits = get_dh_nbits(kex);
/* We have 10 attempts to make a DH key which passes muster. */
while (attempts <= 10) {
const BIGNUM *dh_p, *dh_g, *dh_pub_key = NULL, *dh_priv_key = NULL;
pr_signals_handle();
attempts++;
pr_trace_msg(trace_channel, 9, "attempt #%u to create a good DH key",
attempts);
dh = DH_new();
if (dh == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error creating DH: %s", proxy_ssh_crypto_get_errors());
return -1;
}
dh_p = BN_new();
switch (type) {
case PROXY_SSH_DH_GROUP18_SHA512:
if (BN_hex2bn((BIGNUM **) &dh_p, dh_group18_str) == 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error setting DH (group18) P: %s", proxy_ssh_crypto_get_errors());
BN_clear_free((BIGNUM *) dh_p);
DH_free(dh);
return -1;
}
break;
case PROXY_SSH_DH_GROUP16_SHA512:
if (BN_hex2bn((BIGNUM **) &dh_p, dh_group16_str) == 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error setting DH (group16) P: %s", proxy_ssh_crypto_get_errors());
BN_clear_free((BIGNUM *) dh_p);
DH_free(dh);
return -1;
}
break;
case PROXY_SSH_DH_GROUP14_SHA1:
case PROXY_SSH_DH_GROUP14_SHA256:
if (BN_hex2bn((BIGNUM **) &dh_p, dh_group14_str) == 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error setting DH (group14) P: %s", proxy_ssh_crypto_get_errors());
BN_clear_free((BIGNUM *) dh_p);
DH_free(dh);
return -1;
}
break;
default:
if (BN_hex2bn((BIGNUM **) &dh_p, dh_group1_str) == 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error setting DH (group1) P: %s", proxy_ssh_crypto_get_errors());
BN_clear_free((BIGNUM *) dh_p);
DH_free(dh);
return -1;
}
break;
}
dh_g = BN_new();
if (BN_hex2bn((BIGNUM **) &dh_g, "2") == 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error setting DH G: %s", proxy_ssh_crypto_get_errors());
BN_clear_free((BIGNUM *) dh_p);
BN_clear_free((BIGNUM *) dh_g);
DH_free(dh);
return -1;
}
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
DH_set0_pqg(dh, (BIGNUM *) dh_p, NULL, (BIGNUM *) dh_g);
#else
dh->p = dh_p;
dh->g = dh_g;
#endif /* prior to OpenSSL-1.1.0 */
dh_priv_key = BN_new();
/* Generate a random private exponent of the desired size, in bits. */
if (!BN_rand((BIGNUM *) dh_priv_key, dh_nbits, 0, 0)) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error generating DH random key (%d bits): %s", dh_nbits,
proxy_ssh_crypto_get_errors());
BN_clear_free((BIGNUM *) dh_priv_key);
DH_free(dh);
return -1;
}
dh_pub_key = BN_new();
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
DH_set0_key(dh, (BIGNUM *) dh_pub_key, (BIGNUM *) dh_priv_key);
#else
dh->pub_key = dh_pub_key;
dh->priv_key = dh_priv_key;
#endif /* prior to OpenSSL-1.1.0 */
pr_trace_msg(trace_channel, 12, "generating DH key");
if (DH_generate_key(dh) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error generating DH key: %s", proxy_ssh_crypto_get_errors());
DH_free(dh);
return -1;
}
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
DH_get0_key(dh, &dh_pub_key, NULL);
#else
dh_pub_key = dh->pub_key;
#endif /* prior to OpenSSL-1.1.0 */
if (have_good_dh(dh, dh_pub_key) < 0) {
DH_free(dh);
continue;
}
kex->dh = dh;
switch (type) {
#if defined(HAVE_SHA512_OPENSSL)
case PROXY_SSH_DH_GROUP16_SHA512:
case PROXY_SSH_DH_GROUP18_SHA512:
kex->hash = EVP_sha512();
break;
#endif /* HAVE_SHA512_OPENSSL */
#if defined(HAVE_SHA256_OPENSSL)
case PROXY_SSH_DH_GROUP14_SHA256:
kex->hash = EVP_sha256();
break;
#endif /* HAVE_SHA256_OPENSSL */
default:
kex->hash = EVP_sha1();
}
return 0;
}
errno = EPERM;
return -1;
}
static int prepare_dh(struct proxy_ssh_kex *kex, int type) {
DH *dh;
if (type != PROXY_SSH_DH_GEX_SHA1 &&
type != PROXY_SSH_DH_GEX_SHA256) {
errno = EINVAL;
return -1;
}
if (kex->dh != NULL) {
#if OPENSSL_VERSION_NUMBER < 0x10100000L
if (kex->dh->p != NULL) {
BN_clear_free(kex->dh->p);
kex->dh->p = NULL;
}
if (kex->dh->g != NULL) {
BN_clear_free(kex->dh->g);
kex->dh->g = NULL;
}
if (kex->dh->priv_key != NULL) {
BN_clear_free(kex->dh->priv_key);
kex->dh->priv_key = NULL;
}
if (kex->dh->pub_key != NULL) {
BN_clear_free(kex->dh->pub_key);
kex->dh->pub_key = NULL;
}
#endif /* prior to OpenSSL-1.1.0 */
DH_free(kex->dh);
kex->dh = NULL;
}
dh = DH_new();
if (dh == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error creating DH: %s", proxy_ssh_crypto_get_errors());
return -1;
}
kex->dh = dh;
if (type == PROXY_SSH_DH_GEX_SHA1) {
kex->hash = EVP_sha1();
#if ((OPENSSL_VERSION_NUMBER > 0x000907000L && defined(OPENSSL_FIPS)) || \
(OPENSSL_VERSION_NUMBER > 0x000908000L)) && \
defined(HAVE_SHA256_OPENSSL)
} else if (type == PROXY_SSH_DH_GEX_SHA256) {
kex->hash = EVP_sha256();
#endif
}
return 0;
}
static int create_kexrsa(struct proxy_ssh_kex *kex, int type) {
if (type != PROXY_SSH_KEXRSA_SHA1 &&
type != PROXY_SSH_KEXRSA_SHA256) {
errno = EINVAL;
return -1;
}
if (kex->rsa != NULL) {
RSA_free(kex->rsa);
kex->rsa = NULL;
}
if (kex->rsa_encrypted != NULL) {
pr_memscrub(kex->rsa_encrypted, kex->rsa_encrypted_len);
kex->rsa_encrypted = NULL;
kex->rsa_encrypted_len = 0;
}
if (type == PROXY_SSH_KEXRSA_SHA1) {
kex->hash = EVP_sha1();
#if ((OPENSSL_VERSION_NUMBER > 0x000907000L && defined(OPENSSL_FIPS)) || \
(OPENSSL_VERSION_NUMBER > 0x000908000L)) && \
defined(HAVE_SHA256_OPENSSL)
} else if (type == PROXY_SSH_KEXRSA_SHA256) {
kex->hash = EVP_sha256();
#endif
}
return 0;
}
#if defined(PR_USE_OPENSSL_ECC)
static int create_ecdh(struct proxy_ssh_kex *kex, int type) {
EC_KEY *ec;
int curve_nid = -1;
char *curve_name = NULL;
switch (type) {
case PROXY_SSH_ECDH_SHA256:
curve_name = "NID_X9_62_prime256v1";
# if defined(HAVE_SHA256_OPENSSL)
curve_nid = NID_X9_62_prime256v1;
kex->hash = EVP_sha256();
# else
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to generate EC key using '%s': OpenSSL lacks SHA256 support",
curve_name);
errno = ENOSYS;
return -1;
# endif /* HAVE_SHA256_OPENSSL */
break;
case PROXY_SSH_ECDH_SHA384:
curve_name = "NID_secp384r1";
# if defined(HAVE_SHA256_OPENSSL)
curve_nid = NID_secp384r1;
kex->hash = EVP_sha384();
# else
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to generate EC key using '%s': OpenSSL lacks SHA256 support",
curve_name);
errno = ENOSYS;
return -1;
# endif /* HAVE_SHA256_OPENSSL */
break;
case PROXY_SSH_ECDH_SHA512:
curve_name = "NID_secp521r1";
# if defined(HAVE_SHA512_OPENSSL)
curve_nid = NID_secp521r1;
kex->hash = EVP_sha512();
# else
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to generate EC key using '%s': OpenSSL lacks SHA512 support",
curve_name);
errno = ENOSYS;
return -1;
# endif /* HAVE_SHA512_OPENSSL */
break;
default:
errno = EINVAL;
return -1;
}
ec = EC_KEY_new_by_curve_name(curve_nid);
if (ec == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error generating new EC key using '%s': %s", curve_name,
proxy_ssh_crypto_get_errors());
return -1;
}
if (EC_KEY_generate_key(ec) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error generating new EC key: %s", proxy_ssh_crypto_get_errors());
EC_KEY_free(ec);
return -1;
}
kex->ec = ec;
return 0;
}
#endif /* PR_USE_OPENSSL_ECC */
#if defined(PR_USE_SODIUM) && defined(HAVE_SHA256_OPENSSL)
static int generate_curve25519_keys(unsigned char *priv_key,
unsigned char *pub_key) {
static const unsigned char basepoint[CURVE25519_SIZE] = {9};
unsigned char zero_curve25519[CURVE25519_SIZE];
int res;
randombytes_buf(priv_key, CURVE25519_SIZE);
res = crypto_scalarmult_curve25519(pub_key, priv_key, basepoint);
if (res < 0) {
pr_trace_msg(trace_channel, 3,
"error performing Curve25519 scalar multiplication");
errno = EINVAL;
return -1;
}
/* Check for all-zero public keys. */
sodium_memzero(zero_curve25519, CURVE25519_SIZE);
if (sodium_memcmp(pub_key, zero_curve25519, CURVE25519_SIZE) == 0) {
pr_trace_msg(trace_channel, 12,
"generated all-zero Curve25519 public key, trying again");
return generate_curve25519_keys(priv_key, pub_key);
}
return 0;
}
static int get_curve25519_shared_key(unsigned char *shared_key,
unsigned char *pub_key, unsigned char *priv_key) {
int res;
res = crypto_scalarmult_curve25519(shared_key, priv_key, pub_key);
if (res < 0) {
pr_trace_msg(trace_channel, 3,
"error performing Curve25519 scalar multiplication");
errno = EINVAL;
return -1;
}
return CURVE25519_SIZE;
}
static const unsigned char *calculate_curve25519_h(pool *p,
struct proxy_ssh_kex *kex,
const unsigned char *hostkey_data, uint32_t hostkey_datalen,
const BIGNUM *k, uint32_t *hlen) {
unsigned char *buf, *ptr;
uint32_t buflen, bufsz, len = 0;
bufsz = buflen = 4096;
/* XXX Is this buffer large enough? Too large? */
ptr = buf = palloc(p, bufsz);
/* Write all of the data into the buffer in the SSH2 format, and hash it.
* The ordering of these fields is described in RFC5656.
*/
/* First, the version strings */
len += proxy_ssh_msg_write_string(&buf, &buflen, kex->client_version);
len += proxy_ssh_msg_write_string(&buf, &buflen, kex->server_version);
/* Client's KEXINIT */
len += proxy_ssh_msg_write_int(&buf, &buflen,
kex->client_kexinit_payload_len + 1);
len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEXINIT);
len += proxy_ssh_msg_write_data(&buf, &buflen, kex->client_kexinit_payload,
kex->client_kexinit_payload_len, FALSE);
/* Server's KEXINIT */
len += proxy_ssh_msg_write_int(&buf, &buflen,
kex->server_kexinit_payload_len + 1);
len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEXINIT);
len += proxy_ssh_msg_write_data(&buf, &buflen, kex->server_kexinit_payload,
kex->server_kexinit_payload_len, FALSE);
/* Hostkey data */
len += proxy_ssh_msg_write_data(&buf, &buflen, hostkey_data, hostkey_datalen,
TRUE);
/* Client's key */
len += proxy_ssh_msg_write_data(&buf, &buflen, kex->client_curve25519_pub_key,
CURVE25519_SIZE, TRUE);
/* Server's key */
len += proxy_ssh_msg_write_data(&buf, &buflen, kex->server_curve25519_pub_key,
CURVE25519_SIZE, TRUE);
/* Shared secret */
len += proxy_ssh_msg_write_mpint(&buf, &buflen, k);
if (digest_data(kex, ptr, len, hlen) < 0) {
pr_memscrub(ptr, bufsz);
return NULL;
}
pr_memscrub(ptr, bufsz);
return kex_digest_buf;
}
static int create_curve25519(struct proxy_ssh_kex *kex) {
kex->client_curve25519_priv_key = palloc(kex_pool, CURVE25519_SIZE);
kex->client_curve25519_pub_key = palloc(kex_pool, CURVE25519_SIZE);
return generate_curve25519_keys(kex->client_curve25519_priv_key,
kex->client_curve25519_pub_key);
}
#endif /* PR_USE_SODIUM and HAVE_SHA256_OPENSSL */
#if defined(HAVE_X448_OPENSSL) && defined(HAVE_SHA512_OPENSSL)
static int generate_curve448_keys(unsigned char *priv_key,
unsigned char *pub_key) {
EVP_PKEY_CTX *pctx = NULL;
EVP_PKEY *pkey = NULL;
size_t key_len = 0;
pctx = EVP_PKEY_CTX_new_id(NID_X448, NULL);
if (pctx == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error initializing context for Curve448 key: %s",
proxy_ssh_crypto_get_errors());
return -1;
}
if (EVP_PKEY_keygen_init(pctx) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error preparing to generate Curve448 key: %s",
proxy_ssh_crypto_get_errors());
EVP_PKEY_CTX_free(pctx);
return -1;
}
if (EVP_PKEY_keygen(pctx, &pkey) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error generating Curve448 shared key: %s",
proxy_ssh_crypto_get_errors());
EVP_PKEY_CTX_free(pctx);
return -1;
}
key_len = CURVE448_SIZE;
if (EVP_PKEY_get_raw_private_key(pkey, priv_key, &key_len) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error obtaining Curve448 private key: %s",
proxy_ssh_crypto_get_errors());
EVP_PKEY_CTX_free(pctx);
EVP_PKEY_free(pkey);
return -1;
}
key_len = CURVE448_SIZE;
if (EVP_PKEY_get_raw_public_key(pkey, pub_key, &key_len) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error obtaining Curve448 public key: %s", proxy_ssh_crypto_get_errors());
EVP_PKEY_CTX_free(pctx);
EVP_PKEY_free(pkey);
return -1;
}
EVP_PKEY_CTX_free(pctx);
EVP_PKEY_free(pkey);
return 0;
}
static int get_curve448_shared_key(unsigned char *shared_key,
unsigned char *pub_key, unsigned char *priv_key) {
EVP_PKEY_CTX *pctx = NULL;
EVP_PKEY *client_pkey = NULL, *server_pkey = NULL;
size_t shared_keylen = 0;
server_pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_X448, NULL, priv_key,
CURVE448_SIZE);
if (server_pkey == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error initializing Curve448 server key: %s",
proxy_ssh_crypto_get_errors());
return -1;
}
client_pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_X448, NULL, pub_key,
CURVE448_SIZE);
if (client_pkey == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error initializing Curve448 client key: %s",
proxy_ssh_crypto_get_errors());
EVP_PKEY_free(server_pkey);
return -1;
}
pctx = EVP_PKEY_CTX_new(server_pkey, NULL);
if (pctx == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error initializing context for Curve448 shared key: %s",
proxy_ssh_crypto_get_errors());
EVP_PKEY_free(server_pkey);
EVP_PKEY_free(client_pkey);
return -1;
}
if (EVP_PKEY_derive_init(pctx) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error preparing for Curve448 shared key: %s",
proxy_ssh_crypto_get_errors());
EVP_PKEY_CTX_free(pctx);
EVP_PKEY_free(server_pkey);
EVP_PKEY_free(client_pkey);
return -1;
}
if (EVP_PKEY_derive_set_peer(pctx, client_pkey) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error setting peer for Curve448 shared key: %s",
proxy_ssh_crypto_get_errors());
EVP_PKEY_CTX_free(pctx);
EVP_PKEY_free(server_pkey);
EVP_PKEY_free(client_pkey);
return -1;
}
shared_keylen = CURVE448_SIZE;
if (EVP_PKEY_derive(pctx, shared_key, &shared_keylen) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error generating Curve448 shared key: %s",
proxy_ssh_crypto_get_errors());
EVP_PKEY_CTX_free(pctx);
EVP_PKEY_free(server_pkey);
EVP_PKEY_free(client_pkey);
return -1;
}
if (shared_keylen != CURVE448_SIZE) {
pr_trace_msg(trace_channel, 1,
"generated Curve448 shared key length (%lu bytes) is not as expected "
"(%lu bytes)", (unsigned long) shared_keylen,
(unsigned long) CURVE448_SIZE);
}
EVP_PKEY_CTX_free(pctx);
EVP_PKEY_free(server_pkey);
EVP_PKEY_free(client_pkey);
return CURVE448_SIZE;
}
static const unsigned char *calculate_curve448_h(pool *p,
struct proxy_ssh_kex *kex,
const unsigned char *hostkey_data, uint32_t hostkey_datalen,
const BIGNUM *k, uint32_t *hlen) {
unsigned char *buf, *ptr;
uint32_t buflen, bufsz, len = 0;
bufsz = buflen = 4096;
/* XXX Is this buffer large enough? Too large? */
ptr = buf = palloc(p, bufsz);
/* Write all of the data into the buffer in the SSH2 format, and hash it.
* The ordering of these fields is described in RFC5656.
*/
/* First, the version strings */
len += proxy_ssh_msg_write_string(&buf, &buflen, kex->client_version);
len += proxy_ssh_msg_write_string(&buf, &buflen, kex->server_version);
/* Client's KEXINIT */
len += proxy_ssh_msg_write_int(&buf, &buflen,
kex->client_kexinit_payload_len + 1);
len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEXINIT);
len += proxy_ssh_msg_write_data(&buf, &buflen, kex->client_kexinit_payload,
kex->client_kexinit_payload_len, FALSE);
/* Server's KEXINIT */
len += proxy_ssh_msg_write_int(&buf, &buflen,
kex->server_kexinit_payload_len + 1);
len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEXINIT);
len += proxy_ssh_msg_write_data(&buf, &buflen, kex->server_kexinit_payload,
kex->server_kexinit_payload_len, FALSE);
/* Hostkey data */
len += proxy_ssh_msg_write_data(&buf, &buflen, hostkey_data, hostkey_datalen,
TRUE);
/* Client's key */
len += proxy_ssh_msg_write_data(&buf, &buflen, kex->client_curve448_pub_key,
CURVE448_SIZE, TRUE);
/* Server's key */
len += proxy_ssh_msg_write_data(&buf, &buflen, kex->server_curve448_pub_key,
CURVE448_SIZE, TRUE);
/* Shared secret */
len += proxy_ssh_msg_write_mpint(&buf, &buflen, k);
if (digest_data(kex, ptr, len, hlen) < 0) {
pr_memscrub(ptr, bufsz);
return NULL;
}
pr_memscrub(ptr, bufsz);
return kex_digest_buf;
}
static int create_curve448(struct proxy_ssh_kex *kex) {
kex->client_curve448_priv_key = palloc(kex_pool, CURVE448_SIZE);
kex->client_curve448_pub_key = palloc(kex_pool, CURVE448_SIZE);
return generate_curve448_keys(kex->client_curve448_priv_key,
kex->client_curve448_pub_key);
}
#endif /* HAVE_X448_OPENSSL and HAVE_SHA512_OPENSSL */
/* Given a name-list, return the first (i.e. preferred) name in the list. */
static const char *get_preferred_name(pool *p, const char *names) {
register unsigned int i;
/* Advance to the first comma, or NUL. */
for (i = 0; names[i] && names[i] != ','; i++);
if (names[i] == ',' ||
names[i] == '\0') {
char *pref;
pref = pcalloc(p, i + 1);
memcpy(pref, names, i);
return pref;
}
/* This should never happen. */
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to find preferred name in '%s'", names);
return NULL;
}
/* Note that in this default list of key exchange algorithms, one of the
* REQUIRED algorithms is conspicuously absent:
*
* diffie-hellman-group1-sha1
*
* This exchange has a weak hardcoded DH group, and will thus only be used
* if explicitly requested via ProxySFTPKeyExchanges, or if the AllowWeakDH
* SFTPOption is used.
*/
static const char *kex_exchanges[] = {
#if defined(HAVE_X448_OPENSSL) && defined(HAVE_SHA512_OPENSSL)
"curve448-sha512",
#endif /* HAVE_X448_OPENSSL and HAVE_SHA512_OPENSSL */
#if defined(PR_USE_SODIUM) && defined(HAVE_SHA256_OPENSSL)
"curve25519-sha256",
"curve25519-sha256@libssh.org",
#endif /* PR_USE_SODIUM and HAVE_SHA256_OPENSSL */
#if defined(PR_USE_OPENSSL_ECC)
"ecdh-sha2-nistp521",
"ecdh-sha2-nistp384",
"ecdh-sha2-nistp256",
#endif /* PR_USE_OPENSSL_ECC */
#if (OPENSSL_VERSION_NUMBER > 0x000907000L && defined(OPENSSL_FIPS)) || \
(OPENSSL_VERSION_NUMBER > 0x000908000L)
# if defined(HAVE_SHA512_OPENSSL)
"diffie-hellman-group18-sha512",
"diffie-hellman-group16-sha512",
# endif /* HAVE_SHA512_OPENSSL */
# if defined(HAVE_SHA256_OPENSSL)
"diffie-hellman-group14-sha256",
"diffie-hellman-group-exchange-sha256",
# endif /* HAVE_SHA256_OPENSSL */
#endif
"diffie-hellman-group-exchange-sha1",
"diffie-hellman-group14-sha1",
#if 0
/* We cannot currently support rsa2048-sha256, since it requires support
* for PKCS#1 v2.1 (RFC3447). OpenSSL only supports PKCS#1 v2.0 (RFC2437)
* at present, which only allows EME-OAEP using SHA1. v2.1 allows for
* using other message digests, e.g. SHA256, for EME-OAEP.
*/
#if ((OPENSSL_VERSION_NUMBER > 0x000907000L && defined(OPENSSL_FIPS)) || \
(OPENSSL_VERSION_NUMBER > 0x000908000L)) && \
defined(HAVE_SHA256_OPENSSL)
"rsa2048-sha256",
#endif
#endif
"rsa1024-sha1",
NULL,
};
static const char *get_kexinit_exchange_list(pool *p) {
char *res = "";
config_rec *c;
c = find_config(main_server->conf, CONF_PARAM, "ProxySFTPKeyExchanges",
FALSE);
if (c != NULL) {
res = pstrdup(p, c->argv[0]);
} else {
register unsigned int i;
for (i = 0; kex_exchanges[i]; i++) {
res = pstrcat(p, res, *res ? "," : "", pstrdup(p, kex_exchanges[i]),
NULL);
}
if (proxy_opts & PROXY_OPT_SSH_ALLOW_WEAK_DH) {
/* The hardcoded group for this exchange is rather weak in the face of
* the "Logjam" vulnerability (see https://weakdh.org). Thus it is
* only appended to the end of the default exchanges if the AllowWeakDH
* SFTPOption is in effect.
*/
res = pstrcat(p, res, ",", pstrdup(p, "diffie-hellman-group1-sha1"),
NULL);
}
}
if (!(proxy_opts & PROXY_OPT_SSH_NO_EXT_INFO)) {
/* Indicate support for RFC 8308's extension negotiation mechanism. */
res = pstrcat(p, res, *res ? "," : "", pstrdup(p, "ext-info-c"), NULL);
}
if (!(proxy_opts & PROXY_OPT_SSH_NO_STRICT_KEX)) {
/* Indicate support for OpenSSH's custom "strict KEX" mode extension,
* but only if we have not done/completed our first KEX.
*/
if (kex_done_first_kex == FALSE) {
res = pstrcat(p, res, *res ? "," : "",
pstrdup(p, "kex-strict-c-v00@openssh.com"), NULL);
}
}
return res;
}
static const char *get_kexinit_hostkey_algo_list(pool *p) {
char *list = "";
/* Our list of supported hostkey algorithms depends on the hostkeys
* that have been configured. Show a preference for RSA over DSA,
* and ECDSA over both RSA and DSA, and ED25519/ED448 over all.
*
* XXX Should this be configurable later?
*/
#if defined(HAVE_X448_OPENSSL) && defined(HAVE_SHA512_OPENSSL)
list = pstrcat(p, list, *list ? "," : "", "ssh-ed448", NULL);
#endif /* HAVE_X448_OPENSSL and HAVE_SHA512_OPENSSL */
#if defined(PR_USE_SODIUM)
list = pstrcat(p, list, *list ? "," : "", "ssh-ed25519", NULL);
#endif /* PR_USE_SODIUM */
#if defined(PR_USE_OPENSSL_ECC)
list = pstrcat(p, list, *list ? "," : "", "ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521", NULL);
#endif /* PR_USE_OPENSSL_ECC */
#if defined(HAVE_SHA512_OPENSSL)
list = pstrcat(p, list, *list ? "," : "", "rsa-sha2-512", NULL);
#endif /* HAVE_SHA512_OPENSSL */
#if defined(HAVE_SHA256_OPENSSL)
list = pstrcat(p, list, *list ? "," : "", "rsa-sha2-256", NULL);
#endif /* HAVE_SHA256_OPENSSL */
list = pstrcat(p, list, *list ? "," : "", "ssh-rsa", NULL);
#if !defined(OPENSSL_NO_DSA)
list = pstrcat(p, list, *list ? "," : "", "ssh-dss", NULL);
#endif /* OPENSSL_NO_DSA */
return list;
}
static struct proxy_ssh_kex *create_kex(pool *p) {
struct proxy_ssh_kex *kex;
const char *list;
config_rec *c;
pool *tmp_pool;
tmp_pool = make_sub_pool(p);
pr_pool_tag(tmp_pool, "Kex KEXINIT Pool");
kex = pcalloc(tmp_pool, sizeof(struct proxy_ssh_kex));
kex->pool = tmp_pool;
kex->client_version = kex_client_version;
kex->server_version = kex_server_version;
kex->client_names = pcalloc(kex->pool, sizeof(struct proxy_ssh_kex_names));
kex->server_names = pcalloc(kex->pool, sizeof(struct proxy_ssh_kex_names));
kex->session_names = pcalloc(kex->pool, sizeof(struct proxy_ssh_kex_names));
kex->use_hostkey_type = PROXY_SSH_KEY_UNKNOWN;
kex->dh = NULL;
kex->e = NULL;
kex->hash = NULL;
kex->k = NULL;
kex->h = NULL;
kex->hlen = 0;
kex->dh_gex_min = kex->dh_gex_pref = kex->dh_gex_max = 0;
kex->rsa = NULL;
kex->rsa_encrypted = NULL;
kex->rsa_encrypted_len = 0;
list = get_kexinit_exchange_list(kex->pool);
kex->client_names->kex_algo = list;
list = get_kexinit_hostkey_algo_list(kex->pool);
kex->client_names->server_hostkey_algo = list;
list = proxy_ssh_crypto_get_kexinit_cipher_list(kex->pool);
kex->client_names->c2s_encrypt_algo = list;
kex->client_names->s2c_encrypt_algo = list;
list = proxy_ssh_crypto_get_kexinit_digest_list(kex->pool);
kex->client_names->c2s_mac_algo = list;
kex->client_names->s2c_mac_algo = list;
c = find_config(main_server->conf, CONF_PARAM, "ProxySFTPCompression", FALSE);
if (c != NULL) {
int comp_mode;
comp_mode = *((int *) c->argv[0]);
switch (comp_mode) {
case 2:
/* Advertise that we support OpenSSH's "delayed" compression mode. */
kex->client_names->c2s_comp_algo = "zlib@openssh.com,zlib,none";
kex->client_names->s2c_comp_algo = "zlib@openssh.com,zlib,none";
break;
case 1:
kex->client_names->c2s_comp_algo = "zlib,none";
kex->client_names->s2c_comp_algo = "zlib,none";
break;
default:
kex->client_names->c2s_comp_algo = "none";
kex->client_names->s2c_comp_algo = "none";
break;
}
} else {
kex->client_names->c2s_comp_algo = "none";
kex->client_names->s2c_comp_algo = "none";
}
kex->client_names->c2s_lang = "";
kex->client_names->s2c_lang = "";
return kex;
}
static void destroy_kex(struct proxy_ssh_kex *kex) {
if (kex != NULL) {
if (kex->dh != NULL) {
#if OPENSSL_VERSION_NUMBER < 0x10100000L
if (kex->dh->p != NULL) {
BN_clear_free(kex->dh->p);
kex->dh->p = NULL;
}
if (kex->dh->g != NULL) {
BN_clear_free(kex->dh->g);
kex->dh->g = NULL;
}
#endif /* prior to OpenSSL-1.1.0 */
DH_free(kex->dh);
kex->dh = NULL;
}
if (kex->rsa != NULL) {
RSA_free(kex->rsa);
kex->rsa = NULL;
}
if (kex->rsa_encrypted != NULL) {
pr_memscrub(kex->rsa_encrypted, kex->rsa_encrypted_len);
kex->rsa_encrypted = NULL;
kex->rsa_encrypted_len = 0;
}
if (kex->e != NULL) {
BN_clear_free((BIGNUM *) kex->e);
kex->e = NULL;
}
if (kex->k != NULL) {
BN_clear_free((BIGNUM *) kex->k);
kex->k = NULL;
}
if (kex->hlen > 0) {
pr_memscrub((char *) kex->h, kex->hlen);
kex->hlen = 0;
}
#if defined(PR_USE_OPENSSL_ECC)
if (kex->ec != NULL) {
EC_KEY_free(kex->ec);
kex->ec = NULL;
}
if (kex->server_point != NULL) {
EC_POINT_free(kex->server_point);
kex->server_point = NULL;
}
#endif /* PR_USE_OPENSSL_ECC */
#if defined(PR_USE_SODIUM) && defined(HAVE_SHA256_OPENSSL)
if (kex->client_curve25519_priv_key != NULL) {
pr_memscrub(kex->client_curve25519_priv_key, CURVE25519_SIZE);
kex->client_curve25519_priv_key = NULL;
}
if (kex->client_curve25519_pub_key != NULL) {
pr_memscrub(kex->client_curve25519_pub_key, CURVE25519_SIZE);
kex->client_curve25519_pub_key = NULL;
}
if (kex->server_curve25519_pub_key != NULL) {
pr_memscrub(kex->server_curve25519_pub_key, CURVE25519_SIZE);
kex->server_curve25519_pub_key = NULL;
}
#endif /* PR_USE_SODIUM and HAVE_SHA256_OPENSSL */
#if defined(HAVE_X448_OPENSSL) && defined(HAVE_SHA512_OPENSSL)
if (kex->client_curve448_priv_key != NULL) {
pr_memscrub(kex->client_curve448_priv_key, CURVE448_SIZE);
kex->client_curve448_priv_key = NULL;
}
if (kex->client_curve448_pub_key != NULL) {
pr_memscrub(kex->client_curve448_pub_key, CURVE448_SIZE);
kex->client_curve448_pub_key = NULL;
}
if (kex->server_curve448_pub_key != NULL) {
pr_memscrub(kex->server_curve448_pub_key, CURVE448_SIZE);
kex->server_curve448_pub_key = NULL;
}
#endif /* HAVE_X448_OPENSSL and HAVE_SHA512_OPENSSL */
if (kex->pool != NULL) {
destroy_pool(kex->pool);
}
}
kex_first_kex = kex_rekey_kex = NULL;
}
static int setup_kex_algo(struct proxy_ssh_kex *kex, const char *algo) {
if (strcmp(algo, "diffie-hellman-group1-sha1") == 0) {
if (create_dh(kex, PROXY_SSH_DH_GROUP1_SHA1) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error using '%s' as the key exchange algorithm: %s", algo,
strerror(errno));
return -1;
}
kex->session_names->kex_algo = algo;
return 0;
}
if (strcmp(algo, "diffie-hellman-group14-sha1") == 0) {
if (create_dh(kex, PROXY_SSH_DH_GROUP14_SHA1) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error using '%s' as the key exchange algorithm: %s", algo,
strerror(errno));
return -1;
}
kex->session_names->kex_algo = algo;
return 0;
}
if (strcmp(algo, "diffie-hellman-group14-sha256") == 0) {
if (create_dh(kex, PROXY_SSH_DH_GROUP14_SHA256) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error using '%s' as the key exchange algorithm: %s", algo,
strerror(errno));
return -1;
}
kex->session_names->kex_algo = algo;
return 0;
}
if (strcmp(algo, "diffie-hellman-group16-sha512") == 0) {
if (create_dh(kex, PROXY_SSH_DH_GROUP16_SHA512) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error using '%s' as the key exchange algorithm: %s", algo,
strerror(errno));
return -1;
}
kex->session_names->kex_algo = algo;
return 0;
}
if (strcmp(algo, "diffie-hellman-group18-sha512") == 0) {
if (create_dh(kex, PROXY_SSH_DH_GROUP18_SHA512) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error using '%s' as the key exchange algorithm: %s", algo,
strerror(errno));
return -1;
}
kex->session_names->kex_algo = algo;
return 0;
}
if (strcmp(algo, "diffie-hellman-group-exchange-sha1") == 0) {
if (prepare_dh(kex, PROXY_SSH_DH_GEX_SHA1) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error using '%s' as the key exchange algorithm: %s", algo,
strerror(errno));
return -1;
}
kex->session_names->kex_algo = algo;
kex->use_gex = TRUE;
return 0;
}
if (strcmp(algo, "rsa1024-sha1") == 0) {
if (create_kexrsa(kex, PROXY_SSH_KEXRSA_SHA1) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error using '%s' as the key exchange algorithm: %s", algo,
strerror(errno));
return -1;
}
kex->session_names->kex_algo = algo;
kex->use_kexrsa = TRUE;
return 0;
}
#if ((OPENSSL_VERSION_NUMBER > 0x000907000L && defined(OPENSSL_FIPS)) || \
(OPENSSL_VERSION_NUMBER > 0x000908000L)) && \
defined(HAVE_SHA256_OPENSSL)
if (strcmp(algo, "diffie-hellman-group-exchange-sha256") == 0) {
if (prepare_dh(kex, PROXY_SSH_DH_GEX_SHA256) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error using '%s' as the key exchange algorithm: %s", algo,
strerror(errno));
return -1;
}
kex->session_names->kex_algo = algo;
kex->use_gex = TRUE;
return 0;
}
if (strcmp(algo, "rsa2048-sha256") == 0) {
if (create_kexrsa(kex, PROXY_SSH_KEXRSA_SHA256) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error using '%s' as the key exchange algorithm: %s", algo,
strerror(errno));
return -1;
}
kex->session_names->kex_algo = algo;
kex->use_kexrsa = TRUE;
return 0;
}
#endif
#if defined(PR_USE_OPENSSL_ECC)
if (strcmp(algo, "ecdh-sha2-nistp256") == 0) {
if (create_ecdh(kex, PROXY_SSH_ECDH_SHA256) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error using '%s' as the key exchange algorithm: %s", algo,
strerror(errno));
return -1;
}
kex->session_names->kex_algo = algo;
kex->use_ecdh = TRUE;
return 0;
}
if (strcmp(algo, "ecdh-sha2-nistp384") == 0) {
if (create_ecdh(kex, PROXY_SSH_ECDH_SHA384) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error using '%s' as the key exchange algorithm: %s", algo,
strerror(errno));
return -1;
}
kex->session_names->kex_algo = algo;
kex->use_ecdh = TRUE;
return 0;
}
if (strcmp(algo, "ecdh-sha2-nistp521") == 0) {
if (create_ecdh(kex, PROXY_SSH_ECDH_SHA512) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error using '%s' as the key exchange algorithm: %s", algo,
strerror(errno));
return -1;
}
kex->session_names->kex_algo = algo;
kex->use_ecdh = TRUE;
return 0;
}
#endif /* PR_USE_OPENSSL_ECC */
#if defined(PR_USE_SODIUM) && defined(HAVE_SHA256_OPENSSL)
if (strcmp(algo, "curve25519-sha256") == 0 ||
strcmp(algo, "curve25519-sha256@libssh.org") == 0) {
if (create_curve25519(kex) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error using '%s' as the key exchange algorithm: %s", algo,
strerror(errno));
return -1;
}
kex->hash = EVP_sha256();
kex->session_names->kex_algo = algo;
kex->use_curve25519 = TRUE;
return 0;
}
#endif /* PR_USE_SODIUM and HAVE_SHA256_OPENSSL */
#if defined(HAVE_X448_OPENSSL) && defined(HAVE_SHA512_OPENSSL)
if (strcmp(algo, "curve448-sha512") == 0) {
if (create_curve448(kex) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error using '%s' as the key exchange algorithm: %s", algo,
strerror(errno));
return -1;
}
kex->hash = EVP_sha512();
kex->session_names->kex_algo = algo;
kex->use_curve448 = TRUE;
return 0;
}
#endif /* HAVE_X448_OPENSSL and HAVE_SHA512_OPENSSL */
if (strcmp(algo, "ext-info-c") == 0 ||
strcmp(algo, "ext-info-s") == 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to use extension negotiation algorithm '%s' for key exchange",
algo);
errno = EINVAL;
return -1;
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unsupported key exchange algorithm '%s'", algo);
errno = EINVAL;
return -1;
}
static int setup_hostkey_algo(struct proxy_ssh_kex *kex, const char *algo) {
kex->session_names->server_hostkey_algo = (char *) algo;
if (strcmp(algo, "ssh-dss") == 0) {
kex->use_hostkey_type = PROXY_SSH_KEY_DSA;
return 0;
}
if (strcmp(algo, "ssh-rsa") == 0) {
kex->use_hostkey_type = PROXY_SSH_KEY_RSA;
return 0;
}
#if defined(HAVE_SHA256_OPENSSL)
if (strcmp(algo, "rsa-sha2-256") == 0) {
kex->use_hostkey_type = PROXY_SSH_KEY_RSA_SHA256;
return 0;
}
#endif /* HAVE_SHA256_OPENSSL */
#if defined(HAVE_SHA512_OPENSSL)
if (strcmp(algo, "rsa-sha2-512") == 0) {
kex->use_hostkey_type = PROXY_SSH_KEY_RSA_SHA512;
return 0;
}
#endif /* HAVE_SHA512_OPENSSL */
#if defined(PR_USE_OPENSSL_ECC)
if (strcmp(algo, "ecdsa-sha2-nistp256") == 0) {
kex->use_hostkey_type = PROXY_SSH_KEY_ECDSA_256;
return 0;
}
if (strcmp(algo, "ecdsa-sha2-nistp384") == 0) {
kex->use_hostkey_type = PROXY_SSH_KEY_ECDSA_384;
return 0;
}
if (strcmp(algo, "ecdsa-sha2-nistp521") == 0) {
kex->use_hostkey_type = PROXY_SSH_KEY_ECDSA_521;
return 0;
}
#endif /* PR_USE_OPENSSL_ECC */
#if defined(PR_USE_SODIUM)
if (strcmp(algo, "ssh-ed25519") == 0) {
kex->use_hostkey_type = PROXY_SSH_KEY_ED25519;
return 0;
}
#endif /* PR_USE_SODIUM */
#if defined(HAVE_X448_OPENSSL)
if (strcmp(algo, "ssh-ed448") == 0) {
kex->use_hostkey_type = PROXY_SSH_KEY_ED448;
return 0;
}
#endif /* HAVE_X448_OPENSSL */
errno = EINVAL;
return -1;
}
static int setup_c2s_encrypt_algo(struct proxy_ssh_kex *kex, const char *algo) {
if (proxy_ssh_cipher_set_read_algo(kex_pool, algo) < 0) {
return -1;
}
kex->session_names->c2s_encrypt_algo = algo;
return 0;
}
static int setup_s2c_encrypt_algo(struct proxy_ssh_kex *kex, const char *algo) {
if (proxy_ssh_cipher_set_write_algo(kex_pool, algo) < 0) {
return -1;
}
kex->session_names->s2c_encrypt_algo = algo;
return 0;
}
static int setup_c2s_mac_algo(struct proxy_ssh_kex *kex, const char *algo) {
if (proxy_ssh_mac_set_read_algo(kex_pool, algo) < 0) {
return -1;
}
kex->session_names->c2s_mac_algo = algo;
return 0;
}
static int setup_s2c_mac_algo(struct proxy_ssh_kex *kex, const char *algo) {
if (proxy_ssh_mac_set_write_algo(kex_pool, algo) < 0) {
return -1;
}
kex->session_names->s2c_mac_algo = algo;
return 0;
}
static int setup_c2s_comp_algo(struct proxy_ssh_kex *kex, const char *algo) {
if (proxy_ssh_compress_set_read_algo(kex_pool, algo) < 0) {
return -1;
}
kex->session_names->c2s_comp_algo = algo;
return 0;
}
static int setup_s2c_comp_algo(struct proxy_ssh_kex *kex, const char *algo) {
if (proxy_ssh_compress_set_write_algo(kex_pool, algo) < 0) {
return -1;
}
kex->session_names->s2c_comp_algo = algo;
return 0;
}
static int setup_c2s_lang(struct proxy_ssh_kex *kex, const char *lang) {
/* XXX Need to implement the functionality here. */
kex->session_names->c2s_lang = lang;
return 0;
}
static int setup_s2c_lang(struct proxy_ssh_kex *kex, const char *lang) {
/* XXX Need to implement the functionality here. */
kex->session_names->s2c_lang = lang;
return 0;
}
static int get_session_names(struct proxy_ssh_kex *kex, int *correct_guess) {
const char *kex_algo, *shared, *client_list, *server_list;
const char *client_pref, *server_pref;
pool *tmp_pool;
tmp_pool = make_sub_pool(kex->pool);
pr_pool_tag(tmp_pool, "Proxy SSH session shared name pool");
client_list = kex->client_names->kex_algo;
server_list = kex->server_names->kex_algo;
pr_trace_msg(trace_channel, 8, "client-sent key exchange algorithms: %s",
client_list);
pr_trace_msg(trace_channel, 8, "server-sent key exchange algorithms: %s",
server_list);
client_pref = get_preferred_name(tmp_pool, client_list);
server_pref = get_preferred_name(tmp_pool, server_list);
/* Did the client correctly guess at the key exchange algorithm that
* we would list first in our server list, if it says it sent
* a guess KEX packet?
*/
if (kex->first_kex_follows == TRUE &&
*correct_guess == TRUE &&
client_pref != NULL &&
server_pref != NULL) {
if (strcmp(client_pref, server_pref) != 0) {
*correct_guess = FALSE;
pr_trace_msg(trace_channel, 7,
"client incorrectly guessed key exchange algorithm '%s'", client_pref);
} else {
pr_trace_msg(trace_channel, 7,
"client correctly guessed key exchange algorithm '%s'", server_pref);
}
}
kex_algo = proxy_ssh_misc_namelist_shared(kex->pool, client_list,
server_list);
if (kex_algo != NULL) {
/* Unlike the following algorithms, we wait to setup the chosen kex algo
* until the end. Why? The kex algo setup may require knowledge of the
* ciphers chosen for encryption, MAC, etc (Bug#4097).
*/
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
" + Session key exchange: %s", kex_algo);
pr_trace_msg(trace_channel, 20, "session key exchange algorithm: %s",
kex_algo);
/* Did the server indicate EXT_INFO support */
kex->use_ext_info = proxy_ssh_misc_namelist_contains(kex->pool, server_list,
"ext-info-s");
pr_trace_msg(trace_channel, 20, "server %s EXT_INFO support",
kex->use_ext_info ? "signaled" : "did not signal" );
if (!(proxy_opts & PROXY_OPT_SSH_NO_STRICT_KEX)) {
/* Did the server indicate "strict kex" support (Issue 257)?
*
* Note that we only check for this if it is our first KEXINIT.
* The "strict kex" extension is ignored in any subsequent KEXINITs, as
* for rekeys.
*/
if (kex_done_first_kex == FALSE) {
use_strict_kex = proxy_ssh_misc_namelist_contains(kex->pool,
server_list, "kex-strict-s-v00@openssh.com");
pr_trace_msg(trace_channel, 20, "server %s strict KEX support",
use_strict_kex ? "signaled" : "did not signal" );
}
}
} else {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"no shared key exchange algorithm found (client sent '%s', server sent "
"'%s')", client_list, server_list);
destroy_pool(tmp_pool);
return -1;
}
client_list = kex->client_names->server_hostkey_algo;
server_list = kex->server_names->server_hostkey_algo;
pr_trace_msg(trace_channel, 8,
"client-sent host key algorithms: %s", client_list);
pr_trace_msg(trace_channel, 8,
"server-sent host key algorithms: %s", server_list);
shared = proxy_ssh_misc_namelist_shared(kex->pool, client_list, server_list);
if (shared != NULL) {
if (setup_hostkey_algo(kex, shared) < 0) {
destroy_pool(tmp_pool);
return -1;
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
" + Session server hostkey: %s", shared);
pr_trace_msg(trace_channel, 20, "session server hostkey algorithm: %s",
shared);
} else {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"no shared server hostkey algorithm found (client sent '%s', server sent "
"'%s'", client_list, server_list);
destroy_pool(tmp_pool);
return -1;
}
client_list = kex->client_names->c2s_encrypt_algo;
server_list = kex->server_names->c2s_encrypt_algo;
pr_trace_msg(trace_channel, 8, "client-sent client encryption algorithms: %s",
client_list);
pr_trace_msg(trace_channel, 8, "server-sent client encryption algorithms: %s",
server_list);
shared = proxy_ssh_misc_namelist_shared(kex->pool, client_list, server_list);
if (shared != NULL) {
if (setup_c2s_encrypt_algo(kex, shared) < 0) {
destroy_pool(tmp_pool);
return -1;
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
" + Session client-to-server encryption: %s", shared);
pr_trace_msg(trace_channel, 20,
"session client-to-server encryption algorithm: %s", shared);
} else {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"no shared client-to-server encryption algorithm found (client sent '%s',"
" server sent '%s')", client_list, server_list);
destroy_pool(tmp_pool);
return -1;
}
client_list = kex->client_names->s2c_encrypt_algo;
server_list = kex->server_names->s2c_encrypt_algo;
pr_trace_msg(trace_channel, 8, "client-sent server encryption algorithms: %s",
client_list);
pr_trace_msg(trace_channel, 8, "server-sent server encryption algorithms: %s",
server_list);
shared = proxy_ssh_misc_namelist_shared(kex->pool, client_list, server_list);
if (shared != NULL) {
if (setup_s2c_encrypt_algo(kex, shared) < 0) {
destroy_pool(tmp_pool);
return -1;
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
" + Session server-to-client encryption: %s", shared);
pr_trace_msg(trace_channel, 20,
"session server-to-client encryption algorithm: %s", shared);
} else {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"no shared server-to-client encryption algorithm found (client sent '%s',"
" server sent '%s')", client_list, server_list);
destroy_pool(tmp_pool);
return -1;
}
client_list = kex->client_names->c2s_mac_algo;
server_list = kex->server_names->c2s_mac_algo;
pr_trace_msg(trace_channel, 8, "client-sent client MAC algorithms: %s",
client_list);
pr_trace_msg(trace_channel, 8, "server-sent client MAC algorithms: %s",
server_list);
/* Ignore MAC/digests when authenticated encryption algorithms are used. */
if (proxy_ssh_cipher_get_read_auth_size2() == 0) {
shared = proxy_ssh_misc_namelist_shared(kex->pool, client_list,
server_list);
if (shared != NULL) {
if (setup_c2s_mac_algo(kex, shared) < 0) {
destroy_pool(tmp_pool);
return -1;
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
" + Session client-to-server MAC: %s", shared);
pr_trace_msg(trace_channel, 20,
"session client-to-server MAC algorithm: %s", shared);
} else {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"no shared client-to-server MAC algorithm found (client sent '%s', "
"server sent '%s')", client_list, server_list);
destroy_pool(tmp_pool);
return -1;
}
} else {
pr_trace_msg(trace_channel, 8, "ignoring MAC algorithms due to use of "
"client-to-server authenticated cipher algorithm '%s'",
kex->session_names->c2s_encrypt_algo);
pr_trace_msg(trace_channel, 20,
"session client-to-server MAC algorithm: ");
}
client_list = kex->client_names->s2c_mac_algo;
server_list = kex->server_names->s2c_mac_algo;
pr_trace_msg(trace_channel, 8, "client-sent server MAC algorithms: %s",
client_list);
pr_trace_msg(trace_channel, 8, "server-sent server MAC algorithms: %s",
server_list);
/* Ignore MAC/digests when authenticated encryption algorithms are used. */
if (proxy_ssh_cipher_get_write_auth_size2() == 0) {
shared = proxy_ssh_misc_namelist_shared(kex->pool, client_list,
server_list);
if (shared != NULL) {
if (setup_s2c_mac_algo(kex, shared) < 0) {
destroy_pool(tmp_pool);
return -1;
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
" + Session server-to-client MAC: %s", shared);
pr_trace_msg(trace_channel, 20,
"session server-to-client MAC algorithm: %s", shared);
} else {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"no shared server-to-client MAC algorithm found (client sent '%s', "
"server sent '%s')", client_list, server_list);
destroy_pool(tmp_pool);
return -1;
}
} else {
pr_trace_msg(trace_channel, 8, "ignoring MAC algorithms due to use of "
"server-to-client authenticated cipher algorithm '%s'",
kex->session_names->s2c_encrypt_algo);
pr_trace_msg(trace_channel, 20,
"session server-to-client MAC algorithm: ");
}
client_list = kex->client_names->c2s_comp_algo;
server_list = kex->server_names->c2s_comp_algo;
pr_trace_msg(trace_channel, 8,
"client-sent client compression algorithms: %s", client_list);
pr_trace_msg(trace_channel, 8,
"server-sent client compression algorithms: %s", server_list);
shared = proxy_ssh_misc_namelist_shared(kex->pool, client_list, server_list);
if (shared != NULL) {
if (setup_c2s_comp_algo(kex, shared) < 0) {
destroy_pool(tmp_pool);
return -1;
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
" + Session client-to-server compression: %s", shared);
pr_trace_msg(trace_channel, 20,
"session client-to-server compression algorithm: %s", shared);
} else {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"no shared client-to-server compression algorithm found (client sent "
"'%s', server sent '%s'", client_list, server_list);
destroy_pool(tmp_pool);
return -1;
}
client_list = kex->client_names->s2c_comp_algo;
server_list = kex->server_names->s2c_comp_algo;
pr_trace_msg(trace_channel, 8,
"client-sent server compression algorithms: %s", client_list);
pr_trace_msg(trace_channel, 8,
"server-sent server compression algorithms: %s", server_list);
shared = proxy_ssh_misc_namelist_shared(kex->pool, client_list, server_list);
if (shared != NULL) {
if (setup_s2c_comp_algo(kex, shared) < 0) {
destroy_pool(tmp_pool);
return -1;
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
" + Session server-to-client compression: %s", shared);
pr_trace_msg(trace_channel, 20,
"session server-to-client compression algorithm: %s", shared);
} else {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"no shared server-to-client compression algorithm found (client sent "
"'%s', server sent '%s'", client_list, server_list);
destroy_pool(tmp_pool);
return -1;
}
client_list = kex->client_names->c2s_lang;
server_list = kex->server_names->c2s_lang;
pr_trace_msg(trace_channel, 8,
"client-sent client languages: %s", client_list);
pr_trace_msg(trace_channel, 8,
"server-sent client languages: %s", client_list);
shared = proxy_ssh_misc_namelist_shared(kex->pool, client_list, server_list);
if (shared != NULL) {
if (setup_c2s_lang(kex, shared) < 0) {
destroy_pool(tmp_pool);
return -1;
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
" + Session client-to-server language: %s", shared);
pr_trace_msg(trace_channel, 20,
"session client-to-server language: %s", shared);
/* Currently ignore any lack of shared languages. */
}
client_list = kex->client_names->s2c_lang;
server_list = kex->server_names->s2c_lang;
pr_trace_msg(trace_channel, 8,
"client-sent server languages: %s", client_list);
pr_trace_msg(trace_channel, 8,
"server-sent server languages: %s", client_list);
shared = proxy_ssh_misc_namelist_shared(kex->pool, client_list, server_list);
if (shared != NULL) {
if (setup_s2c_lang(kex, shared) < 0) {
destroy_pool(tmp_pool);
return -1;
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
" + Session server-to-client language: %s", shared);
pr_trace_msg(trace_channel, 20,
"session server-to-client language: %s", shared);
/* Currently ignore any lack of shared languages. */
}
/* Now that we've finished setting up the other bits, we can set up the
* kex algo.
*/
if (setup_kex_algo(kex, kex_algo) < 0) {
destroy_pool(tmp_pool);
return -1;
}
destroy_pool(tmp_pool);
return 0;
}
static int read_kexinit(struct proxy_ssh_packet *pkt,
struct proxy_ssh_kex *kex) {
unsigned char *buf;
unsigned char *cookie;
char *list;
uint32_t buflen, reserved;
buf = pkt->payload;
buflen = pkt->payload_len;
/* Make a copy of the payload for later. */
kex->server_kexinit_payload = palloc(kex->pool, pkt->payload_len);
kex->server_kexinit_payload_len = pkt->payload_len;
memcpy(kex->server_kexinit_payload, pkt->payload, pkt->payload_len);
/* Read the cookie, which is a mandated length of 16 bytes. */
proxy_ssh_msg_read_data(pkt->pool, &buf, &buflen, 16, &cookie);
proxy_ssh_msg_read_string(kex->pool, &buf, &buflen, &list);
kex->server_names->kex_algo = list;
proxy_ssh_msg_read_string(kex->pool, &buf, &buflen, &list);
kex->server_names->server_hostkey_algo = list;
proxy_ssh_msg_read_string(kex->pool, &buf, &buflen, &list);
kex->server_names->c2s_encrypt_algo = list;
proxy_ssh_msg_read_string(kex->pool, &buf, &buflen, &list);
kex->server_names->s2c_encrypt_algo = list;
proxy_ssh_msg_read_string(kex->pool, &buf, &buflen, &list);
kex->server_names->c2s_mac_algo = list;
proxy_ssh_msg_read_string(kex->pool, &buf, &buflen, &list);
kex->server_names->s2c_mac_algo = list;
proxy_ssh_msg_read_string(kex->pool, &buf, &buflen, &list);
kex->server_names->c2s_comp_algo = list;
proxy_ssh_msg_read_string(kex->pool, &buf, &buflen, &list);
kex->server_names->s2c_comp_algo = list;
/* Client-to-server languages */
proxy_ssh_msg_read_string(kex->pool, &buf, &buflen, &list);
kex->server_names->c2s_lang = list;
/* Server-to-client languages */
proxy_ssh_msg_read_string(kex->pool, &buf, &buflen, &list);
kex->server_names->s2c_lang = list;
/* Read the "first kex packet follows" byte */
proxy_ssh_msg_read_bool(pkt->pool, &buf, &buflen, &(kex->first_kex_follows));
pr_trace_msg(trace_channel, 3, "first kex packet follows = %s",
kex->first_kex_follows ? "true" : "false");
/* Reserved flags */
proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &reserved);
return 0;
}
static int write_kexinit(struct proxy_ssh_packet *pkt,
struct proxy_ssh_kex *kex) {
unsigned char cookie[16];
unsigned char *buf, *ptr;
const char *list;
uint32_t bufsz, buflen, len = 0;
/* XXX Always have empty language lists; we really don't care. */
const char *langs = "";
bufsz = buflen = sizeof(char) +
sizeof(cookie) +
sizeof(uint32_t) + strlen(kex->client_names->kex_algo) +
sizeof(uint32_t) + strlen(kex->client_names->server_hostkey_algo) +
sizeof(uint32_t) + strlen(kex->client_names->c2s_encrypt_algo) +
sizeof(uint32_t) + strlen(kex->client_names->s2c_encrypt_algo) +
sizeof(uint32_t) + strlen(kex->client_names->c2s_mac_algo) +
sizeof(uint32_t) + strlen(kex->client_names->s2c_mac_algo) +
sizeof(uint32_t) + strlen(kex->client_names->c2s_comp_algo) +
sizeof(uint32_t) + strlen(kex->client_names->s2c_comp_algo) +
sizeof(uint32_t) + strlen(langs) +
sizeof(uint32_t) + strlen(langs) +
sizeof(char) +
sizeof(uint32_t);
ptr = buf = pcalloc(pkt->pool, bufsz);
len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEXINIT);
/* Try first to use cryptographically secure bytes for the cookie.
* If that fails (e.g. if the PRNG hasn't been seeded well), use
* pseudo-cryptographically secure bytes.
*/
memset(cookie, 0, sizeof(cookie));
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
RAND_bytes(cookie, sizeof(cookie));
#else
if (RAND_bytes(cookie, sizeof(cookie)) != 1) {
RAND_pseudo_bytes(cookie, sizeof(cookie));
}
#endif /* prior to OpenSSL-1.1.0 */
len += proxy_ssh_msg_write_data(&buf, &buflen, cookie, sizeof(cookie), FALSE);
list = kex->client_names->kex_algo;
len += proxy_ssh_msg_write_string(&buf, &buflen, list);
list = kex->client_names->server_hostkey_algo;
len += proxy_ssh_msg_write_string(&buf, &buflen, list);
list = kex->client_names->c2s_encrypt_algo;
len += proxy_ssh_msg_write_string(&buf, &buflen, list);
list = kex->client_names->s2c_encrypt_algo;
len += proxy_ssh_msg_write_string(&buf, &buflen, list);
list = kex->client_names->c2s_mac_algo;
len += proxy_ssh_msg_write_string(&buf, &buflen, list);
list = kex->client_names->s2c_mac_algo;
len += proxy_ssh_msg_write_string(&buf, &buflen, list);
list = kex->client_names->c2s_comp_algo;
len += proxy_ssh_msg_write_string(&buf, &buflen, list);
list = kex->client_names->s2c_comp_algo;
len += proxy_ssh_msg_write_string(&buf, &buflen, list);
/* XXX Need to support langs here. */
len += proxy_ssh_msg_write_string(&buf, &buflen, langs);
len += proxy_ssh_msg_write_string(&buf, &buflen, langs);
/* We don't try to optimistically guess what algorithms the client would
* use and send a preemptive kex packet.
*/
len += proxy_ssh_msg_write_bool(&buf, &buflen, FALSE);
len += proxy_ssh_msg_write_int(&buf, &buflen, 0);
pkt->payload = ptr;
pkt->payload_len = len;
/* Make a copy of the payload for later. Skip past the first byte, which
* is the KEXINIT identifier.
*/
kex->client_kexinit_payload_len = pkt->payload_len - 1;
kex->client_kexinit_payload = palloc(kex->pool, pkt->payload_len - 1);
memcpy(kex->client_kexinit_payload, pkt->payload + 1, pkt->payload_len - 1);
return 0;
}
/* Only set the given environment variable/value IFF it is not already
* present.
*/
static void set_env_var(pool *p, const char *k, const char *v) {
const char *val;
int have_val = FALSE;
val = pr_env_get(p, k);
if (val != NULL) {
if (strcmp(val, v) == 0) {
have_val = TRUE;
}
}
if (have_val == FALSE) {
k = pstrdup(p, k);
v = pstrdup(p, v);
pr_env_unset(p, k);
pr_env_set(p, k, v);
}
}
static int set_session_keys(struct proxy_ssh_kex *kex) {
unsigned char *buf, *ptr;
uint32_t buflen, bufsz, klen;
int comp_read_flags, comp_write_flags;
/* To date, the kex algo that has generated the largest K that I have
* seen so far is "diffie-hellman-group18-sha512".
*/
bufsz = buflen = 2048;
ptr = buf = palloc(kex_pool, bufsz);
/* Need to use SSH2-style format of K for the key. */
klen = proxy_ssh_msg_write_mpint(&buf, &buflen, kex->k);
if (proxy_ssh_cipher_set_read_key(kex_pool, kex->hash, ptr, klen, kex->h,
kex->hlen, PROXY_SSH_ROLE_CLIENT) < 0) {
pr_memscrub(ptr, bufsz);
return -1;
}
if (proxy_ssh_cipher_set_write_key(kex_pool, kex->hash, ptr, klen, kex->h,
kex->hlen, PROXY_SSH_ROLE_CLIENT) < 0) {
pr_memscrub(ptr, bufsz);
return -1;
}
if (proxy_ssh_mac_set_read_key(kex_pool, kex->hash, ptr, klen, kex->h,
kex->hlen, PROXY_SSH_ROLE_CLIENT) < 0) {
pr_memscrub(ptr, bufsz);
return -1;
}
if (proxy_ssh_mac_set_write_key(kex_pool, kex->hash, ptr, klen, kex->h,
kex->hlen, PROXY_SSH_ROLE_CLIENT) < 0) {
pr_memscrub(ptr, bufsz);
return -1;
}
comp_read_flags = comp_write_flags = PROXY_SSH_COMPRESS_FL_NEW_KEY;
/* If we are rekeying, AND the existing compression is "delayed", then
* we need to use slightly different compression flags.
*/
if (kex_rekey_kex != NULL) {
const char *algo;
algo = proxy_ssh_compress_get_read_algo();
if (strcmp(algo, "zlib@openssh.com") == 0) {
comp_read_flags = PROXY_SSH_COMPRESS_FL_AUTHENTICATED;
}
algo = proxy_ssh_compress_get_write_algo();
if (strcmp(algo, "zlib@openssh.com") == 0) {
comp_write_flags = PROXY_SSH_COMPRESS_FL_AUTHENTICATED;
}
}
if (proxy_ssh_compress_init_read(comp_read_flags) < 0) {
pr_memscrub(ptr, bufsz);
return -1;
}
if (proxy_ssh_compress_init_write(comp_write_flags) < 0) {
pr_memscrub(ptr, bufsz);
return -1;
}
pr_memscrub(ptr, bufsz);
set_env_var(session.pool, "PROXY_SSH_CLIENT_CIPHER_ALGO",
proxy_ssh_cipher_get_write_algo());
set_env_var(session.pool, "PROXY_SSH_SERVER_CIPHER_ALGO",
proxy_ssh_cipher_get_read_algo());
if (proxy_ssh_cipher_get_read_auth_size2() == 0) {
set_env_var(session.pool, "PROXY_SSH_CLIENT_MAC_ALGO",
proxy_ssh_mac_get_write_algo());
} else {
set_env_var(session.pool, "PROXY_SSH_CLIENT_MAC_ALGO", "implicit");
}
if (proxy_ssh_cipher_get_write_auth_size2() == 0) {
set_env_var(session.pool, "PROXY_SSH_SERVER_MAC_ALGO",
proxy_ssh_mac_get_read_algo());
} else {
set_env_var(session.pool, "PROXY_SSH_SERVER_MAC_ALGO", "implicit");
}
set_env_var(session.pool, "PROXY_SSH_CLIENT_COMPRESSION_ALGO",
proxy_ssh_compress_get_write_algo());
set_env_var(session.pool, "PROXY_SSH_SERVER_COMPRESSION_ALGO",
proxy_ssh_compress_get_read_algo());
set_env_var(session.pool, "PROXY_SSH_KEX_ALGO",
kex->session_names->kex_algo);
if (kex_rekey_kex != NULL) {
pr_trace_msg(trace_channel, 3, "rekey KEX completed");
kex_rekey_kex = NULL;
}
return 0;
}
static int write_newkeys_reply(struct proxy_ssh_packet *pkt) {
unsigned char *buf, *ptr;
uint32_t bufsz, buflen, len = 0;
/* Write out the NEWKEYS message. */
bufsz = buflen = 1;
ptr = buf = palloc(pkt->pool, bufsz);
len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_NEWKEYS);
pkt->payload = ptr;
pkt->payload_len = len;
return 0;
}
static int handle_server_hostkey(pool *p,
enum proxy_ssh_key_type_e hostkey_type, unsigned char *hostkey_data,
uint32_t hostkey_datalen) {
unsigned int vhost_id;
const struct proxy_session *proxy_sess;
const char *backend_uri, *hostkey_algo, *stored_hostkey_algo = NULL;
const unsigned char *stored_hostkey_data = NULL;
uint32_t stored_hostkey_datalen = 0;
proxy_sess = pr_table_get(session.notes, "mod_proxy.proxy-session", NULL);
if (proxy_sess == NULL) {
/* Unlikely to occur. */
errno = EINVAL;
return -1;
}
backend_uri = proxy_conn_get_uri(proxy_sess->dst_pconn);
vhost_id = main_server->sid;
hostkey_algo = proxy_ssh_keys_get_key_type_desc(hostkey_type);
stored_hostkey_data = (kex_ds->hostkey_get)(p, kex_ds->dsh, vhost_id,
backend_uri, &stored_hostkey_algo, &stored_hostkey_datalen);
if (stored_hostkey_data == NULL) {
if (errno != ENOENT) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error retrieving stored hostkey for vhost ID %u, URI '%s': %s",
vhost_id, backend_uri, strerror(errno));
return 0;
}
pr_trace_msg(trace_channel, 18,
"no existing hostkey stored for vhost ID %u, URI '%s', "
"storing '%s' hostkey (%lu bytes)", vhost_id, backend_uri, hostkey_algo,
(unsigned long) hostkey_datalen);
if ((kex_ds->hostkey_add)(p, kex_ds->dsh, vhost_id, backend_uri,
hostkey_algo, hostkey_data, hostkey_datalen) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error adding '%s' hostkey for vhost ID %u, URI '%s': %s",
hostkey_algo, vhost_id, backend_uri, strerror(errno));
}
} else {
int verified = TRUE;
pr_trace_msg(trace_channel, 12,
"found stored '%s' hostkey (%lu bytes) for vhost ID %u, URI '%s'",
stored_hostkey_algo, (unsigned long) stored_hostkey_datalen, vhost_id,
backend_uri);
if (strcmp(hostkey_algo, stored_hostkey_algo) != 0) {
pr_trace_msg(trace_channel, 1,
"stored hostkey for vhost ID %u, URI '%s' uses different algorithm: "
"'%s' (stored), '%s' (current)", vhost_id, backend_uri,
stored_hostkey_algo, hostkey_algo);
verified = FALSE;
}
if (verified == TRUE &&
hostkey_datalen != stored_hostkey_datalen) {
pr_trace_msg(trace_channel, 1,
"stored hostkey for vhost ID %u, URI '%s' has different length: "
"%lu bytes (stored), %lu bytes (current)", vhost_id, backend_uri,
(unsigned long) stored_hostkey_datalen,
(unsigned long) hostkey_datalen);
verified = FALSE;
}
if (verified == TRUE &&
memcmp(hostkey_data, stored_hostkey_data, hostkey_datalen) != 0) {
pr_trace_msg(trace_channel, 1,
"stored hostkey for vhost ID %u, URI '%s' does not match current key",
vhost_id, backend_uri);
verified = FALSE;
}
if (verified == TRUE) {
pr_trace_msg(trace_channel, 18,
"stored hostkey matches current hostkey for vhost ID %u, URI '%s'",
vhost_id, backend_uri);
} else {
if (kex_verify_hostkeys == TRUE) {
/* TODO: This is where we would implement functionality similar to
* OpenSSH's UpdateHostKeys, via hostkey rotation extensions, where
* available.
*/
/* TODO: If we fail the KEX here, what recourse does the admin have?
* We currently are not providing a way to update/remove the offending
* stored hostkey in the SQLite database.
*
* For now, just loudly log the mismatch.
*/
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"stored hostkey does not match current hostkey "
"(vhost ID %u, URI '%s') and ProxySFTPVerifyServer is enabled",
vhost_id, backend_uri);
} else {
/* Replace the stored hostkey. */
pr_trace_msg(trace_channel, 10, "stored hostkey does not match current "
"hostkey (vhost ID %u, URI '%s') and ProxySFTPVerifyServer is "
"disabled, updating stored hostkey", vhost_id, backend_uri);
if ((kex_ds->hostkey_update)(p, kex_ds->dsh, vhost_id, backend_uri,
hostkey_algo, hostkey_data, hostkey_datalen) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error updating '%s' hostkey for vhost ID %u, URI '%s': %s",
hostkey_algo, vhost_id, backend_uri, strerror(errno));
}
}
}
}
return 0;
}
static struct proxy_ssh_packet *read_kex_packet(pool *p,
struct proxy_ssh_kex *kex, conn_t *conn, int disconn_code,
char *found_msg_type, unsigned int ntypes, ...) {
register unsigned int i;
va_list ap;
struct proxy_ssh_packet *pkt = NULL;
array_header *allowed_types;
pr_trace_msg(trace_channel, 9, "waiting for a message of %d %s from server",
ntypes, ntypes != 1 ? "types" : "type");
allowed_types = make_array(p, 1, sizeof(char));
va_start(ap, ntypes);
while (ntypes-- > 0) {
*((char *) push_array(allowed_types)) = va_arg(ap, int);
}
va_end(ap);
/* Keep looping until we get the desired message, or we time out (hopefully
* via TimeoutLogin or somesuch).
*/
while (pkt == NULL) {
int found = FALSE, res;
char msg_type;
pr_signals_handle();
pkt = proxy_ssh_packet_create(p);
res = proxy_ssh_packet_read(conn, pkt);
if (res < 0) {
int xerrno = errno;
destroy_kex(kex);
destroy_pool(pkt->pool);
errno = xerrno;
return NULL;
}
pr_response_clear(&resp_list);
pr_response_clear(&resp_err_list);
pr_response_set_pool(pkt->pool);
/* Per RFC 4253, Section 11, DEBUG, DISCONNECT, IGNORE, and UNIMPLEMENTED
* messages can occur at any time, even during KEX. We have to be prepared
* for this, and Do The Right Thing(tm).
*
* However, due to the Terrapin attack, if we are using a "strict KEX"
* mode, then only DISCONNECT messages can occur during KEX; DEBUG,
* IGNORE, and UNIMPLEMENTED messages will lead to disconnecting.
*/
msg_type = proxy_ssh_packet_get_msg_type(pkt);
for (i = 0; i < allowed_types->nelts; i++) {
if (msg_type == ((unsigned char *) allowed_types->elts)[i]) {
/* Exactly what we were looking for. Excellent. */
pr_trace_msg(trace_channel, 13,
"received expected %s message",
proxy_ssh_packet_get_msg_type_desc(msg_type));
if (found_msg_type != NULL) {
/* The caller wants to know the type of message we're returning;
* packet_get_msg_type() performs a destructive read.
*/
*found_msg_type = msg_type;
}
found = TRUE;
break;
}
}
if (found == TRUE) {
break;
}
switch (msg_type) {
/* DISCONNECT messages are always allowed. */
case PROXY_SSH_MSG_DISCONNECT:
proxy_ssh_packet_handle_disconnect(pkt);
pr_response_set_pool(NULL);
pkt = NULL;
break;
case PROXY_SSH_MSG_DEBUG:
if (use_strict_kex == FALSE) {
proxy_ssh_packet_handle_debug(pkt);
pr_response_set_pool(NULL);
pkt = NULL;
break;
}
case PROXY_SSH_MSG_IGNORE:
if (use_strict_kex == FALSE) {
proxy_ssh_packet_handle_ignore(pkt);
pr_response_set_pool(NULL);
pkt = NULL;
break;
}
case PROXY_SSH_MSG_UNIMPLEMENTED:
if (use_strict_kex == FALSE) {
proxy_ssh_packet_handle_unimplemented(pkt);
pr_response_set_pool(NULL);
pkt = NULL;
break;
}
default:
/* For any other message type, it's considered a protocol error. */
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"received %s (%d) unexpectedly%s, disconnecting",
proxy_ssh_packet_get_msg_type_desc(msg_type), msg_type,
use_strict_kex ? " during strict KEX" : "");
pr_response_set_pool(NULL);
destroy_kex(kex);
destroy_pool(pkt->pool);
PROXY_SSH_DISCONNECT_CONN(conn, disconn_code, NULL);
}
}
return pkt;
}
static int write_dh_init(struct proxy_ssh_packet *pkt,
struct proxy_ssh_kex *kex) {
unsigned char *buf, *ptr;
uint32_t buflen, bufsz, len = 0;
const BIGNUM *dh_pub_key;
/* In our DH_INIT, send 'e', our client DH public key. */
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
DH_get0_key(kex->dh, &dh_pub_key, NULL);
#else
dh_pub_key = kex->dh->pub_key;
#endif /* prior to OpenSSL-1.1.0 */
bufsz = buflen = 2048;
/* XXX Is this buffer large enough? Too large? */
ptr = buf = palloc(pkt->pool, bufsz);
len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEX_DH_INIT);
len += proxy_ssh_msg_write_mpint(&buf, &buflen, dh_pub_key);
pkt->payload = ptr;
pkt->payload_len = len;
return 0;
}
static int read_dh_reply(struct proxy_ssh_packet *pkt,
struct proxy_ssh_kex *kex) {
const unsigned char *h;
unsigned char *buf, *buf2, *server_hostkey_data = NULL, *sig = NULL;
uint32_t buflen, server_hostkey_datalen = 0, siglen = 0, hlen = 0;
const BIGNUM *server_pub_key = NULL, *k = NULL;
size_t dh_len = 0;
int res;
buf = pkt->payload;
buflen = pkt->payload_len;
/* See RFC 4253, Section 8 "Diffie-Hellman Key Exchange" */
proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &server_hostkey_datalen);
proxy_ssh_msg_read_data(pkt->pool, &buf, &buflen, server_hostkey_datalen,
&server_hostkey_data);
res = handle_server_hostkey(pkt->pool, kex->use_hostkey_type,
server_hostkey_data, server_hostkey_datalen);
if (res < 0) {
int xerrno;
xerrno = errno;
DH_free(kex->dh);
kex->dh = NULL;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error handling server host key: %s", strerror(xerrno));
errno = xerrno;
return -1;
}
proxy_ssh_msg_read_mpint(pkt->pool, &buf, &buflen, &server_pub_key);
if (have_good_dh(kex->dh, server_pub_key) < 0) {
DH_free(kex->dh);
kex->dh = NULL;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"invalid server public DH key");
return -1;
}
/* Compute the shared secret. */
dh_len = DH_size(kex->dh);
buf2 = palloc(pkt->pool, dh_len);
pr_trace_msg(trace_channel, 12, "computing DH key");
res = DH_compute_key(buf2, server_pub_key, kex->dh);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error computing DH shared secret: %s", proxy_ssh_crypto_get_errors());
DH_free(kex->dh);
kex->dh = NULL;
return -1;
}
k = BN_new();
if (BN_bin2bn(buf2, res, (BIGNUM *) k) == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error converting DH shared secret to BN: %s",
proxy_ssh_crypto_get_errors());
DH_free(kex->dh);
kex->dh = NULL;
return -1;
}
kex->k = k;
/* Calculate H */
h = calculate_h(pkt->pool, kex, server_hostkey_data, server_hostkey_datalen,
server_pub_key, k, &hlen);
if (h == NULL) {
BN_clear_free((BIGNUM *) kex->k);
kex->k = NULL;
DH_free(kex->dh);
kex->dh = NULL;
return -1;
}
proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &siglen);
proxy_ssh_msg_read_data(pkt->pool, &buf, &buflen, siglen, &sig);
/* Verify H */
res = verify_h(pkt->pool, kex, server_hostkey_data, server_hostkey_datalen,
sig, siglen, h, hlen);
if (res < 0) {
BN_clear_free((BIGNUM *) kex->k);
kex->k = NULL;
DH_free(kex->dh);
kex->dh = NULL;
return -1;
}
kex->h = palloc(kex->pool, hlen);
kex->hlen = hlen;
memcpy((char *) kex->h, h, kex->hlen);
/* Save H as the session ID. */
proxy_ssh_session_set_id(session.pool, h, hlen);
return 0;
}
static int handle_kex_dh(struct proxy_ssh_kex *kex, conn_t *conn) {
int res;
struct proxy_ssh_packet *pkt;
pr_trace_msg(trace_channel, 9, "writing DH_INIT message to server");
pkt = proxy_ssh_packet_create(kex_pool);
res = write_dh_init(pkt, kex);
if (res < 0) {
destroy_pool(pkt->pool);
PROXY_SSH_DISCONNECT_CONN(conn,
PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL);
}
res = proxy_ssh_packet_write(conn, pkt);
if (res < 0) {
destroy_pool(pkt->pool);
PROXY_SSH_DISCONNECT_CONN(conn,
PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL);
}
destroy_pool(pkt->pool);
pr_trace_msg(trace_channel, 9, "reading DH_REPLY message from server");
pkt = read_kex_packet(kex_pool, kex, conn,
PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL, 1,
PROXY_SSH_MSG_KEX_DH_REPLY);
res = read_dh_reply(pkt, kex);
if (res < 0) {
destroy_pool(pkt->pool);
PROXY_SSH_DISCONNECT_CONN(conn,
PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL);
}
destroy_pool(pkt->pool);
return 0;
}
/* Values from NIST Special Publication 800-57: Recommendation for Key
* Management Part 1 (rev 3) limited by the recommended maximum value from
* RFC 4419 section 3.
*/
static uint32_t estimate_dh(int nbits) {
if (nbits <= 112) {
return PROXY_SSH_DH_MIN_LEN;
}
if (nbits <= 128) {
return 3072;
}
if (nbits <= 192) {
return 7680;
}
return PROXY_SSH_DH_MAX_LEN;
}
static int write_dh_gex_request(struct proxy_ssh_packet *pkt,
struct proxy_ssh_kex *kex) {
unsigned char *buf, *ptr;
uint32_t buflen, bufsz, len = 0;
int dh_nbits = 0;
/* Estimate our desired DH size based on our negotiated ciphers. */
dh_nbits = get_dh_nbits(kex);
kex->dh_gex_pref = estimate_dh(dh_nbits);
if (kex->dh_gex_pref < PROXY_SSH_DH_MIN_LEN) {
kex->dh_gex_pref = PROXY_SSH_DH_MIN_LEN;
}
if (kex->dh_gex_pref > PROXY_SSH_DH_MAX_LEN) {
kex->dh_gex_pref = PROXY_SSH_DH_MAX_LEN;
}
/* XXX Is this buffer large enough? Too large? */
bufsz = buflen = 2048;
ptr = buf = palloc(pkt->pool, bufsz);
if (proxy_ssh_interop_supports_feature(PROXY_SSH_FEAT_DH_NEW_GEX) == TRUE) {
kex->dh_gex_min = PROXY_SSH_DH_MIN_LEN;
kex->dh_gex_max = PROXY_SSH_DH_MAX_LEN;
len += proxy_ssh_msg_write_byte(&buf, &buflen,
PROXY_SSH_MSG_KEX_DH_GEX_REQUEST);
len += proxy_ssh_msg_write_int(&buf, &buflen, kex->dh_gex_min);
len += proxy_ssh_msg_write_int(&buf, &buflen, kex->dh_gex_pref);
len += proxy_ssh_msg_write_int(&buf, &buflen, kex->dh_gex_max);
} else {
len += proxy_ssh_msg_write_byte(&buf, &buflen,
PROXY_SSH_MSG_KEX_DH_GEX_REQUEST_OLD);
len += proxy_ssh_msg_write_int(&buf, &buflen, kex->dh_gex_pref);
}
pkt->payload = ptr;
pkt->payload_len = len;
return 0;
}
static int create_gex_dh(struct proxy_ssh_kex *kex, const BIGNUM *dh_p,
const BIGNUM *dh_g) {
unsigned int attempts = 0;
int dh_nbits;
DH *dh;
if (kex->dh != NULL) {
#if OPENSSL_VERSION_NUMBER < 0x10100000L
if (kex->dh->p != NULL) {
BN_clear_free(kex->dh->p);
kex->dh->p = NULL;
}
if (kex->dh->g != NULL) {
BN_clear_free(kex->dh->g);
kex->dh->g = NULL;
}
if (kex->dh->priv_key != NULL) {
BN_clear_free(kex->dh->priv_key);
kex->dh->priv_key = NULL;
}
if (kex->dh->pub_key != NULL) {
BN_clear_free(kex->dh->pub_key);
kex->dh->pub_key = NULL;
}
#endif /* prior to OpenSSL-1.1.0 */
DH_free(kex->dh);
kex->dh = NULL;
}
dh_nbits = get_dh_nbits(kex);
/* We have 10 attempts to make a DH key which passes muster. */
while (attempts <= 10) {
const BIGNUM *dh_pub_key = NULL, *dh_priv_key = NULL;
pr_signals_handle();
attempts++;
pr_trace_msg(trace_channel, 9, "attempt #%u to create a good DH key",
attempts);
dh = DH_new();
if (dh == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error creating DH: %s", proxy_ssh_crypto_get_errors());
return -1;
}
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
DH_set0_pqg(dh, (BIGNUM *) dh_p, NULL, (BIGNUM *) dh_g);
#else
dh->p = dh_p;
dh->g = dh_g;
#endif /* prior to OpenSSL-1.1.0 */
dh_priv_key = BN_new();
/* Generate a random private exponent of the desired size, in bits. */
if (!BN_rand((BIGNUM *) dh_priv_key, dh_nbits, 0, 0)) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error generating DH random key (%d bits): %s", dh_nbits,
proxy_ssh_crypto_get_errors());
BN_clear_free((BIGNUM *) dh_priv_key);
DH_free(dh);
return -1;
}
dh_pub_key = BN_new();
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
DH_set0_key(dh, (BIGNUM *) dh_pub_key, (BIGNUM *) dh_priv_key);
#else
dh->pub_key = dh_pub_key;
dh->priv_key = dh_priv_key;
#endif /* prior to OpenSSL-1.1.0 */
pr_trace_msg(trace_channel, 12, "generating DH key");
if (DH_generate_key(dh) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error generating DH key: %s", proxy_ssh_crypto_get_errors());
DH_free(dh);
return -1;
}
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
DH_get0_key(dh, &dh_pub_key, NULL);
#else
dh_pub_key = dh->pub_key;
#endif /* prior to OpenSSL-1.1.0 */
if (have_good_dh(dh, dh_pub_key) < 0) {
DH_free(dh);
continue;
}
kex->dh = dh;
return 0;
}
errno = EPERM;
return -1;
}
static int read_dh_gex_group(struct proxy_ssh_packet *pkt,
struct proxy_ssh_kex *kex) {
unsigned char *buf = NULL;
uint32_t buflen = 0;
const BIGNUM *dh_p, *dh_g;
int dh_nbits;
buf = pkt->payload;
buflen = pkt->payload_len;
proxy_ssh_msg_read_mpint(pkt->pool, &buf, &buflen, &dh_p);
proxy_ssh_msg_read_mpint(pkt->pool, &buf, &buflen, &dh_g);
dh_nbits = BN_num_bits(dh_p);
if (kex->dh_gex_min > (uint32_t) dh_nbits ||
kex->dh_gex_max < (uint32_t) dh_nbits) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"server provided out-of-range DH size %d (requested %lu<%lu<%lu)",
dh_nbits, (unsigned long) kex->dh_gex_min,
(unsigned long) kex->dh_gex_pref, (unsigned long) kex->dh_gex_max);
return -1;
}
if (create_gex_dh(kex, dh_p, dh_g) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error generating group-exchange DH: %s", strerror(errno));
return -1;
}
return 0;
}
static int write_dh_gex_init(struct proxy_ssh_packet *pkt,
struct proxy_ssh_kex *kex) {
unsigned char *buf, *ptr;
uint32_t buflen, bufsz, len = 0;
const BIGNUM *dh_pub_key;
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
DH_get0_key(kex->dh, &dh_pub_key, NULL);
#else
dh_pub_key = kex->dh->pub_key;
#endif /* prior to OpenSSL-1.1.0 */
/* XXX Is this buffer large enough? Too large? */
bufsz = buflen = 2048;
ptr = buf = palloc(pkt->pool, bufsz);
len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEX_DH_GEX_INIT);
len += proxy_ssh_msg_write_mpint(&buf, &buflen, dh_pub_key);
pkt->payload = ptr;
pkt->payload_len = len;
return 0;
}
static int read_dh_gex_reply(struct proxy_ssh_packet *pkt,
struct proxy_ssh_kex *kex) {
const unsigned char *h;
unsigned char *buf, *buf2, *server_hostkey_data = NULL, *sig = NULL;
uint32_t buflen, server_hostkey_datalen = 0, siglen = 0, hlen = 0;
const BIGNUM *server_pub_key = NULL, *k = NULL;
size_t dh_len = 0;
int res;
buf = pkt->payload;
buflen = pkt->payload_len;
/* See RFC 4419, Section 3 "Diffie-Hellman Group and Key Exchange" */
proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &server_hostkey_datalen);
proxy_ssh_msg_read_data(pkt->pool, &buf, &buflen, server_hostkey_datalen,
&server_hostkey_data);
res = handle_server_hostkey(pkt->pool, kex->use_hostkey_type,
server_hostkey_data, server_hostkey_datalen);
if (res < 0) {
int xerrno;
xerrno = errno;
DH_free(kex->dh);
kex->dh = NULL;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error handling server host key: %s", strerror(xerrno));
errno = xerrno;
return -1;
}
proxy_ssh_msg_read_mpint(pkt->pool, &buf, &buflen, &server_pub_key);
if (have_good_dh(kex->dh, server_pub_key) < 0) {
DH_free(kex->dh);
kex->dh = NULL;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"invalid server public DH key");
return -1;
}
/* Compute the shared secret. */
dh_len = DH_size(kex->dh);
buf2 = palloc(pkt->pool, dh_len);
pr_trace_msg(trace_channel, 12, "computing DH key");
res = DH_compute_key(buf2, server_pub_key, kex->dh);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error computing DH shared secret: %s", proxy_ssh_crypto_get_errors());
DH_free(kex->dh);
kex->dh = NULL;
return -1;
}
k = BN_new();
if (BN_bin2bn(buf2, res, (BIGNUM *) k) == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error converting DH shared secret to BN: %s",
proxy_ssh_crypto_get_errors());
DH_free(kex->dh);
kex->dh = NULL;
return -1;
}
kex->k = k;
/* Calculate H */
h = calculate_gex_h(pkt->pool, kex, server_hostkey_data,
server_hostkey_datalen, server_pub_key, k, &hlen);
if (h == NULL) {
BN_clear_free((BIGNUM *) kex->k);
kex->k = NULL;
DH_free(kex->dh);
kex->dh = NULL;
return -1;
}
proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &siglen);
proxy_ssh_msg_read_data(pkt->pool, &buf, &buflen, siglen, &sig);
/* Verify H */
res = verify_h(pkt->pool, kex, server_hostkey_data, server_hostkey_datalen,
sig, siglen, h, hlen);
if (res < 0) {
BN_clear_free((BIGNUM *) kex->k);
kex->k = NULL;
DH_free(kex->dh);
kex->dh = NULL;
return -1;
}
kex->h = palloc(kex->pool, hlen);
kex->hlen = hlen;
memcpy((char *) kex->h, h, kex->hlen);
/* Save H as the session ID. */
proxy_ssh_session_set_id(session.pool, h, hlen);
return 0;
}
static int handle_kex_dh_gex(struct proxy_ssh_kex *kex, conn_t *conn) {
int res;
struct proxy_ssh_packet *pkt;
pr_trace_msg(trace_channel, 9, "writing DH_GEX_REQUEST message to server");
pkt = proxy_ssh_packet_create(kex_pool);
res = write_dh_gex_request(pkt, kex);
if (res < 0) {
destroy_pool(pkt->pool);
PROXY_SSH_DISCONNECT_CONN(conn,
PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL);
}
res = proxy_ssh_packet_write(conn, pkt);
if (res < 0) {
destroy_pool(pkt->pool);
PROXY_SSH_DISCONNECT_CONN(conn,
PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL);
}
destroy_pool(pkt->pool);
pr_trace_msg(trace_channel, 9, "reading DH_GEX_GROUP message from server");
pkt = read_kex_packet(kex_pool, kex, conn,
PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL, 1,
PROXY_SSH_MSG_KEX_DH_GEX_GROUP);
res = read_dh_gex_group(pkt, kex);
if (res < 0) {
destroy_pool(pkt->pool);
PROXY_SSH_DISCONNECT_CONN(conn,
PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL);
}
destroy_pool(pkt->pool);
pr_trace_msg(trace_channel, 9, "writing DH_GEX_INIT message to server");
pkt = proxy_ssh_packet_create(kex_pool);
res = write_dh_gex_init(pkt, kex);
if (res < 0) {
destroy_pool(pkt->pool);
PROXY_SSH_DISCONNECT_CONN(conn,
PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL);
}
res = proxy_ssh_packet_write(conn, pkt);
if (res < 0) {
destroy_pool(pkt->pool);
PROXY_SSH_DISCONNECT_CONN(conn,
PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL);
}
destroy_pool(pkt->pool);
pr_trace_msg(trace_channel, 9, "reading DH_GEX_REPLY message from server");
pkt = read_kex_packet(kex_pool, kex, conn,
PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL, 1,
PROXY_SSH_MSG_KEX_DH_GEX_REPLY);
res = read_dh_gex_reply(pkt, kex);
if (res < 0) {
destroy_pool(pkt->pool);
PROXY_SSH_DISCONNECT_CONN(conn,
PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL);
}
destroy_pool(pkt->pool);
return 0;
}
#if defined(PR_USE_OPENSSL_ECC)
static int write_ecdh_init(struct proxy_ssh_packet *pkt,
struct proxy_ssh_kex *kex) {
unsigned char *buf, *ptr;
uint32_t buflen, bufsz, len = 0;
/* In our ECDH_INIT, send 'e', our client curve public key. */
/* XXX Is this buffer large enough? Too large? */
bufsz = buflen = 2048;
ptr = buf = palloc(pkt->pool, bufsz);
len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEX_ECDH_INIT);
len += proxy_ssh_msg_write_ecpoint(&buf, &buflen,
EC_KEY_get0_group(kex->ec), EC_KEY_get0_public_key(kex->ec));
pkt->payload = ptr;
pkt->payload_len = len;
return 0;
}
/* This is used to validate the ECDSA parameters we might receive e.g. from
* a server. These checks come from Section 3.2.2.1 of 'Standards for
* Efficient Cryptography Group, "Elliptic Curve Cryptography", SEC 1,
* May 2009:
*
* http://www.secg.org/download/aid-780/sec1-v2.pdf
*
* as per RFC 5656 recommendation.
*/
static int validate_ecdsa_params(const EC_GROUP *group, const EC_POINT *point) {
BN_CTX *bn_ctx;
BIGNUM *ec_order, *x_coord, *y_coord, *bn_tmp;
int coord_nbits, ec_order_nbits;
EC_POINT *subgroup_order = NULL;
if (EC_METHOD_get_field_type(EC_GROUP_method_of(group)) != NID_X9_62_prime_field) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"ECDSA group is not a prime field, rejecting");
errno = EACCES;
return -1;
}
/* A Q of infinity is unacceptable. */
if (EC_POINT_is_at_infinity(group, point) != 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"ECDSA EC point has infinite value, rejecting");
errno = EACCES;
return -1;
}
/* A BN_CTX is like our pools; we allocate one, use it to get any
* number of BIGNUM variables, and only have free up the BN_CTX when
* we're done, rather than all of the individual BIGNUMs.
*/
bn_ctx = BN_CTX_new();
if (bn_ctx == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error allocating BN_CTX: %s", proxy_ssh_crypto_get_errors());
return -1;
}
BN_CTX_start(bn_ctx);
ec_order = BN_CTX_get(bn_ctx);
if (ec_order == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error getting new BIGNUM from BN_CTX: %s",
proxy_ssh_crypto_get_errors());
BN_CTX_free(bn_ctx);
errno = EPERM;
return -1;
}
if (EC_GROUP_get_order(group, ec_order, bn_ctx) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error getting EC group order: %s", proxy_ssh_crypto_get_errors());
BN_CTX_free(bn_ctx);
errno = EPERM;
return -1;
}
x_coord = BN_CTX_get(bn_ctx);
if (x_coord == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error getting new BIGNUM from BN_CTX: %s",
proxy_ssh_crypto_get_errors());
BN_CTX_free(bn_ctx);
errno = EPERM;
return -1;
}
y_coord = BN_CTX_get(bn_ctx);
if (y_coord == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error getting new BIGNUM from BN_CTX: %s",
proxy_ssh_crypto_get_errors());
BN_CTX_free(bn_ctx);
errno = EPERM;
return -1;
}
if (EC_POINT_get_affine_coordinates_GFp(group, point, x_coord, y_coord,
bn_ctx) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error getting EC point affine coordinates: %s",
proxy_ssh_crypto_get_errors());
BN_CTX_free(bn_ctx);
errno = EPERM;
return -1;
}
/* Ensure that the following are both true:
*
* log2(X coord) > log2(EC order)/2
* log2(Y coord) > log2(EC order)/2
*/
coord_nbits = BN_num_bits(x_coord);
ec_order_nbits = BN_num_bits(ec_order);
if (coord_nbits <= (ec_order_nbits / 2)) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"EC public key X coordinate (%d bits) too small (<= %d bits), rejecting",
coord_nbits, (ec_order_nbits / 2));
BN_CTX_free(bn_ctx);
errno = EACCES;
return -1;
}
coord_nbits = BN_num_bits(y_coord);
if (coord_nbits <= (ec_order_nbits / 2)) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"EC public key Y coordinate (%d bits) too small (<= %d bits), rejecting",
coord_nbits, (ec_order_nbits / 2));
BN_CTX_free(bn_ctx);
errno = EACCES;
return -1;
}
/* Ensure that the following is true:
*
* subgroup order == infinity
*/
subgroup_order = EC_POINT_new(group);
if (subgroup_order == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error allocating new EC_POINT: %s", proxy_ssh_crypto_get_errors());
BN_CTX_free(bn_ctx);
errno = EPERM;
return -1;
}
if (EC_POINT_mul(group, subgroup_order, NULL, point, ec_order, bn_ctx) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error doing EC point multiplication: %s", proxy_ssh_crypto_get_errors());
EC_POINT_free(subgroup_order);
BN_CTX_free(bn_ctx);
errno = EPERM;
return -1;
}
if (EC_POINT_is_at_infinity(group, subgroup_order) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"EC public key has finite subgroup order, rejecting");
EC_POINT_free(subgroup_order);
BN_CTX_free(bn_ctx);
errno = EACCES;
return -1;
}
EC_POINT_free(subgroup_order);
/* Ensure that the following are both true:
*
* X < order - 1
* Y < order - 1
*/
bn_tmp = BN_CTX_get(bn_ctx);
if (bn_tmp == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error getting new BIGNUM from BN_CTX: %s",
proxy_ssh_crypto_get_errors());
BN_CTX_free(bn_ctx);
errno = EPERM;
return -1;
}
if (BN_sub(bn_tmp, ec_order, BN_value_one()) == 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error subtracting one from EC group order: %s",
proxy_ssh_crypto_get_errors());
BN_CTX_free(bn_ctx);
errno = EPERM;
return -1;
}
if (BN_cmp(x_coord, bn_tmp) >= 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"EC public key X coordinate too large (>= EC group order - 1), "
"rejecting");
BN_CTX_free(bn_ctx);
errno = EACCES;
return -1;
}
if (BN_cmp(y_coord, bn_tmp) >= 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"EC public key Y coordinate too large (>= EC group order - 1), "
"rejecting");
BN_CTX_free(bn_ctx);
errno = EACCES;
return -1;
}
BN_CTX_free(bn_ctx);
return 0;
}
static int read_ecdh_reply(struct proxy_ssh_packet *pkt,
struct proxy_ssh_kex *kex) {
const unsigned char *h;
unsigned char *buf, *buf2, *server_hostkey_data = NULL, *sig = NULL;
uint32_t buflen, server_hostkey_datalen = 0, siglen = 0, hlen = 0;
const BIGNUM *k = NULL;
const EC_GROUP *curve = NULL;
EC_POINT *server_point = NULL;
size_t ecdh_len = 0;
int res;
buf = pkt->payload;
buflen = pkt->payload_len;
/* See RFC 5656, Section 4 "ECDH Key Exchange" */
proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &server_hostkey_datalen);
proxy_ssh_msg_read_data(pkt->pool, &buf, &buflen, server_hostkey_datalen,
&server_hostkey_data);
res = handle_server_hostkey(pkt->pool, kex->use_hostkey_type,
server_hostkey_data, server_hostkey_datalen);
if (res < 0) {
int xerrno;
xerrno = errno;
EC_KEY_free(kex->ec);
kex->ec = NULL;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error handling server host key: %s", strerror(xerrno));
errno = xerrno;
return -1;
}
curve = EC_KEY_get0_group(kex->ec);
server_point = EC_POINT_new(curve);
if (server_point == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error allocating EC_POINT: %s", proxy_ssh_crypto_get_errors());
EC_KEY_free(kex->ec);
kex->ec = NULL;
return -1;
}
proxy_ssh_msg_read_ecpoint(pkt->pool, &buf, &buflen, curve, &server_point);
if (server_point == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error reading ECDH_REPLY: %s", strerror(errno));
EC_KEY_free(kex->ec);
kex->ec = NULL;
return -1;
}
kex->server_point = server_point;
if (validate_ecdsa_params(curve, kex->server_point) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"invalid server ECDH public key (EC point): %s", strerror(errno));
EC_POINT_clear_free(kex->server_point);
kex->server_point = NULL;
return -1;
}
/* Compute the shared secret */
ecdh_len = ((EC_GROUP_get_degree(EC_KEY_get0_group(kex->ec)) + 7) / 8);
buf2 = palloc(kex_pool, ecdh_len);
pr_trace_msg(trace_channel, 12, "computing ECDH key");
res = ECDH_compute_key((unsigned char *) buf2, ecdh_len, kex->server_point,
kex->ec, NULL);
if (res <= 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error computing ECDH shared secret: %s", proxy_ssh_crypto_get_errors());
pr_memscrub(buf2, ecdh_len);
return -1;
}
if ((size_t) res != ecdh_len) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"computed ECDH shared secret length (%d) does not match needed length "
"(%lu), rejecting", res, (unsigned long) ecdh_len);
pr_memscrub(buf2, ecdh_len);
return -1;
}
k = BN_new();
if (k == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error allocating new BIGNUM: %s", proxy_ssh_crypto_get_errors());
pr_memscrub(buf2, ecdh_len);
return -1;
}
if (BN_bin2bn(buf2, res, (BIGNUM *) k) == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error converting ECDH shared secret to BN: %s",
proxy_ssh_crypto_get_errors());
EC_KEY_free(kex->ec);
kex->ec = NULL;
pr_memscrub(buf2, ecdh_len);
return -1;
}
kex->k = k;
pr_memscrub(buf2, ecdh_len);
/* Calculate H */
h = calculate_ecdh_h(pkt->pool, kex, server_hostkey_data,
server_hostkey_datalen, k, &hlen);
if (h == NULL) {
BN_clear_free((BIGNUM *) kex->k);
kex->k = NULL;
EC_KEY_free(kex->ec);
kex->ec = NULL;
return -1;
}
proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &siglen);
proxy_ssh_msg_read_data(pkt->pool, &buf, &buflen, siglen, &sig);
/* Verify H */
res = verify_h(pkt->pool, kex, server_hostkey_data, server_hostkey_datalen,
sig, siglen, h, hlen);
if (res < 0) {
BN_clear_free((BIGNUM *) kex->k);
kex->k = NULL;
EC_KEY_free(kex->ec);
kex->ec = NULL;
return -1;
}
kex->h = palloc(kex->pool, hlen);
kex->hlen = hlen;
memcpy((char *) kex->h, h, kex->hlen);
/* Save H as the session ID. */
proxy_ssh_session_set_id(session.pool, h, hlen);
return 0;
}
static int handle_kex_ecdh(struct proxy_ssh_kex *kex, conn_t *conn) {
int res;
struct proxy_ssh_packet *pkt;
pr_trace_msg(trace_channel, 9, "writing ECDH_INIT message to server");
pkt = proxy_ssh_packet_create(kex_pool);
res = write_ecdh_init(pkt, kex);
if (res < 0) {
destroy_pool(pkt->pool);
PROXY_SSH_DISCONNECT_CONN(conn,
PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL);
}
res = proxy_ssh_packet_write(conn, pkt);
if (res < 0) {
destroy_pool(pkt->pool);
PROXY_SSH_DISCONNECT_CONN(conn,
PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL);
}
destroy_pool(pkt->pool);
pr_trace_msg(trace_channel, 9, "reading ECDH_REPLY message from server");
pkt = read_kex_packet(kex_pool, kex, conn,
PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL, 1,
PROXY_SSH_MSG_KEX_ECDH_REPLY);
res = read_ecdh_reply(pkt, kex);
if (res < 0) {
destroy_pool(pkt->pool);
PROXY_SSH_DISCONNECT_CONN(conn,
PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL);
}
destroy_pool(pkt->pool);
return 0;
}
#endif /* PR_USE_OPENSSL_ECC */
static int read_kexrsa_pubkey(struct proxy_ssh_packet *pkt,
struct proxy_ssh_kex *kex, pool *hostkey_pool,
unsigned char **hostkey_data, uint32_t *hostkey_datalen) {
char *key_type = NULL;
unsigned char *buf, *server_hostkey_data = NULL, *rsa_pubkey_data = NULL;
uint32_t buflen, server_hostkey_datalen = 0, rsa_pubkey_datalen = 0;
const BIGNUM *rsa_n = NULL, *rsa_e = NULL;
int res;
buf = pkt->payload;
buflen = pkt->payload_len;
/* See RFC 4432 "SSH RSA Key Exchange" */
proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &server_hostkey_datalen);
proxy_ssh_msg_read_data(pkt->pool, &buf, &buflen, server_hostkey_datalen,
&server_hostkey_data);
res = handle_server_hostkey(pkt->pool, kex->use_hostkey_type,
server_hostkey_data, server_hostkey_datalen);
if (res < 0) {
int xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error handling server host key: %s", strerror(xerrno));
errno = xerrno;
return -1;
}
*hostkey_datalen = server_hostkey_datalen;
*hostkey_data = palloc(hostkey_pool, server_hostkey_datalen);
memcpy(*hostkey_data, server_hostkey_data, server_hostkey_datalen);
proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &rsa_pubkey_datalen);
proxy_ssh_msg_read_data(pkt->pool, &buf, &buflen, rsa_pubkey_datalen,
&rsa_pubkey_data);
kex->rsa = RSA_new();
if (kex->rsa == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error allocating new RSA: %s", proxy_ssh_crypto_get_errors());
pr_memscrub(rsa_pubkey_data, rsa_pubkey_datalen);
return -1;
}
buf = rsa_pubkey_data;
buflen = rsa_pubkey_datalen;
proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &key_type);
if (key_type == NULL ||
strcmp(key_type, "ssh-rsa") != 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unsupported key type received: %s",
key_type != NULL ? key_type : "(nil)");
return -1;
}
proxy_ssh_msg_read_mpint(pkt->pool, &buf, &buflen, &rsa_e);
proxy_ssh_msg_read_mpint(pkt->pool, &buf, &buflen, &rsa_n);
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
RSA_set0_key(kex->rsa, (BIGNUM *) rsa_n, (BIGNUM *) rsa_e, NULL);
#else
kex->rsa->e = rsa_e;
kex->rsa->n = rsa_n;
#endif /* prior to OpenSSL-1.1.0 */
return 0;
}
static int write_kexrsa_secret(struct proxy_ssh_packet *pkt,
struct proxy_ssh_kex *kex) {
BN_CTX *ctx;
BIGNUM *k = NULL, *range = NULL, *two = NULL, *bits = NULL;
int res, klen = 0, hlen = 0, nbits = 0, plaintext_len = 0, encrypted_len = 0;
unsigned char *buf, *ptr, *plaintext = NULL, *encrypted = NULL;
uint32_t bufsz, buflen, len = 0;
klen = RSA_size(kex->rsa) * 8;
hlen = EVP_MD_size(kex->hash) * 8;
nbits = klen - (2 * hlen) - 49;
two = BN_new();
if (two == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error allocating new BIGNUM: %s", proxy_ssh_crypto_get_errors());
return -1;
}
res = BN_set_word(two, (BN_ULONG) 2);
if (res != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error allocating setting BIGNUM value: %s",
proxy_ssh_crypto_get_errors());
BN_free(two);
return -1;
}
bits = BN_new();
if (bits == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error allocating new BIGNUM: %s", proxy_ssh_crypto_get_errors());
BN_free(two);
return -1;
}
res = BN_set_word(bits, (BN_ULONG) nbits);
if (res != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error allocating setting BIGNUM value: %s",
proxy_ssh_crypto_get_errors());
BN_free(two);
BN_free(bits);
return -1;
}
range = BN_new();
if (range == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error allocating new BIGNUM: %s", proxy_ssh_crypto_get_errors());
BN_free(two);
BN_free(bits);
return -1;
}
ctx = BN_CTX_new();
if (ctx == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error allocating new BN_CTX: %s", proxy_ssh_crypto_get_errors());
BN_free(two);
BN_free(bits);
BN_free(range);
return -1;
}
res = BN_exp(range, two, bits, ctx);
if (res != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error expontentiating BIGNUM: %s", proxy_ssh_crypto_get_errors());
BN_free(two);
BN_free(bits);
BN_free(range);
BN_CTX_free(ctx);
return -1;
}
k = BN_new();
if (k == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error allocating new BIGNUM: %s", proxy_ssh_crypto_get_errors());
BN_free(two);
BN_free(bits);
BN_free(range);
BN_CTX_free(ctx);
return -1;
}
res = BN_rand_range(k, range);
if (res != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error generating random BIGNUM: %s", proxy_ssh_crypto_get_errors());
BN_free(k);
BN_free(two);
BN_free(bits);
BN_free(range);
BN_CTX_free(ctx);
return -1;
}
BN_free(two);
BN_free(bits);
BN_free(range);
BN_CTX_free(ctx);
kex->k = k;
plaintext_len = BN_bn2mpi(kex->k, NULL);
plaintext = palloc(pkt->pool, plaintext_len);
res = BN_bn2mpi(kex->k, plaintext);
if (res != plaintext_len) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error converting RSA shared secret from BN: %s",
proxy_ssh_crypto_get_errors());
BN_clear_free((BIGNUM *) kex->k);
kex->k = NULL;
return -1;
}
pr_trace_msg(trace_channel, 12, "encrypting RSA shared secret");
encrypted_len = RSA_size(kex->rsa);
encrypted = palloc(pkt->pool, encrypted_len);
res = RSA_public_encrypt(plaintext_len, plaintext, encrypted, kex->rsa,
RSA_PKCS1_OAEP_PADDING);
if (res == -1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error encrypting RSA shared secret: %s", proxy_ssh_crypto_get_errors());
pr_memscrub(plaintext, plaintext_len);
BN_clear_free((BIGNUM *) kex->k);
kex->k = NULL;
return -1;
}
pr_memscrub(plaintext, plaintext_len);
/* Store the encrypted RSA for calculating H later. */
kex->rsa_encrypted_len = encrypted_len;
kex->rsa_encrypted = palloc(kex_pool, encrypted_len);
memcpy(kex->rsa_encrypted, encrypted, encrypted_len);
bufsz = buflen = 2048;
/* XXX Is this buffer large enough? Too large? */
ptr = buf = palloc(pkt->pool, bufsz);
len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEXRSA_SECRET);
len += proxy_ssh_msg_write_data(&buf, &buflen, encrypted, encrypted_len,
TRUE);
pkt->payload = ptr;
pkt->payload_len = len;
return 0;
}
static int read_kexrsa_done(struct proxy_ssh_packet *pkt,
struct proxy_ssh_kex *kex, unsigned char *server_hostkey_data,
uint32_t server_hostkey_datalen) {
const unsigned char *h;
unsigned char *buf, *sig = NULL;
uint32_t buflen, siglen = 0, hlen = 0;
int res;
buf = pkt->payload;
buflen = pkt->payload_len;
proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &siglen);
proxy_ssh_msg_read_data(pkt->pool, &buf, &buflen, siglen, &sig);
/* Calculate H */
h = calculate_kexrsa_h(pkt->pool, kex, server_hostkey_data,
server_hostkey_datalen, kex->k, &hlen);
if (h == NULL) {
BN_clear_free((BIGNUM *) kex->k);
kex->k = NULL;
return -1;
}
/* Verify H */
res = verify_h(pkt->pool, kex, server_hostkey_data, server_hostkey_datalen,
sig, siglen, h, hlen);
if (res < 0) {
BN_clear_free((BIGNUM *) kex->k);
kex->k = NULL;
return -1;
}
kex->h = palloc(kex->pool, hlen);
kex->hlen = hlen;
memcpy((char *) kex->h, h, kex->hlen);
/* Save H as the session ID. */
proxy_ssh_session_set_id(session.pool, h, hlen);
return 0;
}
static int handle_kex_rsa(struct proxy_ssh_kex *kex, conn_t *conn) {
pool *tmp_pool;
int res;
struct proxy_ssh_packet *pkt;
unsigned char *server_hostkey_data = NULL;
uint32_t server_hostkey_datalen = 0;
tmp_pool = make_sub_pool(session.pool);
pr_pool_tag(tmp_pool, "KEXRSA pool");
pr_trace_msg(trace_channel, 9, "reading KEXRSA_PUBKEY message from server");
pkt = read_kex_packet(kex_pool, kex, conn,
PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL, 1,
PROXY_SSH_MSG_KEXRSA_PUBKEY);
res = read_kexrsa_pubkey(pkt, kex, tmp_pool, &server_hostkey_data,
&server_hostkey_datalen);
if (res < 0) {
destroy_pool(pkt->pool);
PROXY_SSH_DISCONNECT_CONN(conn,
PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL);
}
destroy_pool(pkt->pool);
pr_trace_msg(trace_channel, 9, "writing KEXRSA_SECRET message to server");
pkt = proxy_ssh_packet_create(kex_pool);
res = write_kexrsa_secret(pkt, kex);
if (res < 0) {
destroy_pool(pkt->pool);
PROXY_SSH_DISCONNECT_CONN(conn,
PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL);
}
pr_trace_msg(trace_channel, 9, "sending KEXRSA_SECRET message to server");
res = proxy_ssh_packet_write(conn, pkt);
if (res < 0) {
destroy_pool(pkt->pool);
PROXY_SSH_DISCONNECT_CONN(conn,
PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL);
}
destroy_pool(pkt->pool);
pr_trace_msg(trace_channel, 9, "reading KEXRSA_DONE message from server");
pkt = read_kex_packet(kex_pool, kex, conn,
PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL, 1,
PROXY_SSH_MSG_KEXRSA_DONE);
res = read_kexrsa_done(pkt, kex, server_hostkey_data, server_hostkey_datalen);
if (res < 0) {
destroy_pool(pkt->pool);
PROXY_SSH_DISCONNECT_CONN(conn,
PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL);
}
destroy_pool(pkt->pool);
destroy_pool(tmp_pool);
return 0;
}
#if defined(PR_USE_SODIUM) && defined(HAVE_SHA256_OPENSSL)
static int write_curve25519_init(struct proxy_ssh_packet *pkt,
struct proxy_ssh_kex *kex) {
unsigned char *buf, *ptr;
uint32_t buflen, bufsz, len = 0;
/* In our Curve25519 ECDH_INIT, send 'e', our client curve25519 public key. */
/* XXX Is this buffer large enough? Too large? */
bufsz = buflen = 2048;
ptr = buf = palloc(pkt->pool, bufsz);
len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEX_ECDH_INIT);
len += proxy_ssh_msg_write_data(&buf, &buflen, kex->client_curve25519_pub_key,
CURVE25519_SIZE, TRUE);
pkt->payload = ptr;
pkt->payload_len = len;
return 0;
}
static int read_curve25519_reply(struct proxy_ssh_packet *pkt,
struct proxy_ssh_kex *kex) {
const unsigned char *h;
unsigned char zero_curve25519[CURVE25519_SIZE];
unsigned char *buf, *buf2, *server_hostkey_data = NULL, *sig = NULL;
uint32_t buflen, pub_keylen, server_hostkey_datalen = 0, siglen = 0, hlen = 0;
const BIGNUM *k = NULL;
int res;
buf = pkt->payload;
buflen = pkt->payload_len;
/* See RFC 5656, Section 4 "ECDH Key Exchange", modified by RFC 8731. */
proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &server_hostkey_datalen);
proxy_ssh_msg_read_data(pkt->pool, &buf, &buflen, server_hostkey_datalen,
&server_hostkey_data);
res = handle_server_hostkey(pkt->pool, kex->use_hostkey_type,
server_hostkey_data, server_hostkey_datalen);
if (res < 0) {
int xerrno;
xerrno = errno;
pr_memscrub(kex->client_curve25519_priv_key, CURVE25519_SIZE);
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error handling server host key: %s", strerror(xerrno));
errno = xerrno;
return -1;
}
proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &pub_keylen);
if (pub_keylen != CURVE25519_SIZE) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"rejecting invalid length (%lu %s, wanted %d) of server Curve25519 key",
(unsigned long) pub_keylen, pub_keylen != 1 ? "bytes" : "byte",
CURVE25519_SIZE);
pr_memscrub(kex->client_curve25519_priv_key, CURVE25519_SIZE);
return -1;
}
proxy_ssh_msg_read_data(kex->pool, &buf, &buflen, pub_keylen,
&(kex->server_curve25519_pub_key));
if (kex->server_curve25519_pub_key == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error reading ECDH_REPLY: %s", strerror(errno));
pr_memscrub(kex->client_curve25519_priv_key, CURVE25519_SIZE);
return -1;
}
/* Watch for all-zero public keys, and reject them. */
sodium_memzero(zero_curve25519, CURVE25519_SIZE);
if (sodium_memcmp(kex->server_curve25519_pub_key, zero_curve25519,
CURVE25519_SIZE) == 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"rejecting invalid (all-zero) server Curve25519 key");
pr_memscrub(kex->client_curve25519_priv_key, CURVE25519_SIZE);
return -1;
}
/* Compute the shared secret */
buf2 = palloc(kex->pool, CURVE25519_SIZE);
pr_trace_msg(trace_channel, 12, "computing Curve25519 key");
res = get_curve25519_shared_key((unsigned char *) buf2,
kex->server_curve25519_pub_key, kex->client_curve25519_priv_key);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error computing Curve25519 shared secret: %s", strerror(errno));
pr_memscrub(buf2, CURVE25519_SIZE);
pr_memscrub(kex->client_curve25519_priv_key, CURVE25519_SIZE);
return -1;
}
k = BN_new();
if (k == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error allocating new BIGNUM: %s", proxy_ssh_crypto_get_errors());
pr_memscrub(buf2, CURVE25519_SIZE);
pr_memscrub(kex->client_curve25519_priv_key, CURVE25519_SIZE);
return -1;
}
if (BN_bin2bn(buf2, res, (BIGNUM *) k) == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error converting Curve25519 shared secret to BN: %s",
proxy_ssh_crypto_get_errors());
pr_memscrub(buf2, CURVE25519_SIZE);
pr_memscrub(kex->client_curve25519_priv_key, CURVE25519_SIZE);
return -1;
}
kex->k = k;
pr_memscrub(buf2, CURVE25519_SIZE);
/* Calculate H */
h = calculate_curve25519_h(pkt->pool, kex, server_hostkey_data,
server_hostkey_datalen, k, &hlen);
if (h == NULL) {
BN_clear_free((BIGNUM *) kex->k);
kex->k = NULL;
pr_memscrub(kex->client_curve25519_priv_key, CURVE25519_SIZE);
return -1;
}
proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &siglen);
proxy_ssh_msg_read_data(pkt->pool, &buf, &buflen, siglen, &sig);
/* Verify H */
res = verify_h(pkt->pool, kex, server_hostkey_data, server_hostkey_datalen,
sig, siglen, h, hlen);
if (res < 0) {
BN_clear_free((BIGNUM *) kex->k);
kex->k = NULL;
pr_memscrub(kex->client_curve25519_priv_key, CURVE25519_SIZE);
return -1;
}
kex->h = palloc(kex->pool, hlen);
kex->hlen = hlen;
memcpy((char *) kex->h, h, kex->hlen);
/* Save H as the session ID. */
proxy_ssh_session_set_id(session.pool, h, hlen);
/* We no longer need the private key. */
pr_memscrub(kex->client_curve25519_priv_key, CURVE25519_SIZE);
return 0;
}
static int handle_kex_curve25519(struct proxy_ssh_kex *kex, conn_t *conn) {
int res;
struct proxy_ssh_packet *pkt;
pr_trace_msg(trace_channel, 9, "writing ECDH_INIT message to server");
pkt = proxy_ssh_packet_create(kex_pool);
res = write_curve25519_init(pkt, kex);
if (res < 0) {
destroy_pool(pkt->pool);
PROXY_SSH_DISCONNECT_CONN(conn,
PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL);
}
res = proxy_ssh_packet_write(conn, pkt);
if (res < 0) {
destroy_pool(pkt->pool);
PROXY_SSH_DISCONNECT_CONN(conn,
PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL);
}
destroy_pool(pkt->pool);
pr_trace_msg(trace_channel, 9, "reading ECDH_REPLY message from server");
pkt = read_kex_packet(kex_pool, kex, conn,
PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL, 1,
PROXY_SSH_MSG_KEX_ECDH_REPLY);
res = read_curve25519_reply(pkt, kex);
if (res < 0) {
destroy_pool(pkt->pool);
PROXY_SSH_DISCONNECT_CONN(conn,
PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL);
}
destroy_pool(pkt->pool);
return 0;
}
#endif /* PR_USE_SODIUM and HAVE_SHA256_OPENSSL */
#if defined(HAVE_X448_OPENSSL) && defined(HAVE_SHA512_OPENSSL)
static int write_curve448_init(struct proxy_ssh_packet *pkt,
struct proxy_ssh_kex *kex) {
unsigned char *buf, *ptr;
uint32_t buflen, bufsz, len = 0;
/* In our Curve448 ECDH_INIT, send 'e', our client curve448 public key. */
/* XXX Is this buffer large enough? Too large? */
bufsz = buflen = 2048;
ptr = buf = palloc(pkt->pool, bufsz);
len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_KEX_ECDH_INIT);
len += proxy_ssh_msg_write_data(&buf, &buflen, kex->client_curve448_pub_key,
CURVE448_SIZE, TRUE);
pkt->payload = ptr;
pkt->payload_len = len;
return 0;
}
static int read_curve448_reply(struct proxy_ssh_packet *pkt,
struct proxy_ssh_kex *kex) {
const unsigned char *h;
unsigned char zero_curve448[CURVE448_SIZE];
unsigned char *buf, *buf2, *server_hostkey_data = NULL, *sig = NULL;
uint32_t buflen, pub_keylen, server_hostkey_datalen = 0, siglen = 0, hlen = 0;
const BIGNUM *k = NULL;
int res;
buf = pkt->payload;
buflen = pkt->payload_len;
/* See RFC 5656, Section 4 "ECDH Key Exchange", modified by RFC 8731. */
proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &server_hostkey_datalen);
proxy_ssh_msg_read_data(pkt->pool, &buf, &buflen, server_hostkey_datalen,
&server_hostkey_data);
res = handle_server_hostkey(pkt->pool, kex->use_hostkey_type,
server_hostkey_data, server_hostkey_datalen);
if (res < 0) {
int xerrno;
xerrno = errno;
pr_memscrub(kex->client_curve448_priv_key, CURVE448_SIZE);
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error handling server host key: %s", strerror(xerrno));
errno = xerrno;
return -1;
}
proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &pub_keylen);
if (pub_keylen != CURVE448_SIZE) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"rejecting invalid length (%lu %s, wanted %d) of server Curve448 key",
(unsigned long) pub_keylen, pub_keylen != 1 ? "bytes" : "byte",
CURVE448_SIZE);
pr_memscrub(kex->client_curve448_priv_key, CURVE448_SIZE);
return -1;
}
/* Note that we use `kex->pool` here, since we are storing the key in the
* `kex` structure.
*/
proxy_ssh_msg_read_data(kex->pool, &buf, &buflen, pub_keylen,
&(kex->server_curve448_pub_key));
if (kex->server_curve448_pub_key == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error reading ECDH_REPLY: %s", strerror(errno));
pr_memscrub(kex->client_curve448_priv_key, CURVE448_SIZE);
return -1;
}
/* Watch for all-zero public keys, and reject them. */
memset(zero_curve448, '\0', sizeof(zero_curve448));
if (memcmp(kex->server_curve448_pub_key, zero_curve448, CURVE448_SIZE) == 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"rejecting invalid (all-zero) server Curve448 key");
pr_memscrub(kex->client_curve448_priv_key, CURVE448_SIZE);
return -1;
}
/* Compute the shared secret */
buf2 = palloc(kex->pool, CURVE448_SIZE);
pr_trace_msg(trace_channel, 12, "computing Curve448 key");
res = get_curve448_shared_key((unsigned char *) buf2,
kex->server_curve448_pub_key, kex->client_curve448_priv_key);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error computing Curve448 shared secret: %s", strerror(errno));
pr_memscrub(buf2, CURVE448_SIZE);
pr_memscrub(kex->client_curve448_priv_key, CURVE448_SIZE);
return -1;
}
k = BN_new();
if (k == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error allocating new BIGNUM: %s", proxy_ssh_crypto_get_errors());
pr_memscrub(buf2, CURVE448_SIZE);
pr_memscrub(kex->client_curve448_priv_key, CURVE448_SIZE);
return -1;
}
if (BN_bin2bn(buf2, res, (BIGNUM *) k) == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error converting Curve448 shared secret to BN: %s",
proxy_ssh_crypto_get_errors());
pr_memscrub(buf2, CURVE448_SIZE);
pr_memscrub(kex->client_curve448_priv_key, CURVE448_SIZE);
return -1;
}
kex->k = k;
pr_memscrub(buf2, CURVE448_SIZE);
/* Calculate H */
h = calculate_curve448_h(pkt->pool, kex, server_hostkey_data,
server_hostkey_datalen, k, &hlen);
if (h == NULL) {
BN_clear_free((BIGNUM *) kex->k);
kex->k = NULL;
pr_memscrub(kex->client_curve448_priv_key, CURVE448_SIZE);
return -1;
}
proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &siglen);
proxy_ssh_msg_read_data(pkt->pool, &buf, &buflen, siglen, &sig);
/* Verify H */
res = verify_h(pkt->pool, kex, server_hostkey_data, server_hostkey_datalen,
sig, siglen, h, hlen);
if (res < 0) {
BN_clear_free((BIGNUM *) kex->k);
kex->k = NULL;
pr_memscrub(kex->client_curve448_priv_key, CURVE448_SIZE);
return -1;
}
kex->h = palloc(kex->pool, hlen);
kex->hlen = hlen;
memcpy((char *) kex->h, h, kex->hlen);
/* Save H as the session ID. */
proxy_ssh_session_set_id(session.pool, h, hlen);
/* We no longer need the private key. */
pr_memscrub(kex->client_curve448_priv_key, CURVE448_SIZE);
return 0;
}
static int handle_kex_curve448(struct proxy_ssh_kex *kex, conn_t *conn) {
int res;
struct proxy_ssh_packet *pkt;
pr_trace_msg(trace_channel, 9, "writing ECDH_INIT message to server");
pkt = proxy_ssh_packet_create(kex_pool);
res = write_curve448_init(pkt, kex);
if (res < 0) {
destroy_pool(pkt->pool);
PROXY_SSH_DISCONNECT_CONN(conn,
PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL);
}
res = proxy_ssh_packet_write(conn, pkt);
if (res < 0) {
destroy_pool(pkt->pool);
PROXY_SSH_DISCONNECT_CONN(conn,
PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL);
}
destroy_pool(pkt->pool);
pr_trace_msg(trace_channel, 9, "reading ECDH_REPLY message from server");
pkt = read_kex_packet(kex_pool, kex, conn,
PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL, 1,
PROXY_SSH_MSG_KEX_ECDH_REPLY);
res = read_curve448_reply(pkt, kex);
if (res < 0) {
destroy_pool(pkt->pool);
PROXY_SSH_DISCONNECT_CONN(conn,
PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, NULL);
}
destroy_pool(pkt->pool);
return 0;
}
#endif /* HAVE_X448_OPENSSL and HAVE_SHA512_OPENSSL */
static int run_kex(struct proxy_ssh_kex *kex, conn_t *conn) {
const char *algo;
algo = kex->session_names->kex_algo;
if (strcmp(algo, "diffie-hellman-group1-sha1") == 0 ||
strcmp(algo, "diffie-hellman-group14-sha1") == 0 ||
strcmp(algo, "diffie-hellman-group14-sha256") == 0 ||
strcmp(algo, "diffie-hellman-group16-sha512") == 0 ||
strcmp(algo, "diffie-hellman-group18-sha512") == 0) {
return handle_kex_dh(kex, conn);
} else if (strcmp(algo, "diffie-hellman-group-exchange-sha1") == 0) {
return handle_kex_dh_gex(kex, conn);
} else if (strcmp(algo, "rsa1024-sha1") == 0) {
return handle_kex_rsa(kex, conn);
#if ((OPENSSL_VERSION_NUMBER > 0x000907000L && defined(OPENSSL_FIPS)) || \
(OPENSSL_VERSION_NUMBER > 0x000908000L)) && \
defined(HAVE_SHA256_OPENSSL)
} else if (strcmp(algo, "diffie-hellman-group-exchange-sha256") == 0) {
return handle_kex_dh_gex(kex, conn);
} else if (strcmp(algo, "rsa2048-sha256") == 0) {
return handle_kex_rsa(kex, conn);
#endif
#if defined(PR_USE_OPENSSL_ECC)
} else if (strcmp(algo, "ecdh-sha2-nistp256") == 0 ||
strcmp(algo, "ecdh-sha2-nistp384") == 0 ||
strcmp(algo, "ecdh-sha2-nistp521") == 0) {
return handle_kex_ecdh(kex, conn);
#endif /* PR_USE_OPENSSL_ECC */
#if defined(PR_USE_SODIUM) && defined(HAVE_SHA256_OPENSSL)
} else if (strcmp(algo, "curve25519-sha256") == 0 ||
strcmp(algo, "curve25519-sha256@libssh.org") == 0) {
return handle_kex_curve25519(kex, conn);
#endif /* PR_USE_SODIUM and HAVE_SHA256_OPENSSL */
#if defined(HAVE_X448_OPENSSL) && defined(HAVE_SHA512_OPENSSL)
} else if (strcmp(algo, "curve448-sha512") == 0) {
return handle_kex_curve448(kex, conn);
#endif /* HAVE_X448_OPENSSL and HAVE_SHA512_OPENSSL */
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unsupported key exchange algorithm '%s'", algo);
errno = EINVAL;
return -1;
}
int proxy_ssh_kex_handle(struct proxy_ssh_packet *pkt,
const struct proxy_session *proxy_sess) {
int correct_guess = TRUE, res, sent_newkeys = FALSE;
char msg_type;
struct proxy_ssh_kex *kex;
/* We may already have a kex structure, either from the client
* initial connect (kex_first_kex not null), or because we
* are in a server-initiated rekeying (kex_rekey_kex not null).
*/
if (kex_first_kex != NULL) {
kex = kex_first_kex;
/* We need to assign the client/server versions, which this struct
* will not have.
*/
kex->client_version = kex_client_version;
kex->server_version = kex_server_version;
} else if (kex_rekey_kex != NULL) {
kex = kex_rekey_kex;
} else {
kex = create_kex(kex_pool);
}
/* The packet we are given is guaranteed to be a KEXINIT packet. */
pr_trace_msg(trace_channel, 9, "reading KEXINIT message from server");
res = read_kexinit(pkt, kex);
if (res < 0) {
destroy_kex(kex);
destroy_pool(pkt->pool);
return -1;
}
destroy_pool(pkt->pool);
pr_trace_msg(trace_channel, 9,
"determining shared algorithms for SSH session");
if (get_session_names(kex, &correct_guess) < 0) {
destroy_kex(kex);
return -1;
}
if (use_strict_kex == TRUE &&
kex_done_first_kex == FALSE) {
uint32_t server_seqno;
server_seqno = proxy_ssh_packet_get_server_seqno();
if (server_seqno != 1) {
/* Receiving any messages other than a KEXINIT as the first server
* message indicates the possibility of the Terrapin attack being
* conducted (Issue 257). Thus we disconnect the server in such
* cases.
*/
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"'strict KEX' violation, as KEXINIT was not the first message; disconnecting");
destroy_kex(kex);
PROXY_SSH_DISCONNECT_CONN(proxy_sess->backend_ctrl_conn,
PROXY_SSH_DISCONNECT_BY_APPLICATION, NULL);
}
}
/* Once we have received the server KEXINIT message, we can compare what we
* want to send against what we already received from the server.
*
* If the server said that it was going to send a "guess" KEX packet,
* and we determine that its key exchange guess matches what we would have
* sent in our KEXINIT, then we proceed on with reading and handling that
* guess packet. If not, we ignore that packet, and proceed.
*/
if (kex->first_kex_follows == FALSE) {
/* No guess packet sent; send our KEXINIT as normal (as long as we are
* not in a server-initiated rekeying).
*/
if (kex_sent_kexinit == FALSE) {
pkt = proxy_ssh_packet_create(kex_pool);
res = write_kexinit(pkt, kex);
if (res < 0) {
destroy_kex(kex);
destroy_pool(pkt->pool);
return -1;
}
pr_trace_msg(trace_channel, 9, "sending KEXINIT message to server");
res = proxy_ssh_packet_write(proxy_sess->backend_ctrl_conn, pkt);
if (res < 0) {
destroy_kex(kex);
destroy_pool(pkt->pool);
return res;
}
kex_sent_kexinit = TRUE;
destroy_pool(pkt->pool);
}
} else {
/* If the server sent a guess kex packet, but that guess was incorrect,
* then we need to consume and silently ignore that packet, and proceed
* as normal.
*/
if (correct_guess == FALSE) {
pr_trace_msg(trace_channel, 3, "server sent incorrect key exchange "
"guess, ignoring guess packet");
pkt = read_kex_packet(kex_pool, kex, proxy_sess->backend_ctrl_conn,
PROXY_SSH_DISCONNECT_KEY_EXCHANGE_FAILED, &msg_type, 3,
PROXY_SSH_MSG_KEX_DH_INIT,
PROXY_SSH_MSG_KEX_DH_GEX_INIT,
PROXY_SSH_MSG_KEX_ECDH_INIT);
pr_trace_msg(trace_channel, 3,
"ignored %s (%d) guess message sent by server",
proxy_ssh_packet_get_msg_type_desc(msg_type), msg_type);
destroy_pool(pkt->pool);
if (kex_sent_kexinit == FALSE) {
pkt = proxy_ssh_packet_create(kex_pool);
res = write_kexinit(pkt, kex);
if (res < 0) {
destroy_kex(kex);
destroy_pool(pkt->pool);
return -1;
}
pr_trace_msg(trace_channel, 9, "sending KEXINIT message to server");
res = proxy_ssh_packet_write(proxy_sess->backend_ctrl_conn, pkt);
if (res < 0) {
destroy_kex(kex);
destroy_pool(pkt->pool);
return res;
}
kex_sent_kexinit = TRUE;
destroy_pool(pkt->pool);
}
}
if (kex_sent_kexinit == FALSE) {
pkt = proxy_ssh_packet_create(kex_pool);
res = write_kexinit(pkt, kex);
if (res < 0) {
destroy_kex(kex);
destroy_pool(pkt->pool);
return -1;
}
pr_trace_msg(trace_channel, 9, "sending KEXINIT message to server");
res = proxy_ssh_packet_write(proxy_sess->backend_ctrl_conn, pkt);
if (res < 0) {
destroy_kex(kex);
destroy_pool(pkt->pool);
return res;
}
kex_sent_kexinit = TRUE;
destroy_pool(pkt->pool);
}
}
if (run_kex(kex, proxy_sess->backend_ctrl_conn) < 0) {
destroy_kex(kex);
return -1;
}
if (!proxy_ssh_interop_supports_feature(PROXY_SSH_FEAT_PESSIMISTIC_NEWKEYS)) {
pr_trace_msg(trace_channel, 9, "sending NEWKEYS message to server");
/* Send our NEWKEYS reply. */
pkt = proxy_ssh_packet_create(kex_pool);
res = write_newkeys_reply(pkt);
if (res < 0) {
destroy_kex(kex);
destroy_pool(pkt->pool);
return -1;
}
res = proxy_ssh_packet_write(proxy_sess->backend_ctrl_conn, pkt);
if (res < 0) {
destroy_kex(kex);
destroy_pool(pkt->pool);
return -1;
}
destroy_pool(pkt->pool);
sent_newkeys = TRUE;
}
pkt = read_kex_packet(kex_pool, kex, proxy_sess->backend_ctrl_conn,
PROXY_SSH_DISCONNECT_PROTOCOL_ERROR, NULL, 1, PROXY_SSH_MSG_NEWKEYS);
/* If we didn't send our NEWKEYS message earlier, do it now. */
if (sent_newkeys == FALSE) {
struct proxy_ssh_packet *pkt2;
pr_trace_msg(trace_channel, 9, "sending NEWKEYS message to server");
/* Send our NEWKEYS reply. */
pkt2 = proxy_ssh_packet_create(kex_pool);
res = write_newkeys_reply(pkt2);
if (res < 0) {
destroy_kex(kex);
destroy_pool(pkt2->pool);
return -1;
}
res = proxy_ssh_packet_write(proxy_sess->backend_ctrl_conn, pkt2);
if (res < 0) {
destroy_kex(kex);
destroy_pool(pkt2->pool);
return -1;
}
destroy_pool(pkt2->pool);
sent_newkeys = TRUE;
}
if (use_strict_kex == TRUE) {
proxy_ssh_packet_reset_client_seqno();
proxy_ssh_packet_reset_server_seqno();
}
/* Last but certainly not least, set up the keys for encryption and
* authentication, based on H and K.
*/
pr_trace_msg(trace_channel, 9, "setting session keys");
if (set_session_keys(kex) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error setting session keys, disconnecting");
destroy_kex(kex);
PROXY_SSH_DISCONNECT_CONN(proxy_sess->backend_ctrl_conn,
PROXY_SSH_DISCONNECT_BY_APPLICATION, NULL);
}
/* We've now completed our KEX, possibly our first. */
kex_done_first_kex = TRUE;
destroy_pool(pkt->pool);
destroy_kex(kex);
return 0;
}
int proxy_ssh_kex_sess_free(void) {
kex_ds = NULL;
kex_verify_hostkeys = FALSE;
return 0;
}
int proxy_ssh_kex_sess_init(pool *p, struct proxy_ssh_datastore *ds,
int verify_hostkeys) {
(void) p;
kex_ds = ds;
kex_verify_hostkeys = verify_hostkeys;
return 0;
}
int proxy_ssh_kex_free(void) {
struct proxy_ssh_kex *first_kex, *rekey_kex;
/* destroy_kex() will set the kex_first_kex AND kex_rekey_kex pointers to
* null, so we need to keep our own copies of those pointers here.
*/
first_kex = kex_first_kex;
rekey_kex = kex_rekey_kex;
if (first_kex != NULL) {
destroy_kex(first_kex);
}
if (rekey_kex != NULL) {
destroy_kex(rekey_kex);
}
if (kex_pool != NULL) {
destroy_pool(kex_pool);
kex_pool = NULL;
}
return 0;
}
int proxy_ssh_kex_init(pool *p, const char *client_version,
const char *server_version) {
/* If we are called with client_version and server_version both NULL,
* then we're setting up for a rekey. We can destroy/create the Kex
* pool in that case. But not otherwise.
*/
if (client_version == NULL &&
server_version == NULL) {
if (kex_pool != NULL) {
destroy_pool(kex_pool);
kex_pool = NULL;
}
}
if (kex_pool == NULL) {
kex_pool = make_sub_pool(p);
pr_pool_tag(kex_pool, "Proxy SSH Kex Pool");
}
/* Save the client and server versions, the first time through. They
* will be used for any future rekey KEXINIT exchanges.
*/
if (client_version != NULL &&
kex_client_version == NULL) {
kex_client_version = pstrdup(proxy_pool, client_version);
}
if (server_version != NULL &&
kex_server_version == NULL) {
kex_server_version = pstrdup(proxy_pool, server_version);
}
if (client_version == NULL &&
server_version == NULL) {
pr_trace_msg(trace_channel, 19, "preparing for rekey");
kex_rekey_kex = create_kex(kex_pool);
kex_sent_kexinit = FALSE;
}
return 0;
}
int proxy_ssh_kex_send_first_kexinit(pool *p,
const struct proxy_session *proxy_sess) {
struct proxy_ssh_packet *pkt;
int res;
if (kex_pool == NULL) {
kex_pool = make_sub_pool(p);
pr_pool_tag(kex_pool, "Proxy SSH Kex Pool");
}
/* We have just connected to the server. We want to send our version
* ID string _and_ the KEXINIT in the same TCP packet, and save a
* TCP round trip (one TCP ACK for both messages, rather than one ACK
* per message). The packet API will automatically send the version
* ID string along with the first packet we send; we just have to
* send a packet, and the KEXINIT is the first one in the protocol.
*/
kex_first_kex = create_kex(kex_pool);
pkt = proxy_ssh_packet_create(kex_pool);
res = write_kexinit(pkt, kex_first_kex);
if (res < 0) {
destroy_kex(kex_first_kex);
destroy_pool(pkt->pool);
return -1;
}
pr_trace_msg(trace_channel, 9, "sending KEXINIT message to server");
res = proxy_ssh_packet_write(proxy_sess->backend_ctrl_conn, pkt);
if (res < 0) {
destroy_kex(kex_first_kex);
destroy_pool(pkt->pool);
return -1;
}
kex_sent_kexinit = TRUE;
destroy_pool(pkt->pool);
return 0;
}
#endif /* PR_USE_OPENSSL */
proftpd-mod_proxy-0.9.5/lib/proxy/ssh/keys.c 0000664 0000000 0000000 00000473521 14757370167 0021121 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH key mgmt (keys)
* Copyright (c) 2021-2023 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "proxy/ssh/msg.h"
#include "proxy/ssh/packet.h"
#include "proxy/ssh/crypto.h"
#include "proxy/ssh/keys.h"
#include "proxy/ssh/agent.h"
#include "proxy/ssh/interop.h"
#include "proxy/ssh/bcrypt.h"
#if defined(PR_USE_OPENSSL)
#if defined(PR_USE_SODIUM)
# include
#endif /* PR_USE_SODIUM */
#include
#include
#include
#if defined(HAVE_X448_OPENSSL) && defined(HAVE_SHA512_OPENSSL)
# define CURVE448_SIZE 56
#endif /* HAVE_X448_OPENSSL and HAVE_SHA512_OPENSSL */
extern xaset_t *server_list;
/* Note: Should this size be made bigger, in light of larger hostkeys? */
#define PROXY_SSH_DEFAULT_HOSTKEY_SZ 4096
#define PROXY_SSH_MAX_SIG_SZ 4096
struct proxy_ssh_hostkey {
enum proxy_ssh_key_type_e key_type;
EVP_PKEY *pkey;
/* Non-OpenSSL keys */
unsigned char *ed25519_public_key;
unsigned long long ed25519_public_keylen;
unsigned char *ed25519_secret_key;
unsigned long long ed25519_secret_keylen;
unsigned char *ed448_public_key;
unsigned long long ed448_public_keylen;
unsigned char *ed448_secret_key;
unsigned long long ed448_secret_keylen;
const unsigned char *key_data;
uint32_t key_datalen;
/* This will usually not be null; if the key was obtained from a local
* file, this will point to that file.
*/
const char *file_path;
/* This will usually be null; if the key was obtained from an agent,
* this point will point to the Unix domain socket to use for talking
* to that agent, e.g. for data signing requests.
*/
const char *agent_path;
};
static struct proxy_ssh_hostkey *dsa_hostkey = NULL;
static struct proxy_ssh_hostkey *rsa_hostkey = NULL;
#if defined(PR_USE_OPENSSL_ECC)
static struct proxy_ssh_hostkey *ecdsa256_hostkey = NULL;
static struct proxy_ssh_hostkey *ecdsa384_hostkey = NULL;
static struct proxy_ssh_hostkey *ecdsa521_hostkey = NULL;
#endif /* PR_USE_OPENSSL_ECC */
#if defined(PR_USE_SODIUM)
static struct proxy_ssh_hostkey *ed25519_hostkey = NULL;
#endif /* PR_USE_SODIUM */
#if defined(HAVE_X448_OPENSSL)
static struct proxy_ssh_hostkey *ed448_hostkey = NULL;
#endif /* HAVE_X448_OPENSSL */
static const char *passphrase_provider = NULL;
struct proxy_ssh_pkey {
struct proxy_ssh_pkey *next;
size_t pkeysz;
char *client_pkey;
void *client_pkey_ptr;
server_rec *server;
};
#define PROXY_SSH_PASSPHRASE_TIMEOUT 10
static struct proxy_ssh_pkey *pkey_list = NULL;
static unsigned int npkeys = 0;
static struct proxy_ssh_pkey *client_pkey = NULL;
struct proxy_ssh_pkey_data {
server_rec *s;
const char *path;
char *buf;
size_t buflen, bufsz;
const char *prompt;
};
/* Public key files start with "BEGIN ... PUBLIC KEY" and "END ... PUBLIC KEY"
* lines. Note that the "..." can be multiple different values ("RSA", "SSH2",
* etc).
*/
#define PROXY_SSH_PUBLICKEY_BEGIN "BEGIN PUBLIC KEY"
#define PROXY_SSH_PUBLICKEY_BEGIN_LEN (sizeof(PROXY_SSH_PUBLICKEY_BEGIN) - 1)
#define PROXY_SSH_PUBLICKEY_END "END PUBLIC KEY"
#define PROXY_SSH_PUBLICKEY_END_LEN (sizeof(PROXY_SSH_PUBLICKEY_BEGIN) - 1)
/* OpenSSH's homegrown private key file format.
*
* See the PROTOCOL.key file in the OpenSSH source distribution for details
* on their homegrown private key format. See also the implementations in
* sskey.c#sshkey_private_to_blob2 (for writing private keys) and
* sshkey.c#sshkey_parse_private2 (for reading private keys). The values
* for different encryption ciphers are in the `ciphers[]` table in cipher.c.
*/
#define PROXY_SSH_OPENSSH_BEGIN "-----BEGIN OPENSSH PRIVATE KEY-----\n"
#define PROXY_SSH_OPENSSH_END "-----END OPENSSH PRIVATE KEY-----\n"
#define PROXY_SSH_OPENSSH_BEGIN_LEN (sizeof(PROXY_SSH_OPENSSH_BEGIN) - 1)
#define PROXY_SSH_OPENSSH_END_LEN (sizeof(PROXY_SSH_OPENSSH_END) - 1)
#define PROXY_SSH_OPENSSH_KDFNAME "bcrypt"
#define PROXY_SSH_OPENSSH_MAGIC "openssh-key-v1"
/* Encryption cipher info. */
struct openssh_cipher {
const char *algo;
uint32_t blocksz;
uint32_t key_len;
uint32_t iv_len;
uint32_t auth_len;
const EVP_CIPHER *cipher;
const EVP_CIPHER *(*get_cipher)(void);
};
static struct openssh_cipher ciphers[] = {
{ "none", 8, 0, 0, 0, NULL, EVP_enc_null },
{ "aes256-cbc", 16, 32, 16, 0, NULL, EVP_aes_256_cbc },
#if defined(HAVE_EVP_AES_256_CTR_OPENSSL)
{ "aes256-ctr", 16, 32, 16, 0, NULL, EVP_aes_256_ctr },
#else
{ "aes256-ctr", 16, 32, 16, 0, NULL, NULL },
#endif /* HAVE_EVP_AES_256_CTR_OPENSSL */
{ NULL, 0, 0, 0, 0, NULL, NULL }
};
static int handle_ed448_hostkey(pool *p, const unsigned char *key_data,
uint32_t key_datalen, const char *file_path);
static int read_openssh_private_key(pool *p, const char *path, int fd,
const char *passphrase, enum proxy_ssh_key_type_e *key_type,
EVP_PKEY **pkey, unsigned char **key, uint32_t *keylen);
static const char *trace_channel = "proxy.ssh.keys";
static void prepare_provider_fds(int stdout_fd, int stderr_fd) {
long nfiles = 0;
register unsigned int i = 0;
struct rlimit rlim;
if (stdout_fd != STDOUT_FILENO) {
if (dup2(stdout_fd, STDOUT_FILENO) < 0) {
pr_log_debug(DEBUG0, MOD_PROXY_VERSION
": error duping fd %d to stdout: %s", stdout_fd, strerror(errno));
}
(void) close(stdout_fd);
}
if (stderr_fd != STDERR_FILENO) {
if (dup2(stderr_fd, STDERR_FILENO) < 0) {
pr_log_debug(DEBUG0, MOD_PROXY_VERSION
": error duping fd %d to stderr: %s", stderr_fd, strerror(errno));
}
(void) close(stderr_fd);
}
/* Make sure not to pass on open file descriptors. For stdout and stderr,
* we dup some pipes, so that we can capture what the command may write
* to stdout or stderr. The stderr output will be logged to the SFTPLog.
*
* First, use getrlimit() to obtain the maximum number of open files
* for this process -- then close that number.
*/
#if defined(RLIMIT_NOFILE) || defined(RLIMIT_OFILE)
# if defined(RLIMIT_NOFILE)
if (getrlimit(RLIMIT_NOFILE, &rlim) < 0) {
# elif defined(RLIMIT_OFILE)
if (getrlimit(RLIMIT_OFILE, &rlim) < 0) {
# endif
/* Ignore ENOSYS (and EPERM, since some libc's use this as ENOSYS). */
if (errno != ENOSYS &&
errno != EPERM) {
pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": getrlimit error: %s",
strerror(errno));
}
/* Pick some arbitrary high number. */
nfiles = 255;
} else {
nfiles = (unsigned long) rlim.rlim_max;
}
#else /* no RLIMIT_NOFILE or RLIMIT_OFILE */
nfiles = 255;
#endif
/* Appears that on some platforms (e.g. Solaris, Mac OSX), having too
* high of an fd value can lead to undesirable behavior for some reason.
* Need to track down why; the behavior I saw was the inability of
* select() to work properly on the stdout/stderr fds attached to the
* exec'd script.
*/
if (nfiles > 255) {
nfiles = 255;
}
if (nfiles < 0) {
/* Yes, using a long for the nfiles variable is not quite kosher; it should
* be an unsigned type, otherwise a large limit (say, RLIMIT_INFINITY)
* might overflow the data type. In that case, though, we want to know
* about it -- and using a signed type, we will know if the overflowed
* value is a negative number. Chances are we do NOT want to be closing
* fds whose value is as high as they can possibly get; that's too many
* fds to iterate over. Long story short, using a long int is just fine.
*/
nfiles = 255;
}
/* Close the "non-standard" file descriptors. */
for (i = 3; i < nfiles; i++) {
pr_signals_handle();
(void) close(i);
}
return;
}
static void prepare_provider_pipes(int *stdout_pipe, int *stderr_pipe) {
if (pipe(stdout_pipe) < 0) {
pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": error opening stdout pipe: %s",
strerror(errno));
stdout_pipe[0] = -1;
stdout_pipe[1] = STDOUT_FILENO;
} else {
if (fcntl(stdout_pipe[0], F_SETFD, FD_CLOEXEC) < 0) {
pr_log_debug(DEBUG0, MOD_PROXY_VERSION
": error setting close-on-exec flag on stdout pipe read fd: %s",
strerror(errno));
}
if (fcntl(stdout_pipe[1], F_SETFD, 0) < 0) {
pr_log_debug(DEBUG0, MOD_PROXY_VERSION
": error setting close-on-exec flag on stdout pipe write fd: %s",
strerror(errno));
}
}
if (pipe(stderr_pipe) < 0) {
pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": error opening stderr pipe: %s",
strerror(errno));
stderr_pipe[0] = -1;
stderr_pipe[1] = STDERR_FILENO;
} else {
if (fcntl(stderr_pipe[0], F_SETFD, FD_CLOEXEC) < 0) {
pr_log_debug(DEBUG0, MOD_PROXY_VERSION
": error setting close-on-exec flag on stderr pipe read fd: %s",
strerror(errno));
}
if (fcntl(stderr_pipe[1], F_SETFD, 0) < 0) {
pr_log_debug(DEBUG0, MOD_PROXY_VERSION
": error setting close-on-exec flag on stderr pipe write fd: %s",
strerror(errno));
}
}
}
static int exec_passphrase_provider(server_rec *s, char *buf, int buflen,
const char *path) {
pid_t pid;
int status;
int stdout_pipe[2], stderr_pipe[2];
struct sigaction sa_ignore, sa_intr, sa_quit;
sigset_t set_chldmask, set_save;
/* Prepare signal dispositions. */
sa_ignore.sa_handler = SIG_IGN;
sigemptyset(&sa_ignore.sa_mask);
sa_ignore.sa_flags = 0;
if (sigaction(SIGINT, &sa_ignore, &sa_intr) < 0) {
return -1;
}
if (sigaction(SIGQUIT, &sa_ignore, &sa_quit) < 0) {
return -1;
}
sigemptyset(&set_chldmask);
sigaddset(&set_chldmask, SIGCHLD);
if (sigprocmask(SIG_BLOCK, &set_chldmask, &set_save) < 0) {
return -1;
}
prepare_provider_pipes(stdout_pipe, stderr_pipe);
pid = fork();
if (pid < 0) {
int xerrno = errno;
pr_log_pri(PR_LOG_ALERT,
MOD_PROXY_VERSION ": error: unable to fork: %s", strerror(xerrno));
errno = xerrno;
status = -1;
} else if (pid == 0) {
char nbuf[32];
pool *tmp_pool;
char *stdin_argv[4];
/* Child process */
session.pid = getpid();
/* Note: there is no need to clean up this temporary pool, as we've
* forked. If the exec call succeeds, this child process will exit
* normally, and its process space recovered by the OS. If the exec
* call fails, we still exit, and the process space is recovered by
* the OS. Either way, the memory will be cleaned up without need for
* us to do it explicitly (unless one wanted to be pedantic about it,
* of course).
*/
tmp_pool = make_sub_pool(s->pool);
/* Restore previous signal actions. */
sigaction(SIGINT, &sa_intr, NULL);
sigaction(SIGQUIT, &sa_quit, NULL);
sigprocmask(SIG_SETMASK, &set_save, NULL);
stdin_argv[0] = pstrdup(tmp_pool, passphrase_provider);
memset(nbuf, '\0', sizeof(nbuf));
pr_snprintf(nbuf, sizeof(nbuf)-1, "%u", (unsigned int) s->ServerPort);
nbuf[sizeof(nbuf)-1] = '\0';
stdin_argv[1] = pstrcat(tmp_pool, s->ServerName, ":", nbuf, NULL);
stdin_argv[2] = pstrdup(tmp_pool, path);
stdin_argv[3] = NULL;
PRIVS_ROOT
pr_log_debug(DEBUG6, MOD_PROXY_VERSION
": executing '%s' with uid %lu (euid %lu), gid %lu (egid %lu)",
passphrase_provider,
(unsigned long) getuid(), (unsigned long) geteuid(),
(unsigned long) getgid(), (unsigned long) getegid());
/* Prepare the file descriptors that the process will inherit. */
prepare_provider_fds(stdout_pipe[1], stderr_pipe[1]);
errno = 0;
execv(passphrase_provider, stdin_argv);
/* Since all previous file descriptors (including those for log files)
* have been closed, and root privs have been revoked, there's little
* chance of directing a message of execv() failure to proftpd's log
* files. execv() only returns if there's an error; the only way we
* can signal this to the waiting parent process is to exit with a
* non-zero value (the value of errno will do nicely).
*/
exit(errno);
} else {
int res;
int maxfd = -1, fds, send_sigterm = 1;
fd_set readfds;
time_t start_time = time(NULL);
struct timeval tv;
/* Parent process */
close(stdout_pipe[1]);
stdout_pipe[1] = -1;
close(stderr_pipe[1]);
stderr_pipe[1] = -1;
if (stdout_pipe[0] > maxfd) {
maxfd = stdout_pipe[0];
}
if (stderr_pipe[0] > maxfd) {
maxfd = stderr_pipe[0];
}
res = waitpid(pid, &status, WNOHANG);
while (res <= 0) {
if (res < 0) {
if (errno != EINTR) {
pr_log_debug(DEBUG2, MOD_PROXY_VERSION
": passphrase provider error: unable to wait for pid %u: %s",
(unsigned int) pid, strerror(errno));
status = -1;
break;
} else {
pr_signals_handle();
continue;
}
}
/* Check the time elapsed since we started. */
if ((time(NULL) - start_time) > PROXY_SSH_PASSPHRASE_TIMEOUT) {
/* Send TERM, the first time, to be polite. */
if (send_sigterm) {
send_sigterm = 0;
pr_log_debug(DEBUG6, MOD_PROXY_VERSION
": '%s' has exceeded the timeout (%lu seconds), sending "
"SIGTERM (signal %d)", passphrase_provider,
(unsigned long) PROXY_SSH_PASSPHRASE_TIMEOUT, SIGTERM);
kill(pid, SIGTERM);
} else {
/* The child is still around? Terminate with extreme prejudice. */
pr_log_debug(DEBUG6, MOD_PROXY_VERSION
": '%s' has exceeded the timeout (%lu seconds), sending "
"SIGKILL (signal %d)", passphrase_provider,
(unsigned long) PROXY_SSH_PASSPHRASE_TIMEOUT, SIGKILL);
kill(pid, SIGKILL);
}
}
/* Select on the pipe read fds, to see if the child has anything
* to tell us.
*/
FD_ZERO(&readfds);
FD_SET(stdout_pipe[0], &readfds);
FD_SET(stderr_pipe[0], &readfds);
/* Note: this delay should be configurable somehow. */
tv.tv_sec = 2L;
tv.tv_usec = 0L;
fds = select(maxfd + 1, &readfds, NULL, NULL, &tv);
if (fds == -1 &&
errno == EINTR) {
pr_signals_handle();
}
if (fds > 0) {
/* The child sent us something. How thoughtful. */
if (FD_ISSET(stdout_pipe[0], &readfds)) {
res = read(stdout_pipe[0], buf, buflen);
if (res > 0) {
buf[buflen-1] = '\0';
while (res &&
(buf[res-1] == '\r' ||
buf[res-1] == '\n')) {
pr_signals_handle();
res--;
}
buf[res] = '\0';
} else if (res < 0) {
pr_log_debug(DEBUG2, MOD_PROXY_VERSION
": error reading stdout from '%s': %s",
passphrase_provider, strerror(errno));
}
}
if (FD_ISSET(stderr_pipe[0], &readfds)) {
long stderrlen, stderrsz;
char *stderrbuf;
pool *tmp_pool = make_sub_pool(s->pool);
stderrbuf = pr_fsio_getpipebuf(tmp_pool, stderr_pipe[0], &stderrsz);
memset(stderrbuf, '\0', stderrsz);
stderrlen = read(stderr_pipe[0], stderrbuf, stderrsz-1);
if (stderrlen > 0) {
while (stderrlen &&
(stderrbuf[stderrlen-1] == '\r' ||
stderrbuf[stderrlen-1] == '\n')) {
stderrlen--;
}
stderrbuf[stderrlen] = '\0';
pr_log_debug(DEBUG5, MOD_PROXY_VERSION
": stderr from '%s': %s", passphrase_provider, stderrbuf);
} else if (res < 0) {
pr_log_debug(DEBUG2, MOD_PROXY_VERSION
": error reading stderr from '%s': %s",
passphrase_provider, strerror(errno));
}
destroy_pool(tmp_pool);
tmp_pool = NULL;
}
}
res = waitpid(pid, &status, WNOHANG);
}
}
/* Restore the previous signal actions. */
if (sigaction(SIGINT, &sa_intr, NULL) < 0) {
return -1;
}
if (sigaction(SIGQUIT, &sa_quit, NULL) < 0) {
return -1;
}
if (sigprocmask(SIG_SETMASK, &set_save, NULL) < 0) {
return -1;
}
if (WIFSIGNALED(status)) {
pr_log_debug(DEBUG2, MOD_PROXY_VERSION ": '%s' died from signal %d",
passphrase_provider, WTERMSIG(status));
errno = EPERM;
return -1;
}
return 0;
}
/* Return the size of a page on this architecture. */
static size_t get_pagesz(void) {
long pagesz;
#if defined(_SC_PAGESIZE)
pagesz = sysconf(_SC_PAGESIZE);
#elif defined(_SC_PAGE_SIZE)
pagesz = sysconf(_SC_PAGE_SIZE);
#else
/* Default to using OpenSSL's defined buffer size for PEM files. */
pagesz = PEM_BUFSIZE;
#endif /* !_SC_PAGESIZE and !_SC_PAGE_SIZE */
return pagesz;
}
/* Return a page-aligned pointer to memory of at least the given size. */
static char *get_page(size_t sz, void **ptr) {
void *d;
long pagesz = get_pagesz(), p;
d = calloc(1, sz + (pagesz-1));
if (d == NULL) {
pr_log_pri(PR_LOG_ALERT, MOD_PROXY_VERSION ": Out of memory!");
exit(1);
}
*ptr = d;
p = ((long) d + (pagesz-1)) &~ (pagesz-1);
return ((char *) p);
}
static unsigned char *decode_base64(pool *p, unsigned char *text,
size_t text_len, size_t *data_len) {
unsigned char *data = NULL;
int have_padding = FALSE, res;
/* Due to Base64's padding, we need to detect if the last block was padded
* with zeros; we do this by looking for '=' characters at the end of the
* text being decoded. If we see these characters, then we will "trim" off
* any trailing zero values in the decoded data, on the ASSUMPTION that they
* are the auto-added padding bytes.
*/
if (text[text_len-1] == '=') {
have_padding = TRUE;
}
data = pcalloc(p, text_len);
res = EVP_DecodeBlock((unsigned char *) data, (unsigned char *) text,
(int) text_len);
if (res <= 0) {
/* Base64-decoding error. */
errno = EINVAL;
return NULL;
}
if (have_padding == TRUE) {
/* Assume that only one or two zero bytes of padding were added. */
if (data[res-1] == '\0') {
res -= 1;
if (data[res-1] == '\0') {
res -= 1;
}
}
}
*data_len = (size_t) res;
return data;
}
static int is_public_key(int fd) {
struct stat st;
char begin_buf[PROXY_SSH_PUBLICKEY_BEGIN_LEN+20];
ssize_t len;
off_t minsz;
if (fstat(fd, &st) < 0) {
return -1;
}
minsz = PROXY_SSH_PUBLICKEY_BEGIN_LEN + PROXY_SSH_PUBLICKEY_END_LEN;
if (st.st_size < minsz) {
return FALSE;
}
len = pread(fd, begin_buf, sizeof(begin_buf), 0);
if (len != sizeof(begin_buf)) {
return FALSE;
}
begin_buf[len-1] = '\0';
if (strstr(begin_buf, "PUBLIC KEY") == NULL) {
return FALSE;
}
if (strstr(begin_buf, "BEGIN") == NULL) {
return FALSE;
}
return TRUE;
}
static int is_openssh_private_key(int fd) {
struct stat st;
char begin_buf[PROXY_SSH_OPENSSH_BEGIN_LEN], end_buf[PROXY_SSH_OPENSSH_END_LEN];
ssize_t len;
off_t minsz;
if (fstat(fd, &st) < 0) {
return -1;
}
minsz = PROXY_SSH_OPENSSH_BEGIN_LEN + PROXY_SSH_OPENSSH_END_LEN;
if (st.st_size < minsz) {
return FALSE;
}
len = pread(fd, begin_buf, sizeof(begin_buf), 0);
if (len != sizeof(begin_buf)) {
return FALSE;
}
if (memcmp(begin_buf, PROXY_SSH_OPENSSH_BEGIN, PROXY_SSH_OPENSSH_BEGIN_LEN) != 0) {
return FALSE;
}
len = pread(fd, end_buf, sizeof(end_buf),
st.st_size - PROXY_SSH_OPENSSH_END_LEN);
if (len != sizeof(end_buf)) {
return FALSE;
}
if (memcmp(end_buf, PROXY_SSH_OPENSSH_END, PROXY_SSH_OPENSSH_END_LEN) != 0) {
return FALSE;
}
return TRUE;
}
static int get_passphrase_cb(char *buf, int buflen, int rwflag, void *d) {
static int need_banner = TRUE;
struct proxy_ssh_pkey_data *pdata = d;
if (passphrase_provider == NULL) {
register unsigned int attempt;
size_t pwlen = 0;
pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": requesting passphrase from admin");
if (need_banner) {
fprintf(stderr, "\nPlease provide passphrase for the encrypted host key:\n");
need_banner = FALSE;
}
/* You get three attempts at entering the passphrase correctly. */
for (attempt = 0; attempt < 3; attempt++) {
int res;
/* Always handle signals in a loop. */
pr_signals_handle();
res = EVP_read_pw_string(buf, buflen, pdata->prompt, TRUE);
/* A return value of zero from EVP_read_pw_string() means success; -1
* means a system error occurred, and 1 means user interaction problems.
*/
if (res != 0) {
fprintf(stderr, "\nPassphrases do not match. Please try again.\n");
continue;
}
/* Ensure that the buffer is NUL-terminated. */
buf[buflen-1] = '\0';
pwlen = strlen(buf);
if (pwlen < 1) {
fprintf(stderr, "Error: passphrase must be at least one character\n");
} else {
sstrncpy(pdata->buf, buf, pdata->bufsz);
pdata->buflen = pwlen;
return pwlen;
}
}
} else {
pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": requesting passphrase from '%s'",
passphrase_provider);
if (exec_passphrase_provider(pdata->s, buf, buflen, pdata->path) < 0) {
pr_log_debug(DEBUG0, MOD_PROXY_VERSION
": error obtaining passphrase from '%s': %s",
passphrase_provider, strerror(errno));
} else {
size_t pwlen;
/* Ensure that the buffer is NUL-terminated. */
buf[buflen-1] = '\0';
pwlen = strlen(buf);
sstrncpy(pdata->buf, buf, pdata->bufsz);
pdata->buflen = pwlen;
return pwlen;
}
}
#if OPENSSL_VERSION_NUMBER < 0x00908001
PEMerr(PEM_F_DEF_CALLBACK, PEM_R_PROBLEMS_GETTING_PASSWORD);
#else
PEMerr(PEM_F_PEM_DEF_CALLBACK, PEM_R_PROBLEMS_GETTING_PASSWORD);
#endif
pr_memscrub(buf, buflen);
return -1;
}
static void free_hostkey_bio(BIO *bio) {
char *data = NULL;
long datalen = 0;
/* "Rewind" the pointer to the start of the buffer, for scrubbing. */
BIO_reset(bio);
datalen = BIO_get_mem_data(bio, &data);
if (data != NULL &&
datalen > 0) {
pr_memscrub(data, datalen);
}
BIO_free(bio);
}
static BIO *load_hostkey_bio(pool *p, int fd) {
int res, xerrno;
BIO *bio = NULL, *readonly_bio = NULL;
struct stat st;
unsigned char *buf = NULL;
size_t bufsz;
char *data = NULL, *ptr = NULL;
long datalen = 0;
memset(&st, 0, sizeof(st));
res = fstat(fd, &st);
if (res < 0) {
return NULL;
}
bufsz = st.st_blksize;
buf = palloc(p, bufsz);
#if defined(PR_USE_OPENSSL_BIO_SECMEM)
bio = BIO_new(BIO_s_secmem());
#else
bio = BIO_new(BIO_s_mem());
#endif /* PR_USE_OPENSSL_BIO_SECMEM */
res = read(fd, buf, bufsz);
xerrno = errno;
if (res < 0) {
BIO_free(bio);
errno = xerrno;
return NULL;
}
while (res > 0) {
pr_signals_handle();
BIO_write(bio, buf, res);
pr_memscrub(buf, res);
res = read(fd, buf, bufsz);
xerrno = errno;
if (res < 0) {
BIO_free(bio);
errno = xerrno;
return NULL;
}
}
/* Now we create a read-only memory BIO for this hostkey data. This is
* specifically for the on-startup use case, where the admin might mistype
* the passphrase for a passphrase-protected hostkey, and need to retry
* the decryption process. The data in a normal read/write memory BIO is
* consumed from the BIO on read, meaning that that BIO would not be usable
* for a subsequent decryption attempt.
*/
datalen = BIO_get_mem_data(bio, &ptr);
if (ptr == NULL ||
datalen == 0) {
BIO_free(bio);
errno = EIO;
return NULL;
}
/* Make a copy of the data, so that we can destroy the original BIO without
* losing this data.
*/
data = palloc(p, datalen);
memcpy(data, ptr, datalen);
BIO_free(bio);
readonly_bio = BIO_new_mem_buf(data, datalen);
return readonly_bio;
}
static int get_passphrase(struct proxy_ssh_pkey *k, const char *path) {
pool *tmp_pool = NULL;
char prompt[256];
BIO *bio = NULL;
EVP_PKEY *pkey = NULL;
unsigned char *key_data = NULL;
uint32_t key_datalen = 0;
int fd, prompt_fd = -1, res, xerrno, openssh_format = FALSE,
public_key_format = FALSE;
struct proxy_ssh_pkey_data pdata;
register unsigned int attempt;
memset(prompt, '\0', sizeof(prompt));
res = pr_snprintf(prompt, sizeof(prompt)-1,
"Host key for the %s#%d (%s) server: ",
pr_netaddr_get_ipstr(k->server->addr), k->server->ServerPort,
k->server->ServerName);
prompt[res] = '\0';
prompt[sizeof(prompt)-1] = '\0';
PRIVS_ROOT
fd = open(path, O_RDONLY);
xerrno = errno;
PRIVS_RELINQUISH
if (fd < 0) {
SYSerr(SYS_F_FOPEN, xerrno);
errno = xerrno;
return -1;
}
/* Make sure the fd isn't one of the big three. */
if (fd <= STDERR_FILENO) {
res = pr_fs_get_usable_fd(fd);
if (res >= 0) {
(void) close(fd);
fd = res;
}
}
tmp_pool = make_sub_pool(proxy_pool);
pr_pool_tag(tmp_pool, "Proxy SFTP Passphrase pool");
public_key_format = is_public_key(fd);
if (public_key_format == TRUE) {
pr_trace_msg(trace_channel, 3, "hostkey file '%s' uses a public key format",
path);
(void) pr_log_pri(PR_LOG_WARNING, MOD_PROXY_VERSION
": unable to use public key '%s' for SFTPHostKey", path);
(void) close(fd);
destroy_pool(tmp_pool);
errno = EINVAL;
return -1;
}
openssh_format = is_openssh_private_key(fd);
if (openssh_format != TRUE) {
/* Rather than using OpenSSL's PEM_read_PrivateKey and the underlying C
* library's FILE routines, dealing with unbuffered file handles and
* byte-by-byte reads from OpenSSL, we instead provision the file data
* into a memory BIO, and let OpenSSL read from that.
*
* This allows OpenSSL to maintain its byte-by-byte reads, while we read
* the file data using filesystem block-sized reads.
*/
bio = load_hostkey_bio(tmp_pool, fd);
if (bio == NULL) {
xerrno = errno;
(void) close(fd);
destroy_pool(tmp_pool);
SYSerr(SYS_F_FOPEN, xerrno);
errno = xerrno;
return -1;
}
} else {
pr_trace_msg(trace_channel, 9,
"handling host key '%s' as an OpenSSH-formatted private key", path);
}
k->client_pkey = get_page(PEM_BUFSIZE, &k->client_pkey_ptr);
if (k->client_pkey == NULL) {
pr_log_pri(PR_LOG_ALERT, MOD_PROXY_VERSION ": Out of memory!");
exit(1);
}
pdata.s = k->server;
pdata.buf = k->client_pkey;
pdata.buflen = 0;
pdata.bufsz = k->pkeysz;
pdata.path = path;
pdata.prompt = prompt;
/* Reconnect stderr to the term because proftpd connects stderr, earlier,
* to the general stderr logfile.
*/
prompt_fd = open("/dev/null", O_WRONLY);
if (prompt_fd == -1) {
/* This is an arbitrary, meaningless placeholder number. */
prompt_fd = 76;
}
dup2(STDERR_FILENO, prompt_fd);
dup2(STDOUT_FILENO, STDERR_FILENO);
/* The user gets three tries to enter the correct passphrase. */
for (attempt = 0; attempt < 3; attempt++) {
/* Always handle signals in a loop. */
pr_signals_handle();
if (openssh_format == FALSE) {
pkey = PEM_read_bio_PrivateKey(bio, NULL, get_passphrase_cb, &pdata);
if (pkey != NULL) {
break;
}
if (BIO_reset(bio) < 0) {
pr_trace_msg(trace_channel, 3,
"error resetting BIO for '%s': %s", path, strerror(errno));
}
} else {
char buf[PEM_BUFSIZE];
const char *passphrase;
enum proxy_ssh_key_type_e key_type = PROXY_SSH_KEY_UNKNOWN;
/* First we try with no passphrase. Failing that, we have to invoke the
* get_passphase_cb() callback ourselves for OpenSSH keys.
*/
if (attempt == 0) {
passphrase = pstrdup(tmp_pool, "");
res = read_openssh_private_key(tmp_pool, path, fd, passphrase,
&key_type, &pkey, &key_data, &key_datalen);
if (lseek(fd, 0, SEEK_SET) < 0) {
pr_trace_msg(trace_channel, 3, "error rewinding fd %d for '%s': %s",
fd, path, strerror(errno));
}
if (res == 0) {
break;
}
}
res = get_passphrase_cb(buf, PEM_BUFSIZE, 0, &pdata);
if (res > 0) {
passphrase = pdata.buf;
res = read_openssh_private_key(tmp_pool, path, fd, passphrase,
&key_type, &pkey, &key_data, &key_datalen);
if (res == 0) {
break;
}
if (lseek(fd, 0, SEEK_SET) < 0) {
pr_trace_msg(trace_channel, 3, "error rewinding fd %d for '%s': %s",
fd, path, strerror(errno));
}
} else {
pr_trace_msg(trace_channel, 2,
"error reading passphrase for OpenSSH key: %s",
proxy_ssh_crypto_get_errors());
}
}
ERR_clear_error();
fprintf(stderr, "\nWrong passphrase for this key. Please try again.\n");
}
if (bio != NULL) {
free_hostkey_bio(bio);
}
/* Restore the normal stderr logging. */
(void) dup2(prompt_fd, STDERR_FILENO);
(void) close(prompt_fd);
if (pkey == NULL &&
key_data == NULL) {
return -1;
}
if (pkey != NULL) {
EVP_PKEY_free(pkey);
}
if (key_data != NULL) {
pr_memscrub(key_data, key_datalen);
}
destroy_pool(tmp_pool);
if (pdata.buflen > 0) {
#if OPENSSL_VERSION_NUMBER >= 0x000905000L
/* Use the obtained passphrase as additional entropy, ostensibly
* unknown to attackers who may be watching the network, for
* OpenSSL's PRNG.
*
* Human language gives about 2-3 bits of entropy per byte (RFC1750).
*/
RAND_add(pdata.buf, pdata.buflen, pdata.buflen * 0.25);
#endif
#ifdef HAVE_MLOCK
PRIVS_ROOT
if (mlock(k->client_pkey, k->pkeysz) < 0) {
pr_log_debug(DEBUG1, MOD_PROXY_VERSION
": error locking passphrase into memory: %s", strerror(errno));
} else {
pr_log_debug(DEBUG1, MOD_PROXY_VERSION ": passphrase locked into memory");
}
PRIVS_RELINQUISH
#endif
}
return 0;
}
static struct proxy_ssh_pkey *lookup_pkey(void) {
struct proxy_ssh_pkey *k, *pkey = NULL;
for (k = pkey_list; k; k = k->next) {
/* If this pkey matches the current server_rec, mark it and move on. */
if (k->server == main_server) {
#ifdef HAVE_MLOCK
/* mlock() the passphrase memory areas again; page locks are not
* inherited across forks.
*/
PRIVS_ROOT
if (k->client_pkey != NULL) {
if (mlock(k->client_pkey, k->pkeysz) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error locking passphrase into memory: %s", strerror(errno));
}
}
PRIVS_RELINQUISH
#endif /* HAVE_MLOCK */
pkey = k;
continue;
}
/* Otherwise, scrub the passphrase's memory areas. */
if (k->client_pkey != NULL) {
pr_memscrub(k->client_pkey, k->pkeysz);
free(k->client_pkey_ptr);
k->client_pkey = k->client_pkey_ptr = NULL;
}
}
return pkey;
}
static void scrub_pkeys(void) {
struct proxy_ssh_pkey *k;
if (pkey_list == NULL) {
return;
}
/* Scrub and free all passphrases in memory. */
pr_log_debug(DEBUG5, MOD_PROXY_VERSION ": scrubbing %u %s from memory",
npkeys, npkeys != 1 ? "passphrases" : "passphrase");
for (k = pkey_list; k; k = k->next) {
if (k->client_pkey != NULL) {
pr_memscrub(k->client_pkey, k->pkeysz);
free(k->client_pkey_ptr);
k->client_pkey = k->client_pkey_ptr = NULL;
}
}
pkey_list = NULL;
npkeys = 0;
}
static int pkey_cb(char *buf, int buflen, int rwflag, void *d) {
struct proxy_ssh_pkey *k;
if (d == NULL) {
return 0;
}
k = (struct proxy_ssh_pkey *) d;
if (k->client_pkey != NULL) {
sstrncpy(buf, k->client_pkey, buflen);
buf[buflen - 1] = '\0';
return strlen(buf);
}
return 0;
}
static int has_req_perms(int fd, const char *path) {
struct stat st;
if (fstat(fd, &st) < 0) {
return -1;
}
if (st.st_mode & (S_IRWXG|S_IRWXO)) {
errno = EACCES;
return -1;
}
return 0;
}
static uint32_t read_pkey_from_data(pool *p, unsigned char *pkey_data,
uint32_t pkey_datalen, EVP_PKEY **pkey, enum proxy_ssh_key_type_e *key_type,
int openssh_format) {
char *pkey_type = NULL;
uint32_t res, len = 0;
res = proxy_ssh_msg_read_string(p, &pkey_data, &pkey_datalen, &pkey_type);
if (res == 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error reading key: invalid/unsupported key format");
return 0;
}
len += res;
if (strcmp(pkey_type, "ssh-rsa") == 0) {
RSA *rsa;
const BIGNUM *rsa_e = NULL, *rsa_n = NULL, *rsa_d = NULL;
*pkey = EVP_PKEY_new();
if (*pkey == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error allocating EVP_PKEY: %s", proxy_ssh_crypto_get_errors());
return 0;
}
rsa = RSA_new();
if (rsa == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error allocating RSA: %s", proxy_ssh_crypto_get_errors());
EVP_PKEY_free(*pkey);
*pkey = NULL;
return 0;
}
res = proxy_ssh_msg_read_mpint(p, &pkey_data, &pkey_datalen, &rsa_e);
if (res == 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error reading key: invalid/unsupported key format");
RSA_free(rsa);
EVP_PKEY_free(*pkey);
*pkey = NULL;
return 0;
}
len += res;
res = proxy_ssh_msg_read_mpint(p, &pkey_data, &pkey_datalen, &rsa_n);
if (res == 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error reading key: invalid/unsupported key format");
RSA_free(rsa);
EVP_PKEY_free(*pkey);
*pkey = NULL;
return 0;
}
len += res;
if (openssh_format == TRUE) {
const BIGNUM *rsa_p, *rsa_q, *rsa_iqmp;
/* The OpenSSH private key format encodes more factors. */
res = proxy_ssh_msg_read_mpint(p, &pkey_data, &pkey_datalen, &rsa_d);
if (res == 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error reading key: invalid/unsupported key format");
RSA_free(rsa);
EVP_PKEY_free(*pkey);
*pkey = NULL;
return 0;
}
len += res;
/* RSA_get0_crt_params */
res = proxy_ssh_msg_read_mpint(p, &pkey_data, &pkey_datalen, &rsa_iqmp);
if (res == 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error reading key: invalid/unsupported key format");
RSA_free(rsa);
EVP_PKEY_free(*pkey);
*pkey = NULL;
return 0;
}
len += res;
/* RSA_get0_factors */
res = proxy_ssh_msg_read_mpint(p, &pkey_data, &pkey_datalen, &rsa_p);
if (res == 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error reading key: invalid/unsupported key format");
RSA_free(rsa);
EVP_PKEY_free(*pkey);
*pkey = NULL;
return 0;
}
len += res;
res = proxy_ssh_msg_read_mpint(p, &pkey_data, &pkey_datalen, &rsa_q);
if (res == 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error reading key: invalid/unsupported key format");
RSA_free(rsa);
EVP_PKEY_free(*pkey);
*pkey = NULL;
return 0;
}
len += res;
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
RSA_set0_crt_params(rsa, NULL, NULL, (BIGNUM *) rsa_iqmp);
RSA_set0_factors(rsa, (BIGNUM *) rsa_p, (BIGNUM *) rsa_q);
#else
rsa->iqmp = rsa_iqmp;
rsa->p = rsa_p;
rsa->q = rsa_q;
#endif /* prior to OpenSSL-1.1.0 */
/* Turns out that for OpenSSH formatted RSA keys, the 'e' and 'n' values
* are in the opposite order than the normal PEM format. Typical.
*/
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
RSA_set0_key(rsa, (BIGNUM *) rsa_e, (BIGNUM *) rsa_n, (BIGNUM *) rsa_d);
#else
rsa->e = rsa_n;
rsa->n = rsa_e;
rsa->d = rsa_d;
#endif /* prior to OpenSSL-1.1.0 */
} else {
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
RSA_set0_key(rsa, (BIGNUM *) rsa_n, (BIGNUM *) rsa_e, (BIGNUM *) rsa_d);
#else
rsa->e = rsa_e;
rsa->n = rsa_n;
rsa->d = rsa_d;
#endif /* prior to OpenSSL-1.1.0 */
}
if (EVP_PKEY_assign_RSA(*pkey, rsa) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error assigning RSA to EVP_PKEY: %s", proxy_ssh_crypto_get_errors());
RSA_free(rsa);
EVP_PKEY_free(*pkey);
*pkey = NULL;
return 0;
}
if (key_type != NULL) {
*key_type = PROXY_SSH_KEY_RSA;
}
} else if (strcmp(pkey_type, "ssh-dss") == 0) {
#if !defined(OPENSSL_NO_DSA)
DSA *dsa;
const BIGNUM *dsa_p, *dsa_q, *dsa_g, *dsa_pub_key, *dsa_priv_key = NULL;
*pkey = EVP_PKEY_new();
if (*pkey == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error allocating EVP_PKEY: %s", proxy_ssh_crypto_get_errors());
return 0;
}
dsa = DSA_new();
if (dsa == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error allocating DSA: %s", proxy_ssh_crypto_get_errors());
EVP_PKEY_free(*pkey);
*pkey = NULL;
return 0;
}
res = proxy_ssh_msg_read_mpint(p, &pkey_data, &pkey_datalen, &dsa_p);
if (res == 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error reading key: invalid/unsupported key format");
DSA_free(dsa);
EVP_PKEY_free(*pkey);
*pkey = NULL;
return 0;
}
len += res;
res = proxy_ssh_msg_read_mpint(p, &pkey_data, &pkey_datalen, &dsa_q);
if (res == 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error reading key: invalid/unsupported key format");
DSA_free(dsa);
EVP_PKEY_free(*pkey);
*pkey = NULL;
return 0;
}
len += res;
res = proxy_ssh_msg_read_mpint(p, &pkey_data, &pkey_datalen, &dsa_g);
if (res == 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error reading key: invalid/unsupported key format");
DSA_free(dsa);
EVP_PKEY_free(*pkey);
*pkey = NULL;
return 0;
}
len += res;
res = proxy_ssh_msg_read_mpint(p, &pkey_data, &pkey_datalen, &dsa_pub_key);
if (res == 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error reading key: invalid/unsupported key format");
DSA_free(dsa);
EVP_PKEY_free(*pkey);
*pkey = NULL;
return 0;
}
len += res;
if (openssh_format == TRUE) {
res = proxy_ssh_msg_read_mpint(p, &pkey_data, &pkey_datalen,
&dsa_priv_key);
if (res == 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error reading key: invalid/unsupported key format");
DSA_free(dsa);
EVP_PKEY_free(*pkey);
*pkey = NULL;
return 0;
}
len += res;
}
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
DSA_set0_pqg(dsa, (BIGNUM *) dsa_p, (BIGNUM *) dsa_q, (BIGNUM *) dsa_g);
DSA_set0_key(dsa, (BIGNUM *) dsa_pub_key, (BIGNUM *) dsa_priv_key);
#else
dsa->p = dsa_p;
dsa->q = dsa_q;
dsa->g = dsa_g;
dsa->pub_key = dsa_pub_key;
dsa->priv_key = dsa_priv_key;
#endif /* prior to OpenSSL-1.1.0 */
if (EVP_PKEY_assign_DSA(*pkey, dsa) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error assigning RSA to EVP_PKEY: %s", proxy_ssh_crypto_get_errors());
DSA_free(dsa);
EVP_PKEY_free(*pkey);
*pkey = NULL;
return 0;
}
if (key_type != NULL) {
*key_type = PROXY_SSH_KEY_DSA;
}
#else
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unsupported public key algorithm '%s'", pkey_type);
errno = EINVAL;
return 0;
#endif /* !OPENSSL_NO_DSA */
#ifdef PR_USE_OPENSSL_ECC
} else if (strcmp(pkey_type, "ecdsa-sha2-nistp256") == 0 ||
strcmp(pkey_type, "ecdsa-sha2-nistp384") == 0 ||
strcmp(pkey_type, "ecdsa-sha2-nistp521") == 0) {
EC_KEY *ec;
const char *curve_name;
const EC_GROUP *curve;
EC_POINT *point;
int ec_nid;
char *ptr = NULL;
res = proxy_ssh_msg_read_string(p, &pkey_data, &pkey_datalen, &ptr);
if (res == 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error reading key: invalid/unsupported key format");
return 0;
}
len += res;
curve_name = (const char *) ptr;
/* If the curve name does not match the last 8 characters of the
* public key type (which, in the case of ECDSA keys, contains the
* curve name), then it's definitely a mismatch.
*/
if (strncmp(pkey_type + 11, curve_name, 9) != 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"EC public key curve name '%s' does not match public key "
"algorithm '%s'", curve_name, pkey_type);
return 0;
}
if (strcmp(curve_name, "nistp256") == 0) {
ec_nid = NID_X9_62_prime256v1;
if (key_type != NULL) {
*key_type = PROXY_SSH_KEY_ECDSA_256;
}
} else if (strcmp(curve_name, "nistp384") == 0) {
ec_nid = NID_secp384r1;
if (key_type != NULL) {
*key_type = PROXY_SSH_KEY_ECDSA_384;
}
} else if (strcmp(curve_name, "nistp521") == 0) {
ec_nid = NID_secp521r1;
if (key_type != NULL) {
*key_type = PROXY_SSH_KEY_ECDSA_521;
}
} else {
ec_nid = -1;
}
ec = EC_KEY_new_by_curve_name(ec_nid);
if (ec == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error allocating EC_KEY for %s: %s", pkey_type,
proxy_ssh_crypto_get_errors());
return 0;
}
curve = EC_KEY_get0_group(ec);
point = EC_POINT_new(curve);
if (point == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error allocating EC_POINT for %s: %s", pkey_type,
proxy_ssh_crypto_get_errors());
EC_KEY_free(ec);
return 0;
}
res = proxy_ssh_msg_read_ecpoint(p, &pkey_data, &pkey_datalen, curve,
&point);
if (res == 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error reading key: invalid/unsupported key format");
EC_KEY_free(ec);
return 0;
}
len += res;
if (point == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error reading EC_POINT from public key data: %s", strerror(errno));
EC_POINT_free(point);
EC_KEY_free(ec);
return 0;
}
if (proxy_ssh_keys_validate_ecdsa_params(curve, point) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error validating EC public key: %s", strerror(errno));
EC_POINT_free(point);
EC_KEY_free(ec);
return 0;
}
if (EC_KEY_set_public_key(ec, point) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error setting public key on EC_KEY: %s",
proxy_ssh_crypto_get_errors());
EC_POINT_free(point);
EC_KEY_free(ec);
return 0;
}
if (openssh_format) {
const BIGNUM *ec_priv_key = NULL;
res = proxy_ssh_msg_read_mpint(p, &pkey_data, &pkey_datalen,
&ec_priv_key);
if (res == 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error reading key: invalid/unsupported key format");
EC_POINT_free(point);
EC_KEY_free(ec);
*pkey = NULL;
return 0;
}
len += res;
if (EC_KEY_set_private_key(ec, ec_priv_key) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error setting private key on EC_KEY: %s",
proxy_ssh_crypto_get_errors());
EC_POINT_free(point);
EC_KEY_free(ec);
return 0;
}
}
*pkey = EVP_PKEY_new();
if (*pkey == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error allocating EVP_PKEY: %s", proxy_ssh_crypto_get_errors());
EC_POINT_free(point);
EC_KEY_free(ec);
return 0;
}
if (EVP_PKEY_assign_EC_KEY(*pkey, ec) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error assigning ECDSA-256 to EVP_PKEY: %s",
proxy_ssh_crypto_get_errors());
EC_POINT_free(point);
EC_KEY_free(ec);
EVP_PKEY_free(*pkey);
*pkey = NULL;
return 0;
}
#endif /* PR_USE_OPENSSL_ECC */
#if defined(PR_USE_SODIUM)
} else if (strcmp(pkey_type, "ssh-ed25519") == 0) {
if (key_type != NULL) {
*key_type = PROXY_SSH_KEY_ED25519;
}
#endif /* PR_USE_SODIUM */
#if defined(HAVE_X448_OPENSSL)
} else if (strcmp(pkey_type, "ssh-ed448") == 0) {
if (key_type != NULL) {
*key_type = PROXY_SSH_KEY_ED448;
}
#endif /* HAVE_X448_OPENSSL */
} else {
pr_trace_msg(trace_channel, 3, "unsupported public key algorithm '%s'",
pkey_type);
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unsupported public key algorithm '%s'", pkey_type);
errno = EINVAL;
return 0;
}
return len;
}
static const char *get_pkey_type_desc(int pkey_type) {
const char *key_desc = NULL;
switch (pkey_type) {
#ifdef EVP_PKEY_NONE
case EVP_PKEY_NONE:
key_desc = "undefined";
break;
#endif
#ifdef EVP_PKEY_RSA
case EVP_PKEY_RSA:
key_desc = "RSA";
break;
#endif
#ifdef EVP_PKEY_DSA
case EVP_PKEY_DSA:
key_desc = "DSA";
break;
#endif
#ifdef EVP_PKEY_DH
case EVP_PKEY_DH:
key_desc = "DH";
break;
#endif
#ifdef EVP_PKEY_EC
case EVP_PKEY_EC:
key_desc = "ECC";
break;
#endif
default:
key_desc = "unknown";
}
return key_desc;
}
static const char *get_key_type_desc(enum proxy_ssh_key_type_e key_type) {
const char *key_desc = NULL;
switch (key_type) {
case PROXY_SSH_KEY_UNKNOWN:
key_desc = "unknown";
break;
case PROXY_SSH_KEY_DSA:
key_desc = "DSA";
break;
case PROXY_SSH_KEY_RSA:
key_desc = "RSA";
break;
case PROXY_SSH_KEY_ECDSA_256:
key_desc = "ECDSA256";
break;
case PROXY_SSH_KEY_ECDSA_384:
key_desc = "ECDSA384";
break;
case PROXY_SSH_KEY_ECDSA_521:
key_desc = "ECDSA521";
break;
case PROXY_SSH_KEY_ED25519:
key_desc = "ED25519";
break;
case PROXY_SSH_KEY_ED448:
key_desc = "ED448";
break;
default:
key_desc = "undefined";
break;
}
return key_desc;
}
#if defined(PR_USE_OPENSSL_ECC)
/* Make sure the given ECDSA private key is suitable for use. */
static int validate_ecdsa_private_key(const EC_KEY *ec) {
BN_CTX *bn_ctx;
BIGNUM *ec_order, *bn_tmp;
int ec_order_nbits, priv_key_nbits;
/* A BN_CTX is like our pools; we allocate one, use it to get any
* number of BIGNUM variables, and only have free up the BN_CTX when
* we're done, rather than all of the individual BIGNUMs.
*/
bn_ctx = BN_CTX_new();
if (bn_ctx == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error allocating BN_CTX: %s", proxy_ssh_crypto_get_errors());
return -1;
}
BN_CTX_start(bn_ctx);
ec_order = BN_CTX_get(bn_ctx);
if (ec_order == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error getting new BIGNUM from BN_CTX: %s",
proxy_ssh_crypto_get_errors());
BN_CTX_free(bn_ctx);
errno = EPERM;
return -1;
}
bn_tmp = BN_CTX_get(bn_ctx);
if (bn_tmp == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error getting new BIGNUM from BN_CTX: %s",
proxy_ssh_crypto_get_errors());
BN_CTX_free(bn_ctx);
errno = EPERM;
return -1;
}
/* Make sure that log2(private key) is greater than log2(EC order)/2. */
if (EC_GROUP_get_order(EC_KEY_get0_group(ec), ec_order, bn_ctx) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error getting the EC group order: %s", proxy_ssh_crypto_get_errors());
BN_CTX_free(bn_ctx);
errno = EPERM;
return -1;
}
priv_key_nbits = BN_num_bits(EC_KEY_get0_private_key(ec));
ec_order_nbits = BN_num_bits(ec_order);
if (priv_key_nbits <= (ec_order_nbits / 2)) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"ECDSA private key (%d bits) is too small, must be at "
"least %d bits", priv_key_nbits, ec_order_nbits);
BN_CTX_free(bn_ctx);
errno = EACCES;
return -1;
}
/* Ensure that the private key < (EC order - 1). */
if (BN_sub(bn_tmp, ec_order, BN_value_one()) == 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error subtracting one from EC group order: %s",
proxy_ssh_crypto_get_errors());
BN_CTX_free(bn_ctx);
errno = EPERM;
return -1;
}
if (BN_cmp(EC_KEY_get0_private_key(ec), bn_tmp) >= 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"ECDSA private key is greater than or equal to EC group order, "
"rejecting");
BN_CTX_free(bn_ctx);
errno = EACCES;
return -1;
}
BN_CTX_free(bn_ctx);
return 0;
}
enum proxy_ssh_key_type_e proxy_ssh_keys_get_key_type(const char *algo) {
enum proxy_ssh_key_type_e key_type = PROXY_SSH_KEY_UNKNOWN;
if (algo == NULL) {
return PROXY_SSH_KEY_UNKNOWN;
}
if (strcmp(algo, "ssh-dss") == 0) {
key_type = PROXY_SSH_KEY_DSA;
} else if (strcmp(algo, "ssh-rsa") == 0) {
key_type = PROXY_SSH_KEY_RSA;
} else if (strcmp(algo, "rsa-sha2-256") == 0) {
key_type = PROXY_SSH_KEY_RSA_SHA256;
} else if (strcmp(algo, "rsa-sha2-512") == 0) {
key_type = PROXY_SSH_KEY_RSA_SHA512;
} else if (strcmp(algo, "ecdsa-sha2-nistp256") == 0) {
key_type = PROXY_SSH_KEY_ECDSA_256;
} else if (strcmp(algo, "ecdsa-sha2-nistp384") == 0) {
key_type = PROXY_SSH_KEY_ECDSA_384;
} else if (strcmp(algo, "ecdsa-sha2-nistp521") == 0) {
key_type = PROXY_SSH_KEY_ECDSA_521;
} else if (strcmp(algo, "ssh-ed25519") == 0) {
key_type = PROXY_SSH_KEY_ED25519;
} else if (strcmp(algo, "ssh-ed448") == 0) {
key_type = PROXY_SSH_KEY_ED448;
}
return key_type;
}
const char *proxy_ssh_keys_get_key_type_desc(enum proxy_ssh_key_type_e key_type) {
const char *key_desc = NULL;
switch (key_type) {
case PROXY_SSH_KEY_UNKNOWN:
key_desc = "unknown";
break;
case PROXY_SSH_KEY_DSA:
key_desc = "ssh-dss";
break;
case PROXY_SSH_KEY_RSA:
key_desc = "ssh-rsa";
break;
case PROXY_SSH_KEY_RSA_SHA256:
key_desc = "rsa-sha2-256";
break;
case PROXY_SSH_KEY_RSA_SHA512:
key_desc = "rsa-sha2-512";
break;
case PROXY_SSH_KEY_ECDSA_256:
key_desc = "ecdsa-sha2-nistp256";
break;
case PROXY_SSH_KEY_ECDSA_384:
key_desc = "ecdsa-sha2-nistp384";
break;
case PROXY_SSH_KEY_ECDSA_521:
key_desc = "ecdsa-sha2-nistp521";
break;
case PROXY_SSH_KEY_ED25519:
key_desc = "ssh-ed25519";
break;
case PROXY_SSH_KEY_ED448:
key_desc = "ssh-ed448";
break;
default:
key_desc = "undefined";
break;
}
return key_desc;
}
/* This is used to validate the ECDSA parameters we might receive e.g. from
* a server. These checks come from Section 3.2.2.1 of 'Standards for
* Efficient Cryptography Group, "Elliptic Curve Cryptography", SEC 1,
* May 2009:
*
* http://www.secg.org/download/aid-780/sec1-v2.pdf
*
* as per RFC 5656 recommendation.
*/
int proxy_ssh_keys_validate_ecdsa_params(const EC_GROUP *group,
const EC_POINT *point) {
BN_CTX *bn_ctx;
BIGNUM *ec_order, *x_coord, *y_coord, *bn_tmp;
int coord_nbits, ec_order_nbits;
EC_POINT *subgroup_order = NULL;
if (EC_METHOD_get_field_type(EC_GROUP_method_of(group)) != NID_X9_62_prime_field) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"ECDSA group is not a prime field, rejecting");
errno = EACCES;
return -1;
}
/* A Q of infinity is unacceptable. */
if (EC_POINT_is_at_infinity(group, point) != 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"ECDSA EC point has infinite value, rejecting");
errno = EACCES;
return -1;
}
/* A BN_CTX is like our pools; we allocate one, use it to get any
* number of BIGNUM variables, and only have free up the BN_CTX when
* we're done, rather than all of the individual BIGNUMs.
*/
bn_ctx = BN_CTX_new();
if (bn_ctx == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error allocating BN_CTX: %s", proxy_ssh_crypto_get_errors());
return -1;
}
BN_CTX_start(bn_ctx);
ec_order = BN_CTX_get(bn_ctx);
if (ec_order == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error getting new BIGNUM from BN_CTX: %s",
proxy_ssh_crypto_get_errors());
BN_CTX_free(bn_ctx);
errno = EPERM;
return -1;
}
if (EC_GROUP_get_order(group, ec_order, bn_ctx) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error getting EC group order: %s", proxy_ssh_crypto_get_errors());
BN_CTX_free(bn_ctx);
errno = EPERM;
return -1;
}
x_coord = BN_CTX_get(bn_ctx);
if (x_coord == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error getting new BIGNUM from BN_CTX: %s",
proxy_ssh_crypto_get_errors());
BN_CTX_free(bn_ctx);
errno = EPERM;
return -1;
}
y_coord = BN_CTX_get(bn_ctx);
if (y_coord == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error getting new BIGNUM from BN_CTX: %s",
proxy_ssh_crypto_get_errors());
BN_CTX_free(bn_ctx);
errno = EPERM;
return -1;
}
if (EC_POINT_get_affine_coordinates_GFp(group, point, x_coord, y_coord,
bn_ctx) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error getting EC point affine coordinates: %s",
proxy_ssh_crypto_get_errors());
BN_CTX_free(bn_ctx);
errno = EPERM;
return -1;
}
/* Ensure that the following are both true:
*
* log2(X coord) > log2(EC order)/2
* log2(Y coord) > log2(EC order)/2
*/
coord_nbits = BN_num_bits(x_coord);
ec_order_nbits = BN_num_bits(ec_order);
if (coord_nbits <= (ec_order_nbits / 2)) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"EC public key X coordinate (%d bits) too small (<= %d bits), rejecting",
coord_nbits, (ec_order_nbits / 2));
BN_CTX_free(bn_ctx);
errno = EACCES;
return -1;
}
coord_nbits = BN_num_bits(y_coord);
if (coord_nbits <= (ec_order_nbits / 2)) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"EC public key Y coordinate (%d bits) too small (<= %d bits), rejecting",
coord_nbits, (ec_order_nbits / 2));
BN_CTX_free(bn_ctx);
errno = EACCES;
return -1;
}
/* Ensure that the following is true:
*
* subgroup order == infinity
*/
subgroup_order = EC_POINT_new(group);
if (subgroup_order == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error allocating new EC_POINT: %s", proxy_ssh_crypto_get_errors());
BN_CTX_free(bn_ctx);
errno = EPERM;
return -1;
}
if (EC_POINT_mul(group, subgroup_order, NULL, point, ec_order, bn_ctx) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error doing EC point multiplication: %s", proxy_ssh_crypto_get_errors());
EC_POINT_free(subgroup_order);
BN_CTX_free(bn_ctx);
errno = EPERM;
return -1;
}
if (EC_POINT_is_at_infinity(group, subgroup_order) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"EC public key has finite subgroup order, rejecting");
EC_POINT_free(subgroup_order);
BN_CTX_free(bn_ctx);
errno = EACCES;
return -1;
}
EC_POINT_free(subgroup_order);
/* Ensure that the following are both true:
*
* X < order - 1
* Y < order - 1
*/
bn_tmp = BN_CTX_get(bn_ctx);
if (bn_tmp == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error getting new BIGNUM from BN_CTX: %s",
proxy_ssh_crypto_get_errors());
BN_CTX_free(bn_ctx);
errno = EPERM;
return -1;
}
if (BN_sub(bn_tmp, ec_order, BN_value_one()) == 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error subtracting one from EC group order: %s",
proxy_ssh_crypto_get_errors());
BN_CTX_free(bn_ctx);
errno = EPERM;
return -1;
}
if (BN_cmp(x_coord, bn_tmp) >= 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"EC public key X coordinate too large (>= EC group order - 1), "
"rejecting");
BN_CTX_free(bn_ctx);
errno = EACCES;
return -1;
}
if (BN_cmp(y_coord, bn_tmp) >= 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"EC public key Y coordinate too large (>= EC group order - 1), "
"rejecting");
BN_CTX_free(bn_ctx);
errno = EACCES;
return -1;
}
BN_CTX_free(bn_ctx);
return 0;
}
#endif /* PR_USE_OPENSSL_ECC */
#ifdef SFTP_DEBUG_KEYS
static void debug_rsa_key(pool *p, const char *label, RSA *rsa) {
BIO *bio = NULL;
char *data;
long datalen;
bio = BIO_new(BIO_s_mem());
RSA_print(bio, rsa, 0);
BIO_flush(bio);
datalen = BIO_get_mem_data(bio, &data);
if (data != NULL &&
datalen > 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "%s",label);
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "%.*s",
(int) datalen, data);
}
BIO_free(bio);
}
#endif
static int get_pkey_type(EVP_PKEY *pkey) {
int pkey_type;
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESS)
pkey_type = EVP_PKEY_base_id(pkey);
#else
pkey_type = EVP_PKEY_type(pkey->type);
#endif /* OpenSSL 1.1.x and later */
return pkey_type;
}
static int rsa_compare_keys(pool *p, EVP_PKEY *remote_pkey,
EVP_PKEY *local_pkey) {
RSA *remote_rsa = NULL, *local_rsa = NULL;
const BIGNUM *remote_rsa_e = NULL, *local_rsa_e = NULL;
const BIGNUM *remote_rsa_n = NULL, *local_rsa_n = NULL;
int res = 0;
local_rsa = EVP_PKEY_get1_RSA(local_pkey);
remote_rsa = EVP_PKEY_get1_RSA(remote_pkey);
#ifdef SFTP_DEBUG_KEYS
debug_rsa_key(p, "remote RSA key:", remote_rsa);
debug_rsa_key(p, "local RSA key:", local_rsa);
#endif
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
RSA_get0_key(remote_rsa, &remote_rsa_n, &remote_rsa_e, NULL);
RSA_get0_key(local_rsa, &local_rsa_n, &local_rsa_e, NULL);
#else
remote_rsa_e = remote_rsa->e;
local_rsa_e = local_rsa->e;
remote_rsa_n = remote_rsa->n;
local_rsa_n = local_rsa->n;
#endif /* prior to OpenSSL-1.1.0 */
if (BN_cmp(remote_rsa_e, local_rsa_e) != 0) {
pr_trace_msg(trace_channel, 17, "%s",
"RSA key mismatch: client-sent RSA key component 'e' does not match "
"local RSA key component 'e'");
res = -1;
}
if (res == 0) {
if (BN_cmp(remote_rsa_n, local_rsa_n) != 0) {
pr_trace_msg(trace_channel, 17, "%s",
"RSA key mismatch: client-sent RSA key component 'n' does not match "
"local RSA key component 'n'");
res = -1;
}
}
RSA_free(remote_rsa);
RSA_free(local_rsa);
return res;
}
#if !defined(OPENSSL_NO_DSA)
static int dsa_compare_keys(pool *p, EVP_PKEY *remote_pkey,
EVP_PKEY *local_pkey) {
DSA *remote_dsa = NULL, *local_dsa = NULL;
const BIGNUM *remote_dsa_p, *remote_dsa_q, *remote_dsa_g;
const BIGNUM *local_dsa_p, *local_dsa_q, *local_dsa_g;
const BIGNUM *remote_dsa_pub_key, *local_dsa_pub_key;
int res = 0;
local_dsa = EVP_PKEY_get1_DSA(local_pkey);
remote_dsa = EVP_PKEY_get1_DSA(remote_pkey);
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
DSA_get0_pqg(remote_dsa, &remote_dsa_p, &remote_dsa_q, &remote_dsa_g);
DSA_get0_pqg(local_dsa, &local_dsa_p, &local_dsa_q, &local_dsa_g);
DSA_get0_key(remote_dsa, &remote_dsa_pub_key, NULL);
DSA_get0_key(local_dsa, &local_dsa_pub_key, NULL);
#else
remote_dsa_p = remote_dsa->p;
remote_dsa_q = remote_dsa->q;
remote_dsa_g = remote_dsa->g;
remote_dsa_pub_key = remote_dsa->pub_key;
local_dsa_p = local_dsa->p;
local_dsa_q = local_dsa->q;
local_dsa_g = local_dsa->g;
local_dsa_pub_key = local_dsa->pub_key;
#endif /* prior to OpenSSL-1.1.0 */
if (BN_cmp(remote_dsa_p, local_dsa_p) != 0) {
pr_trace_msg(trace_channel, 17, "%s",
"DSA key mismatch: client-sent DSA key parameter 'p' does not match "
"local DSA key parameter 'p'");
res = -1;
}
if (res == 0) {
if (BN_cmp(remote_dsa_q, local_dsa_q) != 0) {
pr_trace_msg(trace_channel, 17, "%s",
"DSA key mismatch: client-sent DSA key parameter 'q' does not match "
"local DSA key parameter 'q'");
res = -1;
}
}
if (res == 0) {
if (BN_cmp(remote_dsa_g, local_dsa_g) != 0) {
pr_trace_msg(trace_channel, 17, "%s",
"DSA key mismatch: client-sent DSA key parameter 'g' does not match "
"local DSA key parameter 'g'");
res = -1;
}
}
if (res == 0) {
if (BN_cmp(remote_dsa_pub_key, local_dsa_pub_key) != 0) {
pr_trace_msg(trace_channel, 17, "%s",
"DSA key mismatch: client-sent DSA key parameter 'pub_key' does not "
"match local DSA key parameter 'pub_key'");
res = -1;
}
}
DSA_free(remote_dsa);
DSA_free(local_dsa);
return res;
}
#endif /* OPENSSL_NO_DSA */
#if defined(PR_USE_OPENSSL_ECC)
static int ecdsa_compare_keys(pool *p, EVP_PKEY *remote_pkey,
EVP_PKEY *local_pkey) {
EC_KEY *remote_ec, *local_ec;
int res = 0;
local_ec = EVP_PKEY_get1_EC_KEY(local_pkey);
remote_ec = EVP_PKEY_get1_EC_KEY(remote_pkey);
if (EC_GROUP_cmp(EC_KEY_get0_group(local_ec),
EC_KEY_get0_group(remote_ec), NULL) != 0) {
pr_trace_msg(trace_channel, 17, "%s",
"ECC key mismatch: client-sent curve does not match local ECC curve");
res = -1;
}
if (res == 0) {
if (EC_POINT_cmp(EC_KEY_get0_group(local_ec),
EC_KEY_get0_public_key(local_ec),
EC_KEY_get0_public_key(remote_ec), NULL) != 0) {
pr_trace_msg(trace_channel, 17, "%s",
"ECC key mismatch: client-sent public key 'Q' does not match "
"local ECC public key 'Q'");
res = -1;
}
}
EC_KEY_free(remote_ec);
EC_KEY_free(local_ec);
return res;
}
#endif /* PR_USE_OPENSSL_ECC */
#if defined(PR_USE_SODIUM)
static int ed25519_compare_keys(pool *p,
unsigned char *remote_pubkey_data, uint32_t remote_pubkey_datalen,
unsigned char *local_pubkey_data, uint32_t local_pubkey_datalen) {
int res = 0;
if (remote_pubkey_datalen != local_pubkey_datalen) {
return -1;
}
if (memcmp(remote_pubkey_data, local_pubkey_data, remote_pubkey_datalen) != 0) {
res = -1;
}
return res;
}
#endif /* PR_USE_SODIUM */
#if defined(HAVE_X448_OPENSSL)
static int ed448_compare_keys(pool *p,
unsigned char *remote_pubkey_data, uint32_t remote_pubkey_datalen,
unsigned char *local_pubkey_data, uint32_t local_pubkey_datalen) {
int res = 0;
if (remote_pubkey_datalen != local_pubkey_datalen) {
return -1;
}
if (memcmp(remote_pubkey_data, local_pubkey_data, remote_pubkey_datalen) != 0) {
res = -1;
}
return res;
}
#endif /* HAVE_X448_OPENSSL */
/* Compare a "blob" of pubkey data sent by the server for authentication
* with a local file pubkey (from an RFC4716 formatted file). Returns -1 if
* there was an error, TRUE if the keys are equals, and FALSE if not.
*/
int proxy_ssh_keys_compare_keys(pool *p,
unsigned char *remote_pubkey_data, uint32_t remote_pubkey_datalen,
unsigned char *local_pubkey_data, uint32_t local_pubkey_datalen) {
enum proxy_ssh_key_type_e remote_key_type, local_key_type;
EVP_PKEY *remote_pkey = NULL, *local_pkey = NULL;
int res = -1;
uint32_t len = 0;
if (remote_pubkey_data == NULL ||
local_pubkey_data == NULL) {
errno = EINVAL;
return -1;
}
remote_key_type = local_key_type = PROXY_SSH_KEY_UNKNOWN;
len = read_pkey_from_data(p, remote_pubkey_data, remote_pubkey_datalen,
&remote_pkey, &remote_key_type, FALSE);
if (len == 0) {
return -1;
}
len = read_pkey_from_data(p, local_pubkey_data, local_pubkey_datalen,
&local_pkey, &local_key_type, FALSE);
if (len == 0) {
int xerrno = errno;
if (remote_pkey != NULL) {
EVP_PKEY_free(remote_pkey);
}
errno = xerrno;
return -1;
}
if (remote_pkey != NULL &&
local_pkey != NULL &&
remote_key_type == local_key_type) {
switch (get_pkey_type(remote_pkey)) {
case EVP_PKEY_RSA: {
if (rsa_compare_keys(p, remote_pkey, local_pkey) == 0) {
res = TRUE;
} else {
res = FALSE;
}
break;
}
#if !defined(OPENSSL_NO_DSA)
case EVP_PKEY_DSA: {
if (dsa_compare_keys(p, remote_pkey, local_pkey) == 0) {
res = TRUE;
} else {
res = FALSE;
}
break;
}
#endif /* !OPENSSL_NO_DSA */
#ifdef PR_USE_OPENSSL_ECC
case EVP_PKEY_EC: {
if (ecdsa_compare_keys(p, remote_pkey, local_pkey) == 0) {
res = TRUE;
} else {
res = FALSE;
}
break;
}
#endif /* PR_USE_OPENSSL_ECC */
default:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to compare %s keys: unsupported key type",
get_pkey_type_desc(get_pkey_type(remote_pkey)));
errno = ENOSYS;
break;
}
} else if (remote_key_type == PROXY_SSH_KEY_ED25519 &&
remote_key_type == local_key_type) {
#if defined(PR_USE_SODIUM)
if (ed25519_compare_keys(p, remote_pubkey_data, remote_pubkey_datalen,
local_pubkey_data, local_pubkey_datalen) == 0) {
res = TRUE;
} else {
res = FALSE;
}
#endif /* PR_USE_SODIUM */
} else if (remote_key_type == PROXY_SSH_KEY_ED448 &&
remote_key_type == local_key_type) {
#if defined(HAVE_X448_OPENSSL)
if (ed448_compare_keys(p, remote_pubkey_data, remote_pubkey_datalen,
local_pubkey_data, local_pubkey_datalen) == 0) {
res = TRUE;
} else {
res = FALSE;
}
#endif /* HAVE_X448_OPENSSL */
} else {
if (pr_trace_get_level(trace_channel) >= 17) {
const char *remote_key_desc, *local_key_desc;
remote_key_desc = get_key_type_desc(remote_key_type);
local_key_desc = get_key_type_desc(local_key_type);
pr_trace_msg(trace_channel, 17, "key mismatch: cannot compare %s key "
"(client-sent) with %s key (local)", remote_key_desc, local_key_desc);
}
res = FALSE;
}
if (remote_pkey != NULL) {
EVP_PKEY_free(remote_pkey);
}
if (local_pkey != NULL) {
EVP_PKEY_free(local_pkey);
}
return res;
}
const char *proxy_ssh_keys_get_fingerprint(pool *p, unsigned char *key_data,
uint32_t key_datalen, int digest_algo) {
#if OPENSSL_VERSION_NUMBER < 0x10100000L || \
defined(HAVE_LIBRESSL)
EVP_MD_CTX ctx;
#endif /* prior to OpenSSL-1.1.0 */
EVP_MD_CTX *pctx;
const EVP_MD *digest;
char *digest_name = "none", *fp;
unsigned char *fp_data;
unsigned int fp_datalen = 0;
register unsigned int i;
switch (digest_algo) {
case PROXY_SSH_KEYS_FP_DIGEST_MD5:
digest = EVP_md5();
digest_name = "md5";
break;
case PROXY_SSH_KEYS_FP_DIGEST_SHA1:
digest = EVP_sha1();
digest_name = "sha1";
break;
#if defined(HAVE_SHA256_OPENSSL)
case PROXY_SSH_KEYS_FP_DIGEST_SHA256:
digest = EVP_sha256();
digest_name = "sha256";
break;
#endif /* HAVE_SHA256_OPENSSL */
default:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unsupported key fingerprint digest algorithm (%d)", digest_algo);
errno = EACCES;
return NULL;
}
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
pctx = EVP_MD_CTX_new();
#else
pctx = &ctx;
#endif /* prior to OpenSSL-1.1.0 */
/* In OpenSSL 0.9.6, many of the EVP_Digest* functions returned void, not
* int. Without these ugly OpenSSL version preprocessor checks, the
* compiler will error out with "void value not ignored as it ought to be".
*/
#if OPENSSL_VERSION_NUMBER >= 0x000907000L
if (EVP_DigestInit(pctx, digest) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error initializing %s digest: %s", digest_name,
proxy_ssh_crypto_get_errors());
# if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
EVP_MD_CTX_free(pctx);
# endif /* OpenSSL-1.1.0 and later */
errno = EPERM;
return NULL;
}
#else
EVP_DigestInit(pctx, digest);
#endif
#if OPENSSL_VERSION_NUMBER >= 0x000907000L
if (EVP_DigestUpdate(pctx, key_data, key_datalen) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error updating %s digest: %s", digest_name,
proxy_ssh_crypto_get_errors());
# if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
EVP_MD_CTX_free(pctx);
# endif /* OpenSSL-1.1.0 and later */
errno = EPERM;
return NULL;
}
#else
EVP_DigestUpdate(pctx, key_data, key_datalen);
#endif
fp_data = palloc(p, EVP_MAX_MD_SIZE);
#if OPENSSL_VERSION_NUMBER >= 0x000907000L
if (EVP_DigestFinal(pctx, fp_data, &fp_datalen) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error finishing %s digest: %s", digest_name,
proxy_ssh_crypto_get_errors());
# if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
EVP_MD_CTX_free(pctx);
# endif /* OpenSSL-1.1.0 and later */
errno = EPERM;
return NULL;
}
#else
EVP_DigestFinal(pctx, fp_data, &fp_datalen);
#endif
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
EVP_MD_CTX_free(pctx);
#endif /* OpenSSL-1.1.0 and later */
/* Now encode that digest in fp_data as hex characters. */
fp = "";
for (i = 0; i < fp_datalen; i++) {
char c[4];
memset(c, '\0', sizeof(c));
pr_snprintf(c, sizeof(c), "%02x:", fp_data[i]);
fp = pstrcat(p, fp, &c, NULL);
}
fp[strlen(fp)-1] = '\0';
return fp;
}
#if defined(PR_USE_OPENSSL_ECC)
/* Returns the NID for the configured EVP_PKEY_EC key. */
static int get_ecdsa_nid(EC_KEY *ec) {
register unsigned int i;
const EC_GROUP *key_group;
EC_GROUP *new_group = NULL;
BN_CTX *bn_ctx = NULL;
int supported_ecdsa_nids[] = {
NID_X9_62_prime256v1,
NID_secp384r1,
NID_secp521r1,
-1
};
int nid;
if (ec == NULL) {
errno = EINVAL;
return -1;
}
/* Since the EC group might be encoded in different ways, we need to try
* different lookups to find the NID.
*
* First, we see if the EC group is encoded as a "named group" in the
* private key.
*/
key_group = EC_KEY_get0_group(ec);
nid = EC_GROUP_get_curve_name(key_group);
if (nid > 0) {
return nid;
}
/* Otherwise, we check to see if the group is encoded via explicit group
* parameters in the private key.
*/
bn_ctx = BN_CTX_new();
if (bn_ctx == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error allocated BN_CTX: %s", proxy_ssh_crypto_get_errors());
return -1;
}
for (i = 0; supported_ecdsa_nids[i] != -1; i++) {
new_group = EC_GROUP_new_by_curve_name(supported_ecdsa_nids[i]);
if (new_group == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error creating new EC_GROUP by curve name %d: %s",
supported_ecdsa_nids[i], proxy_ssh_crypto_get_errors());
BN_CTX_free(bn_ctx);
return -1;
}
if (EC_GROUP_cmp(key_group, new_group, bn_ctx) == 0) {
/* We have a match. */
break;
}
EC_GROUP_free(new_group);
new_group = NULL;
}
BN_CTX_free(bn_ctx);
if (supported_ecdsa_nids[i] != -1) {
EC_GROUP_set_asn1_flag(new_group, OPENSSL_EC_NAMED_CURVE);
if (EC_KEY_set_group(ec, new_group) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error setting EC group on key: %s", proxy_ssh_crypto_get_errors());
EC_GROUP_free(new_group);
return -1;
}
EC_GROUP_free(new_group);
}
return supported_ecdsa_nids[i];
}
#endif /* PR_USE_OPENSSL_ECC */
static int handle_hostkey(pool *p, EVP_PKEY *pkey,
const unsigned char *key_data, uint32_t key_datalen,
const char *file_path, const char *agent_path) {
switch (get_pkey_type(pkey)) {
case EVP_PKEY_RSA: {
#if OPENSSL_VERSION_NUMBER < 0x0090702fL
/* In OpenSSL-0.9.7a and later, RSA blinding is turned on by default.
* Thus if our OpenSSL is older than that, manually enable RSA
* blinding.
*/
RSA *rsa;
rsa = EVP_PKEY_get1_RSA(pkey);
if (rsa) {
if (RSA_blinding_on(rsa, NULL) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error enabling RSA blinding for key '%s': %s",
file_path ? file_path : agent_path,
proxy_ssh_crypto_get_errors());
} else {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"RSA blinding enabled for key '%s'",
file_path ? file_path : agent_path);
}
RSA_free(rsa);
}
#endif
if (rsa_hostkey != NULL) {
/* If we have an existing RSA hostkey, free it up. */
EVP_PKEY_free(rsa_hostkey->pkey);
rsa_hostkey->pkey = NULL;
rsa_hostkey->key_data = NULL;
rsa_hostkey->key_datalen = 0;
rsa_hostkey->file_path = NULL;
rsa_hostkey->agent_path = NULL;
} else {
rsa_hostkey = pcalloc(p, sizeof(struct proxy_ssh_hostkey));
}
rsa_hostkey->key_type = PROXY_SSH_KEY_RSA;
rsa_hostkey->pkey = pkey;
rsa_hostkey->key_data = key_data;
rsa_hostkey->key_datalen = key_datalen;
rsa_hostkey->file_path = file_path;
rsa_hostkey->agent_path = agent_path;
if (file_path != NULL) {
pr_trace_msg(trace_channel, 4, "using '%s' as RSA hostkey",
file_path);
} else if (agent_path != NULL) {
pr_trace_msg(trace_channel, 4,
"using RSA hostkey from SSH agent at '%s'", agent_path);
}
break;
}
case EVP_PKEY_DSA: {
if (dsa_hostkey != NULL) {
/* If we have an existing DSA hostkey, free it up. */
EVP_PKEY_free(dsa_hostkey->pkey);
dsa_hostkey->pkey = NULL;
dsa_hostkey->key_data = NULL;
dsa_hostkey->key_datalen = 0;
dsa_hostkey->file_path = NULL;
dsa_hostkey->agent_path = NULL;
} else {
dsa_hostkey = pcalloc(p, sizeof(struct proxy_ssh_hostkey));
}
dsa_hostkey->key_type = PROXY_SSH_KEY_DSA;
dsa_hostkey->pkey = pkey;
dsa_hostkey->key_data = key_data;
dsa_hostkey->key_datalen = key_datalen;
dsa_hostkey->file_path = file_path;
dsa_hostkey->agent_path = agent_path;
if (file_path != NULL) {
pr_trace_msg(trace_channel, 4, "using '%s' as DSA hostkey",
file_path);
} else if (agent_path != NULL) {
pr_trace_msg(trace_channel, 4,
"using DSA hostkey from SSH agent at '%s'", agent_path);
}
break;
}
#ifdef PR_USE_OPENSSL_ECC
case EVP_PKEY_EC: {
EC_KEY *ec;
int ec_nid;
ec = EVP_PKEY_get1_EC_KEY(pkey);
ec_nid = get_ecdsa_nid(ec);
if (ec_nid < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unsupported NID in EC key, ignoring");
EC_KEY_free(ec);
EVP_PKEY_free(pkey);
return -1;
}
if (proxy_ssh_keys_validate_ecdsa_params(EC_KEY_get0_group(ec),
EC_KEY_get0_public_key(ec)) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error validating EC public key: %s", strerror(errno));
EC_KEY_free(ec);
EVP_PKEY_free(pkey);
return -1;
}
if (validate_ecdsa_private_key(ec)) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error validating EC private key: %s", strerror(errno));
EC_KEY_free(ec);
EVP_PKEY_free(pkey);
return -1;
}
EC_KEY_free(ec);
switch (ec_nid) {
case NID_X9_62_prime256v1:
if (ecdsa256_hostkey != NULL) {
/* If we have an existing 256-bit ECDSA hostkey, free it up. */
EVP_PKEY_free(ecdsa256_hostkey->pkey);
ecdsa256_hostkey->pkey = NULL;
ecdsa256_hostkey->key_data = NULL;
ecdsa256_hostkey->key_datalen = 0;
ecdsa256_hostkey->file_path = NULL;
ecdsa256_hostkey->agent_path = NULL;
} else {
ecdsa256_hostkey = pcalloc(p, sizeof(struct proxy_ssh_hostkey));
}
ecdsa256_hostkey->key_type = PROXY_SSH_KEY_ECDSA_256;
ecdsa256_hostkey->pkey = pkey;
ecdsa256_hostkey->key_data = key_data;
ecdsa256_hostkey->key_datalen = key_datalen;
ecdsa256_hostkey->file_path = file_path;
ecdsa256_hostkey->agent_path = agent_path;
if (file_path != NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"using '%s' as 256-bit ECDSA hostkey", file_path);
} else if (agent_path != NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"using 256-bit ECDSA hostkey from SSH agent at '%s'",
agent_path);
}
break;
case NID_secp384r1:
if (ecdsa384_hostkey != NULL) {
/* If we have an existing 384-bit ECDSA hostkey, free it up. */
EVP_PKEY_free(ecdsa384_hostkey->pkey);
ecdsa384_hostkey->pkey = NULL;
ecdsa384_hostkey->key_data = NULL;
ecdsa384_hostkey->key_datalen = 0;
ecdsa384_hostkey->file_path = NULL;
ecdsa384_hostkey->agent_path = NULL;
} else {
ecdsa384_hostkey = pcalloc(p, sizeof(struct proxy_ssh_hostkey));
}
ecdsa384_hostkey->key_type = PROXY_SSH_KEY_ECDSA_384;
ecdsa384_hostkey->pkey = pkey;
ecdsa384_hostkey->key_data = key_data;
ecdsa384_hostkey->key_datalen = key_datalen;
ecdsa384_hostkey->file_path = file_path;
ecdsa384_hostkey->agent_path = agent_path;
if (file_path != NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"using '%s' as 384-bit ECDSA hostkey", file_path);
} else if (agent_path != NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"using 384-bit ECDSA hostkey from SSH agent at '%s'",
agent_path);
}
break;
case NID_secp521r1:
if (ecdsa521_hostkey != NULL) {
/* If we have an existing 521-bit ECDSA hostkey, free it up. */
EVP_PKEY_free(ecdsa521_hostkey->pkey);
ecdsa521_hostkey->pkey = NULL;
ecdsa521_hostkey->key_data = NULL;
ecdsa521_hostkey->key_datalen = 0;
ecdsa521_hostkey->file_path = NULL;
ecdsa521_hostkey->agent_path = NULL;
} else {
ecdsa521_hostkey = pcalloc(p, sizeof(struct proxy_ssh_hostkey));
}
ecdsa521_hostkey->key_type = PROXY_SSH_KEY_ECDSA_521;
ecdsa521_hostkey->pkey = pkey;
ecdsa521_hostkey->key_data = key_data;
ecdsa521_hostkey->key_datalen = key_datalen;
ecdsa521_hostkey->file_path = file_path;
ecdsa521_hostkey->agent_path = agent_path;
if (file_path != NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"using '%s' as 521-bit ECDSA hostkey", file_path);
} else if (agent_path != NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"using 521-bit hostkey from SSH agent at '%s'", agent_path);
}
break;
}
break;
}
#endif /* PR_USE_OPENSSL_ECC */
#if defined(HAVE_X448_OPENSSL)
case EVP_PKEY_ED448: {
unsigned char *privkey_data;
size_t privkey_datalen;
privkey_datalen = (CURVE448_SIZE * 2);
privkey_data = palloc(p, privkey_datalen);
if (EVP_PKEY_get_raw_private_key(pkey, privkey_data,
&privkey_datalen) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error reading ED448 private key from '%s': %s", file_path,
proxy_ssh_crypto_get_errors());
EVP_PKEY_free(pkey);
return -1;
}
if (handle_ed448_hostkey(p, privkey_data, privkey_datalen,
file_path) < 0) {
EVP_PKEY_free(pkey);
return -1;
}
break;
}
#endif /* HAVE_X448_OPENSSL */
default:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unknown private key type (%d), ignoring", get_pkey_type(pkey));
EVP_PKEY_free(pkey);
return -1;
}
return 0;
}
static int load_agent_hostkeys(pool *p, const char *path) {
register unsigned int i;
int accepted_nkeys = 0, res;
array_header *key_list;
key_list = make_array(p, 0, sizeof(struct agent_key *));
res = proxy_ssh_agent_get_keys(p, path, key_list);
if (res < 0) {
int xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error loading hostkeys from SSH agent at '%s': %s", path,
strerror(xerrno));
errno = xerrno;
return -1;
}
if (key_list->nelts == 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"SSH agent at '%s' returned no keys", path);
errno = ENOENT;
return -1;
}
pr_trace_msg(trace_channel, 9, "processing %d keys from SSH agent at '%s'",
key_list->nelts, path);
for (i = 0; i < key_list->nelts; i++) {
EVP_PKEY *pkey;
uint32_t len;
struct agent_key *agent_key;
agent_key = ((struct agent_key **) key_list->elts)[i];
len = read_pkey_from_data(p, agent_key->key_data, agent_key->key_datalen,
&pkey, NULL, FALSE);
if (len == 0) {
continue;
}
if (handle_hostkey(p, pkey, agent_key->key_data, agent_key->key_datalen,
NULL, path) == 0) {
accepted_nkeys++;
}
}
if (accepted_nkeys == 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"none of the keys provided by the SSH agent at '%s' were acceptable",
path);
errno = EINVAL;
return -1;
}
pr_trace_msg(trace_channel, 9, "loaded %d keys from SSH agent at '%s'",
accepted_nkeys, path);
/* Return the number of keys we successfully accept from the agent. */
return accepted_nkeys;
}
static struct openssh_cipher *get_openssh_cipher(const char *name) {
register unsigned int i;
struct openssh_cipher *cipher = NULL;
for (i = 0; ciphers[i].algo != NULL; i++) {
if (strcmp(ciphers[i].algo, name) == 0) {
cipher = &ciphers[i];
break;
}
}
if (cipher == NULL) {
errno = ENOENT;
return NULL;
}
if (cipher->get_cipher != NULL) {
cipher->cipher = (cipher->get_cipher)();
if (cipher->cipher != NULL) {
return cipher;
}
}
/* The CTR algorithms may require our own implementation, not the OpenSSL
* implementation.
*/
cipher->cipher = proxy_ssh_crypto_get_cipher(name, NULL, NULL, NULL);
if (cipher->cipher == NULL) {
errno = ENOSYS;
return NULL;
}
return cipher;
}
static int decrypt_openssh_data(pool *p, const char *path,
unsigned char *encrypted_data, uint32_t encrypted_len,
const char *passphrase, struct openssh_cipher *cipher,
const char *kdf_name, unsigned char *kdf_data, uint32_t kdf_len,
unsigned char **decrypted_data, uint32_t *decrypted_len) {
EVP_CIPHER_CTX *cipher_ctx = NULL;
unsigned char *buf, *key, *iv, *salt_data;
uint32_t buflen, key_len, rounds, salt_len, len = 0;
size_t passphrase_len;
if (strcmp(kdf_name, "none") == 0) {
*decrypted_data = encrypted_data;
*decrypted_len = encrypted_len;
return 0;
}
if (strcmp(kdf_name, "bcrypt") != 0) {
pr_trace_msg(trace_channel, 3,
"'%s' key uses unsupported %s KDF", path, kdf_name);
errno = ENOSYS;
return -1;
}
len = proxy_ssh_msg_read_int(p, &kdf_data, &kdf_len, &salt_len);
len = proxy_ssh_msg_read_data(p, &kdf_data, &kdf_len, salt_len, &salt_data);
len = proxy_ssh_msg_read_int(p, &kdf_data, &kdf_len, &rounds);
pr_trace_msg(trace_channel, 9,
"'%s' key %s KDF using %lu bytes of salt, %lu rounds", path,
kdf_name, (unsigned long) salt_len, (unsigned long) rounds);
/* Compute the decryption key using the KDF and the passphrase. Note that
* we derive the key AND the IV using this approach at the same time.
*/
passphrase_len = strlen(passphrase);
key_len = cipher->key_len + cipher->iv_len;
pr_trace_msg(trace_channel, 13,
"generating %s decryption key using %s KDF (key len = %lu, IV len = %lu)",
cipher->algo, kdf_name, (unsigned long) cipher->key_len,
(unsigned long) cipher->iv_len);
key = pcalloc(p, key_len);
if (proxy_ssh_bcrypt_pbkdf2(p, passphrase, passphrase_len, salt_data,
salt_len, rounds, key, key_len) < 0) {
pr_trace_msg(trace_channel, 3,
"error computing key using %s KDF: %s", kdf_name, strerror(errno));
errno = EPERM;
return -1;
}
if (cipher->iv_len > 0) {
iv = key + cipher->key_len;
} else {
iv = NULL;
}
cipher_ctx = EVP_CIPHER_CTX_new();
if (cipher_ctx == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error allocating cipher context: %s", proxy_ssh_crypto_get_errors());
errno = EPERM;
return -1;
}
#if OPENSSL_VERSION_NUMBER < 0x10100000L || \
defined(HAVE_LIBRESSL)
EVP_CIPHER_CTX_init(cipher_ctx);
#endif
#if defined(PR_USE_OPENSSL_EVP_CIPHERINIT_EX)
if (EVP_CipherInit_ex(cipher_ctx, cipher->cipher, NULL, key, iv, 0) != 1) {
#else
if (EVP_CipherInit(cipher_ctx, cipher->cipher, key, iv, 0) != 1) {
#endif /* PR_USE_OPENSSL_EVP_CIPHERINIT_EX */
pr_trace_msg(trace_channel, 3,
"error initializing %s cipher for decryption: %s", cipher->algo,
proxy_ssh_crypto_get_errors());
EVP_CIPHER_CTX_free(cipher_ctx);
pr_memscrub(key, key_len);
errno = EPERM;
return -1;
}
if (cipher->key_len > 0) {
if (EVP_CIPHER_CTX_set_key_length(cipher_ctx, cipher->key_len) != 1) {
pr_trace_msg(trace_channel, 3,
"error setting key length (%lu bytes) for %s cipher for decryption: %s",
(unsigned long) cipher->key_len, cipher->algo,
proxy_ssh_crypto_get_errors());
#if OPENSSL_VERSION_NUMBER < 0x10100000L
EVP_CIPHER_CTX_cleanup(cipher_ctx);
#endif /* prior to OpenSSL-1.1.0 */
EVP_CIPHER_CTX_free(cipher_ctx);
pr_memscrub(key, key_len);
errno = EPERM;
return -1;
}
}
buflen = encrypted_len;
buf = pcalloc(p, buflen);
/* TODO: this currently works because our data does NOT contain any extra
* trailing AEAD bytes. Need to fix that in the future.
*/
if (EVP_Cipher(cipher_ctx, buf, encrypted_data, encrypted_len) < 0) {
/* This might happen due to a wrong/bad passphrase. */
pr_trace_msg(trace_channel, 3,
"error decrypting %s data for key: %s", cipher->algo,
proxy_ssh_crypto_get_errors());
#if OPENSSL_VERSION_NUMBER < 0x10100000L
EVP_CIPHER_CTX_cleanup(cipher_ctx);
#endif /* prior to OpenSSL-1.1.0 */
EVP_CIPHER_CTX_free(cipher_ctx);
pr_memscrub(key, key_len);
pr_memscrub(buf, buflen);
errno = EPERM;
return -1;
}
#if OPENSSL_VERSION_NUMBER < 0x10100000L
EVP_CIPHER_CTX_cleanup(cipher_ctx);
#endif /* prior to OpenSSL-1.1.0 */
EVP_CIPHER_CTX_free(cipher_ctx);
pr_memscrub(key, key_len);
*decrypted_data = buf;
*decrypted_len = buflen;
return 0;
}
/* See openssh-7.9p1/sshkey.c#sshkey_from_blob_internal(). */
static int deserialize_openssh_private_key(pool *p, const char *path,
unsigned char **data, uint32_t *data_len,
enum proxy_ssh_key_type_e *key_type, EVP_PKEY **pkey, unsigned char **key,
uint32_t *keylen) {
uint32_t len = 0, public_keylen = 0, secret_keylen = 0;
const char *pkey_type;
unsigned char *public_key = NULL, *secret_key = NULL;
int have_extra_public_key = FALSE;
len = read_pkey_from_data(p, *data, *data_len, pkey, key_type, TRUE);
if (len == 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unsupported key type %d found in '%s'", *key_type, path);
errno = EPERM;
return -1;
}
/* Advance our pointers for all of the data read from them. */
(*data) += len;
(*data_len) -= len;
switch (*key_type) {
case PROXY_SSH_KEY_DSA:
case PROXY_SSH_KEY_RSA:
case PROXY_SSH_KEY_ECDSA_256:
case PROXY_SSH_KEY_ECDSA_384:
case PROXY_SSH_KEY_ECDSA_521:
case PROXY_SSH_KEY_RSA_SHA256:
case PROXY_SSH_KEY_RSA_SHA512:
return 0;
case PROXY_SSH_KEY_ED25519:
pkey_type = "ssh-ed25519";
break;
case PROXY_SSH_KEY_ED448:
pkey_type = "ssh-ed448";
break;
case PROXY_SSH_KEY_UNKNOWN:
default:
*key = NULL;
*keylen = 0;
return 0;
}
len = proxy_ssh_msg_read_int(p, data, data_len, &public_keylen);
len = proxy_ssh_msg_read_data(p, data, data_len, public_keylen,
&public_key);
if (public_key == NULL) {
pr_trace_msg(trace_channel, 2,
"error reading %s key: invalid/supported key format", pkey_type);
errno = EINVAL;
return -1;
}
len = proxy_ssh_msg_read_int(p, data, data_len, &secret_keylen);
/* NOTE: PuTTY's puttygen adds the public key _again_, in the second half
* of the secret key data, per commments in its
* `sshecc.c#eddsa_new_priv_openssh` function. Thus if this secret key
* length is larger than expected for Ed448 keys, only use the first half of
* it. Ugh. This "divide in half" hack only works for these keys where the
* private and public key sizes are the same.
*/
#if defined(HAVE_X448_OPENSSL) && defined(HAVE_SHA512_OPENSSL)
if (*key_type == PROXY_SSH_KEY_ED448) {
if (secret_keylen > (CURVE448_SIZE + 1)) {
have_extra_public_key = TRUE;
secret_keylen /= 2;
}
}
#endif /* HAVE_X448_OPENSSL and HAVE_SHA512_OPENSSL */
len = proxy_ssh_msg_read_data(p, data, data_len, secret_keylen,
&secret_key);
if (secret_key == NULL) {
pr_trace_msg(trace_channel, 2,
"error reading %s key: invalid/supported key format", pkey_type);
errno = EINVAL;
return -1;
}
if (have_extra_public_key == TRUE) {
unsigned char *extra_data = NULL;
/* Read (and ignore) the rest of the secret data */
(void) proxy_ssh_msg_read_data(p, data, data_len, secret_keylen,
&extra_data);
}
/* The secret key is what we need to extract. */
*key = secret_key;
*keylen = secret_keylen;
return 0;
}
static int decrypt_openssh_private_key(pool *p, const char *path,
unsigned char *encrypted_data, uint32_t encrypted_len,
const char *passphrase, struct openssh_cipher *cipher,
const char *kdf_name, unsigned char *kdf_data, uint32_t kdf_len,
enum proxy_ssh_key_type_e *key_type, EVP_PKEY **pkey, unsigned char **key,
uint32_t *keylen) {
unsigned char *decrypted_data = NULL, *decrypted_ptr = NULL;
uint32_t check_bytes[2], decrypted_len = 0, decrypted_sz = 0, len = 0;
char *comment = NULL;
int res;
unsigned int i = 0;
res = decrypt_openssh_data(p, path, encrypted_data, encrypted_len, passphrase,
cipher, kdf_name, kdf_data, kdf_len, &decrypted_data, &decrypted_len);
if (res < 0) {
pr_trace_msg(trace_channel, 6,
"failed to decrypt '%s' using %s cipher: %s", path, cipher->algo,
strerror(errno));
errno = EINVAL;
return -1;
}
pr_trace_msg(trace_channel, 14,
"decrypted %lu bytes into %lu bytes", (unsigned long) encrypted_len,
(unsigned long) decrypted_len);
decrypted_ptr = decrypted_data;
decrypted_sz = decrypted_len;
proxy_ssh_msg_read_int(p, &decrypted_data, &decrypted_len, &(check_bytes[0]));
proxy_ssh_msg_read_int(p, &decrypted_data, &decrypted_len, &(check_bytes[1]));
if (check_bytes[0] != check_bytes[1]) {
pr_trace_msg(trace_channel, 6,
"'%s' has mismatched check bytes (%lu != %lu); wrong passphrase", path,
(unsigned long) check_bytes[0], (unsigned long) check_bytes[1]);
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to read hostkey '%s': wrong passphrase", path);
pr_memscrub(decrypted_ptr, decrypted_sz);
errno = EINVAL;
return -1;
}
res = deserialize_openssh_private_key(p, path, &decrypted_data,
&decrypted_len, key_type, pkey, key, keylen);
if (res < 0) {
int xerrno = errno;
pr_memscrub(decrypted_ptr, decrypted_sz);
errno = xerrno;
return -1;
}
len = proxy_ssh_msg_read_string(p, &decrypted_data, &decrypted_len, &comment);
if (comment != NULL) {
pr_trace_msg(trace_channel, 9,
"'%s' comment: '%s'", path, comment);
}
/* Verify the expected remaining padding. */
for (i = 1; decrypted_len > 0; i++) {
unsigned char padding;
pr_signals_handle();
len = proxy_ssh_msg_read_byte(p, &decrypted_data, &decrypted_len, &padding);
if (padding != (i & 0xFF)) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"'%s' key has invalid padding", path);
pr_memscrub(decrypted_ptr, decrypted_sz);
errno = EINVAL;
return -1;
}
}
pr_memscrub(decrypted_ptr, decrypted_sz);
return 0;
}
static int unwrap_openssh_private_key(pool *p, const char *path,
unsigned char *text, size_t text_len, const char *passphrase,
enum proxy_ssh_key_type_e *key_type, EVP_PKEY **pkey, unsigned char **key,
uint32_t *keylen) {
char *cipher_name, *kdf_name, *tmp;
unsigned char *buf, *data = NULL, *kdf_data, *encrypted_data;
size_t data_len = 0, magicsz;
uint32_t buflen, kdf_len = 0, key_count = 0, encrypted_len = 0, len = 0;
struct openssh_cipher *cipher = NULL;
int xerrno = 0;
data = decode_base64(p, text, text_len, &data_len);
xerrno = errno;
if (data == NULL) {
pr_trace_msg(trace_channel, 6,
"error base64-decoding key '%s': %s", path, strerror(xerrno));
errno = xerrno;
return -1;
}
magicsz = sizeof(PROXY_SSH_OPENSSH_MAGIC);
if (data_len < magicsz) {
pr_trace_msg(trace_channel, 6,
"'%s' key base64-decoded data too short (%lu bytes < %lu minimum "
"required)", path, (unsigned long) data_len, (unsigned long) magicsz);
errno = EINVAL;
return -1;
}
if (memcmp(data, PROXY_SSH_OPENSSH_MAGIC, magicsz) != 0) {
pr_trace_msg(trace_channel, 6,
"'%s' key base64-decoded contains invalid magic value", path);
errno = EINVAL;
return -1;
}
data += magicsz;
data_len -= magicsz;
buf = data;
buflen = data_len;
len = proxy_ssh_msg_read_string(p, &buf, &buflen, &cipher_name);
len = proxy_ssh_msg_read_string(p, &buf, &buflen, &kdf_name);
len = proxy_ssh_msg_read_int(p, &buf, &buflen, &kdf_len);
len = proxy_ssh_msg_read_data(p, &buf, &buflen, kdf_len, &kdf_data);
len = proxy_ssh_msg_read_int(p, &buf, &buflen, &key_count);
/* Ignore the public key */
(void) proxy_ssh_msg_read_string(p, &buf, &buflen, &tmp);
len = proxy_ssh_msg_read_int(p, &buf, &buflen, &encrypted_len);
pr_trace_msg(trace_channel, 9,
"'%s' key cipher = '%s', KDF = '%s' (%lu bytes KDF data), "
"key count = %lu, (%lu bytes encrypted data)", path, cipher_name, kdf_name,
(unsigned long) kdf_len, (unsigned long) key_count,
(unsigned long) encrypted_len);
cipher = get_openssh_cipher(cipher_name);
if (cipher == NULL) {
pr_trace_msg(trace_channel, 6,
"'%s' key uses unexpected/unsupported cipher (%s)", path, cipher_name);
errno = EPERM;
return -1;
}
if ((passphrase == NULL ||
strlen(passphrase) == 0) &&
strcmp(cipher_name, "none") != 0) {
pr_trace_msg(trace_channel, 6,
"'%s' key requires passphrase for cipher (%s)", path, cipher_name);
errno = EPERM;
return -1;
}
/* We only support the "none" and "bcrypt" KDFs at present. */
if (strcmp(kdf_name, "bcrypt") != 0 &&
strcmp(kdf_name, "none") != 0) {
pr_trace_msg(trace_channel, 6,
"'%s' key encrypted using unsupported KDF '%s'", path, kdf_name);
errno = EPERM;
return -1;
}
/* If our KDF is "none" and our cipher is NOT "none", we have a problem. */
if (strcmp(kdf_name, "none") == 0 &&
strcmp(cipher_name, "none") != 0) {
pr_trace_msg(trace_channel, 6,
"'%s' key encrypted using mismatched KDF and cipher algorithms: "
"KDF '%s', cipher '%s'", path, kdf_name, cipher_name);
errno = EPERM;
return -1;
}
/* OpenSSH only supports one key at present. Huh. */
if (key_count != 1) {
pr_trace_msg(trace_channel, 6,
"'%s' key includes unexpected/unsupported key count (%lu)",
path, (unsigned long) key_count);
errno = EPERM;
return -1;
}
/* XXX Should we enforce that the KDF data be empty for the "none" KDF? */
if (strcmp(kdf_name, "none") == 0 &&
kdf_len > 0) {
pr_trace_msg(trace_channel, 6,
"'%s' key uses KDF 'none', but contains unexpected %lu bytes "
"of KDF options", path, (unsigned long) kdf_len);
}
if (buflen < encrypted_len) {
pr_trace_msg(trace_channel, 6,
"'%s' key declares %lu bytes of encrypted data, but has "
"only %lu bytes remaining", path, (unsigned long) encrypted_len,
(unsigned long) buflen);
errno = EPERM;
return -1;
}
if (encrypted_len < cipher->blocksz ||
(encrypted_len % cipher->blocksz) != 0) {
pr_trace_msg(trace_channel, 6,
"'%s' key declares %lu bytes of encrypted data, which is invalid for "
"the %s cipher block size (%lu bytes)", path,
(unsigned long) encrypted_len, cipher_name,
(unsigned long) cipher->blocksz);
errno = EPERM;
return -1;
}
if (buflen < (encrypted_len + cipher->auth_len)) {
pr_trace_msg(trace_channel, 6,
"'%s' key declares %lu bytes of encrypted data and %lu bytes of auth "
"data, but has only %lu bytes remaining", path,
(unsigned long) encrypted_len, (unsigned long) cipher->auth_len,
(unsigned long) buflen);
errno = EPERM;
return -1;
}
len = proxy_ssh_msg_read_data(p, &buf, &buflen, encrypted_len,
&encrypted_data);
#if 0
if (cipher->auth_len > 0) {
/* Read (and ignore) any auth data (for AEAD ciphers). */
(void) proxy_ssh_msg_read_data(p, &encrypted_data, &encrypted_len,
cipher->auth_len, NULL);
}
/* We should have used all of the encrypted data, with none left over. */
if (encrypted_len != 0) {
pr_trace_msg(trace_channel, 3,
"'%s' key provided too much data (%lu bytes remaining unexpectedly)",
path, (unsigned long) encrypted_len);
pr_memscrub(buf, buflen);
errno = EINVAL;
return -1;
}
#endif
return decrypt_openssh_private_key(p, path, encrypted_data, encrypted_len,
passphrase, cipher, kdf_name, kdf_data, kdf_len, key_type, pkey, key,
keylen);
}
static int read_openssh_private_key(pool *p, const char *path, int fd,
const char *passphrase, enum proxy_ssh_key_type_e *key_type,
EVP_PKEY **pkey, unsigned char **key, uint32_t *keylen) {
struct stat st;
pool *tmp_pool;
unsigned char *decoded_buf, *decoded_ptr, *input_buf, *input_ptr;
unsigned char *tmp_key = NULL;
int res, xerrno = 0;
size_t decoded_len, input_len;
off_t input_sz;
if (p == NULL ||
path == NULL ||
fd < 0 ||
key == NULL ||
keylen == NULL) {
errno = EINVAL;
return -1;
}
if (fstat(fd, &st) < 0) {
return -1;
}
tmp_pool = make_sub_pool(p);
/* Read the entire file into memory. */
/* TODO: Impose maximum size limit for this treatment? */
input_sz = st.st_size;
input_ptr = input_buf = palloc(tmp_pool, input_sz);
input_len = 0;
res = read(fd, input_buf, input_sz);
xerrno = errno;
while (res != 0) {
pr_signals_handle();
if (res < 0) {
pr_log_debug(DEBUG0, MOD_PROXY_VERSION ": error reading '%s': %s",
path, strerror(xerrno));
destroy_pool(tmp_pool);
errno = xerrno;
return -1;
}
input_buf += res;
input_len += res;
input_sz -= res;
res = read(fd, input_buf, input_sz);
xerrno = errno;
}
input_buf = input_ptr;
/* Now we read from the input buffer into a buffer for decoding, for use
* in the unwrapping process.
*/
decoded_ptr = decoded_buf = palloc(tmp_pool, input_len);
decoded_len = 0;
/* We know (due to the OpenSSH private key check) that the first bytes
* match the expected start, and that the last bytes match the expected end.
* So skip past them.
*/
input_buf += PROXY_SSH_OPENSSH_BEGIN_LEN;
input_len -= (PROXY_SSH_OPENSSH_BEGIN_LEN + PROXY_SSH_OPENSSH_END_LEN);
while (input_len > 0) {
char ch;
pr_signals_handle();
ch = *input_buf;
/* Skip whitespace */
if (ch != '\r' &&
ch != '\n') {
*decoded_buf++ = ch;
decoded_len++;
}
input_buf++;
input_len--;
}
res = unwrap_openssh_private_key(tmp_pool, path, decoded_ptr, decoded_len,
passphrase, key_type, pkey, &tmp_key, keylen);
xerrno = errno;
if (res < 0) {
destroy_pool(tmp_pool);
errno = xerrno;
return -1;
}
/* At this point, our unwrapped key is allocated out of the temporary pool.
* We need to copy it to memory out of the longer-lived pool given to us.
*/
if (*keylen > 0) {
*key = palloc(p, *keylen);
memcpy(*key, tmp_key, *keylen);
pr_memscrub(tmp_key, *keylen);
}
destroy_pool(tmp_pool);
return 0;
}
#if defined(PR_USE_SODIUM)
static int handle_ed25519_hostkey(pool *p, const unsigned char *key_data,
uint32_t key_datalen, const char *file_path) {
unsigned char *public_key;
if (ed25519_hostkey != NULL) {
/* If we have an existing ED25519 hostkey, free it up. */
pr_memscrub(ed25519_hostkey->ed25519_secret_key,
ed25519_hostkey->ed25519_secret_keylen);
ed25519_hostkey->ed25519_secret_key = NULL;
ed25519_hostkey->ed25519_secret_keylen = 0;
pr_memscrub(ed25519_hostkey->ed25519_public_key,
ed25519_hostkey->ed25519_public_keylen);
ed25519_hostkey->ed25519_public_key = NULL;
ed25519_hostkey->ed25519_public_keylen = 0;
ed25519_hostkey->file_path = NULL;
ed25519_hostkey->agent_path = NULL;
} else {
ed25519_hostkey = pcalloc(p, sizeof(struct proxy_ssh_hostkey));
}
ed25519_hostkey->key_type = PROXY_SSH_KEY_ED25519;
ed25519_hostkey->ed25519_secret_key = (unsigned char *) key_data;
ed25519_hostkey->ed25519_secret_keylen = key_datalen;
/* Use the secret key to get the public key. */
public_key = palloc(p, crypto_sign_ed25519_PUBLICKEYBYTES);
if (crypto_sign_ed25519_sk_to_pk(public_key, key_data) != 0) {
return -1;
}
ed25519_hostkey->ed25519_public_key = public_key;
ed25519_hostkey->ed25519_public_keylen = crypto_sign_ed25519_PUBLICKEYBYTES;
ed25519_hostkey->file_path = file_path;
pr_trace_msg(trace_channel, 4, "using '%s' as Ed25519 hostkey", file_path);
return 0;
}
#endif /* PR_USE_SODIUM */
#if defined(HAVE_X448_OPENSSL)
static int handle_ed448_hostkey(pool *p, const unsigned char *key_data,
uint32_t key_datalen, const char *file_path) {
unsigned char *public_key;
EVP_PKEY *pkey = NULL;
size_t public_keylen = 0;
if (ed448_hostkey != NULL) {
/* If we have an existing ED448 hostkey, free it up. */
pr_memscrub(ed448_hostkey->ed448_secret_key,
ed448_hostkey->ed448_secret_keylen);
ed448_hostkey->ed448_secret_key = NULL;
ed448_hostkey->ed448_secret_keylen = 0;
pr_memscrub(ed448_hostkey->ed448_public_key,
ed448_hostkey->ed448_public_keylen);
ed448_hostkey->ed448_public_key = NULL;
ed448_hostkey->ed448_public_keylen = 0;
ed448_hostkey->file_path = NULL;
ed448_hostkey->agent_path = NULL;
} else {
ed448_hostkey = pcalloc(p, sizeof(struct proxy_ssh_hostkey));
}
ed448_hostkey->key_type = PROXY_SSH_KEY_ED448;
ed448_hostkey->ed448_secret_key = (unsigned char *) key_data;
ed448_hostkey->ed448_secret_keylen = key_datalen;
pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED448, NULL,
ed448_hostkey->ed448_secret_key, ed448_hostkey->ed448_secret_keylen);
if (pkey == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error initializing Ed448 private key: %s",
proxy_ssh_crypto_get_errors());
return -1;
}
/* Use the secret key to get the public key. */
public_keylen = (CURVE448_SIZE * 2);
public_key = palloc(p, public_keylen);
if (EVP_PKEY_get_raw_public_key(pkey, public_key, &public_keylen) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error obtaining Ed448 public key: %s", proxy_ssh_crypto_get_errors());
EVP_PKEY_free(pkey);
return -1;
}
EVP_PKEY_free(pkey);
ed448_hostkey->ed448_public_key = public_key;
ed448_hostkey->ed448_public_keylen = public_keylen;
ed448_hostkey->file_path = file_path;
pr_trace_msg(trace_channel, 4, "using '%s' as Ed448 hostkey", file_path);
return 0;
}
#endif /* HAVE_X448_OPENSSL */
static int load_openssh_hostkey(pool *p, const char *path, int fd) {
const char *passphrase = NULL;
enum proxy_ssh_key_type_e key_type = PROXY_SSH_KEY_UNKNOWN;
EVP_PKEY *pkey = NULL;
unsigned char *key = NULL;
uint32_t keylen = 0;
int res;
if (client_pkey != NULL) {
passphrase = client_pkey->client_pkey;
}
res = read_openssh_private_key(p, path, fd, passphrase, &key_type, &pkey,
&key, &keylen);
if (res < 0) {
return -1;
}
switch (key_type) {
#if defined(PR_USE_SODIUM)
case PROXY_SSH_KEY_ED25519:
res = handle_ed25519_hostkey(p, key, keylen, path);
break;
#endif /* PR_USE_SODIUM */
#if defined(HAVE_X448_OPENSSL)
case PROXY_SSH_KEY_ED448:
res = handle_ed448_hostkey(p, key, keylen, path);
break;
#endif /* HAVE_X448_OPENSSL */
default:
res = handle_hostkey(p, pkey, NULL, 0, path, NULL);
break;
}
return res;
}
static int load_file_hostkey(pool *p, const char *path) {
int fd, xerrno = 0, openssh_format = FALSE, public_key_format = FALSE;
pool *tmp_pool = NULL;
BIO *bio = NULL;
EVP_PKEY *pkey;
pr_signals_block();
PRIVS_ROOT
/* XXX Would we ever want to allow client keys to be read from FIFOs? If
* so, we would need to include the O_NONBLOCK flag here.
*/
fd = open(path, O_RDONLY, 0);
xerrno = errno;
PRIVS_RELINQUISH
pr_signals_unblock();
if (fd < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error reading '%s': %s", path, strerror(xerrno));
errno = xerrno;
return -1;
}
if (has_req_perms(fd, path) < 0) {
xerrno = errno;
if (xerrno == EACCES) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"'%s' is accessible by group or world, which is not allowed", path);
} else {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error checking '%s' perms: %s", path, strerror(xerrno));
}
(void) close(fd);
errno = xerrno;
return -1;
}
if (client_pkey == NULL) {
client_pkey = lookup_pkey();
}
/* Make sure this is not a public key inadvertently configured as a hostkey.
*/
public_key_format = is_public_key(fd);
if (public_key_format == TRUE) {
pr_trace_msg(trace_channel, 3,
"hostkey file '%s' uses a public key format", path);
(void) pr_log_pri(PR_LOG_WARNING, MOD_PROXY_VERSION
": unable to use public key '%s' for ProxySFTPHostKey", path);
(void) close(fd);
errno = EINVAL;
return -1;
}
/* If this happens to be in the OpenSSH private key format, handle it
* separately.
*/
openssh_format = is_openssh_private_key(fd);
if (openssh_format == TRUE) {
int res;
pr_trace_msg(trace_channel, 9,
"hostkey file '%s' uses OpenSSH key format", path);
res = load_openssh_hostkey(p, path, fd);
xerrno = errno;
(void) close(fd);
errno = xerrno;
return res;
}
tmp_pool = make_sub_pool(p);
pr_pool_tag(tmp_pool, "Proxy SSH hostkey BIO pool");
/* Rather than using OpenSSL's PEM_read_PrivateKey and the underlying C
* library's FILE routines, dealing with unbuffered file handles and
* byte-by-byte reads from OpenSSL, we instead provision the file data
* into a memory BIO, and let OpenSSL read from that.
*
* This allows OpenSSL to maintain its byte-by-byte reads, while we read
* the file data using filesystem block-sized reads.
*/
bio = load_hostkey_bio(tmp_pool, fd);
if (bio == NULL) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error reading data from fd %d: %s", fd, strerror(xerrno));
(void) close(fd);
destroy_pool(tmp_pool);
errno = xerrno;
return -1;
}
if (client_pkey != NULL) {
pkey = PEM_read_bio_PrivateKey(bio, NULL, pkey_cb, (void *) client_pkey);
} else {
/* Assume that the key is not passphrase-protected. */
pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, "");
}
free_hostkey_bio(bio);
if (pkey == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error reading private key from '%s': %s", path,
proxy_ssh_crypto_get_errors());
return -1;
}
return handle_hostkey(p, pkey, NULL, 0, path, NULL);
}
int proxy_ssh_keys_get_hostkey(pool *p, const char *path) {
int res;
/* Check whether we are to load keys from a file on disk, or from an
* SSH agent.
*/
if (strncmp(path, "agent:", 6) != 0) {
pr_trace_msg(trace_channel, 9, "loading client key from file '%s'", path);
res = load_file_hostkey(p, path);
} else {
const char *agent_path;
/* Skip past the "agent:" prefix. */
agent_path = (path + 6);
pr_trace_msg(trace_channel, 9, "loading client keys from SSH agent at '%s'",
agent_path);
res = load_agent_hostkeys(p, agent_path);
}
return res;
}
static int get_rsa_hostkey_data(pool *p, const char *key_algo,
unsigned char **buf, unsigned char **ptr, uint32_t *buflen) {
RSA *rsa;
const BIGNUM *rsa_n = NULL, *rsa_e = NULL;
rsa = EVP_PKEY_get1_RSA(rsa_hostkey->pkey);
if (rsa == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error using RSA hostkey: %s", proxy_ssh_crypto_get_errors());
return -1;
}
/* XXX Is this buffer large enough? Too large? */
*ptr = *buf = palloc(p, *buflen);
proxy_ssh_msg_write_string(buf, buflen, key_algo);
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
RSA_get0_key(rsa, &rsa_n, &rsa_e, NULL);
#else
rsa_e = rsa->e;
rsa_n = rsa->n;
#endif /* prior to OpenSSL-1.1.0 */
proxy_ssh_msg_write_mpint(buf, buflen, rsa_e);
proxy_ssh_msg_write_mpint(buf, buflen, rsa_n);
RSA_free(rsa);
return 0;
}
#if !defined(OPENSSL_NO_DSA)
static int get_dsa_hostkey_data(pool *p, unsigned char **buf,
unsigned char **ptr, uint32_t *buflen) {
DSA *dsa;
const BIGNUM *dsa_p = NULL, *dsa_q = NULL, *dsa_g = NULL, *dsa_pub_key = NULL;
dsa = EVP_PKEY_get1_DSA(dsa_hostkey->pkey);
if (dsa == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error using DSA hostkey: %s", proxy_ssh_crypto_get_errors());
return -1;
}
/* XXX Is this buffer large enough? Too large? */
*ptr = *buf = palloc(p, *buflen);
proxy_ssh_msg_write_string(buf, buflen, "ssh-dss");
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
DSA_get0_pqg(dsa, &dsa_p, &dsa_q, &dsa_g);
DSA_get0_key(dsa, &dsa_pub_key, NULL);
#else
dsa_p = dsa->p;
dsa_q = dsa->q;
dsa_g = dsa->g;
dsa_pub_key = dsa->pub_key;;
#endif /* prior to OpenSSL-1.1.0 */
proxy_ssh_msg_write_mpint(buf, buflen, dsa_p);
proxy_ssh_msg_write_mpint(buf, buflen, dsa_q);
proxy_ssh_msg_write_mpint(buf, buflen, dsa_g);
proxy_ssh_msg_write_mpint(buf, buflen, dsa_pub_key);
DSA_free(dsa);
return 0;
}
#endif /* !OPENSSL_NO_DSA */
#if defined(PR_USE_OPENSSL_ECC)
static int get_ecdsa_hostkey_data(pool *p,
struct proxy_ssh_hostkey *hostkey, const char *algo, const char *curve,
unsigned char **buf, unsigned char **ptr, uint32_t *buflen) {
EC_KEY *ec;
ec = EVP_PKEY_get1_EC_KEY(hostkey->pkey);
if (ec == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error using %s hostkey: %s", algo, proxy_ssh_crypto_get_errors());
return -1;
}
/* XXX Is this buffer large enough? Too large? */
*ptr = *buf = palloc(p, *buflen);
proxy_ssh_msg_write_string(buf, buflen, algo);
proxy_ssh_msg_write_string(buf, buflen, curve);
proxy_ssh_msg_write_ecpoint(buf, buflen, EC_KEY_get0_group(ec),
EC_KEY_get0_public_key(ec));
EC_KEY_free(ec);
return 0;
}
#endif /* PR_USE_OPENSSL_ECC */
#if defined(PR_USE_SODIUM)
static int get_ed25519_hostkey_data(pool *p, unsigned char **buf,
unsigned char **ptr, uint32_t *buflen) {
/* XXX Is this buffer large enough? Too large? */
*ptr = *buf = palloc(p, *buflen);
proxy_ssh_msg_write_string(buf, buflen, "ssh-ed25519");
proxy_ssh_msg_write_data(buf, buflen, ed25519_hostkey->ed25519_public_key,
ed25519_hostkey->ed25519_public_keylen, TRUE);
return 0;
}
#endif /* PR_USE_SODIUM */
#if defined(HAVE_X448_OPENSSL)
static int get_ed448_hostkey_data(pool *p, unsigned char **buf,
unsigned char **ptr, uint32_t *buflen) {
/* XXX Is this buffer large enough? Too large? */
*ptr = *buf = palloc(p, *buflen);
proxy_ssh_msg_write_string(buf, buflen, "ssh-ed448");
proxy_ssh_msg_write_data(buf, buflen, ed448_hostkey->ed448_public_key,
ed448_hostkey->ed448_public_keylen, TRUE);
return 0;
}
#endif /* HAVE_X448_OPENSSL */
int proxy_ssh_keys_have_hostkey(enum proxy_ssh_key_type_e key_type) {
/* If the requested type is PROXY_SSH_KEY_UNKNOWN, the caller is asking
* if we have any hostkeys configured at all, regardless of type.
*/
if (key_type == PROXY_SSH_KEY_UNKNOWN) {
if (dsa_hostkey != NULL ||
rsa_hostkey != NULL) {
return 0;
}
#if defined(PR_USE_OPENSSL_ECC)
if (ecdsa256_hostkey != NULL ||
ecdsa384_hostkey != NULL ||
ecdsa521_hostkey != NULL) {
return 0;
}
#endif /* PR_USE_OPENSSL_ECC */
#if defined(PR_USE_SODIUM)
if (ed25519_hostkey != NULL) {
return 0;
}
#endif /* PR_USE_SODIUM */
#if defined(HAVE_X448_OPENSSL)
if (ed448_hostkey != NULL) {
return 0;
}
#endif /* HAVE_X448_OPENSSL */
errno = ENOENT;
return -1;
}
switch (key_type) {
case PROXY_SSH_KEY_DSA:
if (dsa_hostkey != NULL) {
return 0;
}
break;
case PROXY_SSH_KEY_RSA:
case PROXY_SSH_KEY_RSA_SHA256:
case PROXY_SSH_KEY_RSA_SHA512:
if (rsa_hostkey != NULL) {
return 0;
}
break;
#if defined(PR_USE_OPENSSL_ECC)
case PROXY_SSH_KEY_ECDSA_256:
if (ecdsa256_hostkey != NULL) {
return 0;
}
break;
case PROXY_SSH_KEY_ECDSA_384:
if (ecdsa384_hostkey != NULL) {
return 0;
}
break;
case PROXY_SSH_KEY_ECDSA_521:
if (ecdsa521_hostkey != NULL) {
return 0;
}
break;
#endif /* PR_USE_OPENSSL_ECC */
#if defined(PR_USE_SODIUM)
case PROXY_SSH_KEY_ED25519:
if (ed25519_hostkey != NULL) {
return 0;
}
break;
#endif /* PR_USE_SODIUM */
#if defined(HAVE_X448_OPENSSL)
case PROXY_SSH_KEY_ED448:
if (ed448_hostkey != NULL) {
return 0;
}
break;
#endif /* HAVE_X448_OPENSSL */
default:
break;
}
errno = ENOENT;
return -1;
}
const unsigned char *proxy_ssh_keys_get_hostkey_data(pool *p,
enum proxy_ssh_key_type_e key_type, uint32_t *datalen) {
unsigned char *buf = NULL, *ptr = NULL;
uint32_t buflen = PROXY_SSH_DEFAULT_HOSTKEY_SZ;
int res;
switch (key_type) {
case PROXY_SSH_KEY_RSA:
case PROXY_SSH_KEY_RSA_SHA256:
case PROXY_SSH_KEY_RSA_SHA512: {
const char *key_algo = "ssh-rsa";
if (key_type == PROXY_SSH_KEY_RSA_SHA256) {
key_algo = "rsa-sha2-256";
} else if (key_type == PROXY_SSH_KEY_RSA_SHA256) {
key_algo = "rsa-sha2-256";
}
res = get_rsa_hostkey_data(p, key_algo, &buf, &ptr, &buflen);
if (res < 0) {
return NULL;
}
break;
}
#if !defined(OPENSSL_NO_DSA)
case PROXY_SSH_KEY_DSA: {
res = get_dsa_hostkey_data(p, &buf, &ptr, &buflen);
if (res < 0) {
return NULL;
}
break;
}
#endif /* !OPENSSL_NO_DSA */
#if defined(PR_USE_OPENSSL_ECC)
case PROXY_SSH_KEY_ECDSA_256: {
res = get_ecdsa_hostkey_data(p, ecdsa256_hostkey,
"ecdsa-sha2-nistp256", "nistp256", &buf, &ptr, &buflen);
if (res < 0) {
return NULL;
}
break;
}
case PROXY_SSH_KEY_ECDSA_384: {
res = get_ecdsa_hostkey_data(p, ecdsa384_hostkey,
"ecdsa-sha2-nistp384", "nistp384", &buf, &ptr, &buflen);
if (res < 0) {
return NULL;
}
break;
}
case PROXY_SSH_KEY_ECDSA_521: {
res = get_ecdsa_hostkey_data(p, ecdsa521_hostkey,
"ecdsa-sha2-nistp521", "nistp521", &buf, &ptr, &buflen);
if (res < 0) {
return NULL;
}
break;
}
#endif /* PR_USE_OPENSSL_ECC */
#if defined(PR_USE_SODIUM)
case PROXY_SSH_KEY_ED25519: {
res = get_ed25519_hostkey_data(p, &buf, &ptr, &buflen);
if (res < 0) {
return NULL;
}
break;
}
#endif /* PR_USE_SODIUM */
#if defined(HAVE_X448_OPENSSL)
case PROXY_SSH_KEY_ED448: {
res = get_ed448_hostkey_data(p, &buf, &ptr, &buflen);
if (res < 0) {
return NULL;
}
break;
}
#endif /* HAVE_X448_OPENSSL */
default:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unknown/unsupported key type (%d) requested, ignoring", key_type);
return NULL;
}
*datalen = PROXY_SSH_DEFAULT_HOSTKEY_SZ - buflen;
/* If the caller provided a pool, make a copy of the data from the
* given pool, and return the copy. Make sure to scrub the original
* after making the copy.
*
* Note that we do this copy, even though we use the given pool, since
* we only know the actual size of the data after the fact. And we need
* to provide the size of the data to the caller, NOT the optimistic size
* we allocate out of the pool for writing the data in the first place.
* Hence the copy.
*/
buf = palloc(p, *datalen);
memcpy(buf, ptr, *datalen);
pr_memscrub(ptr, *datalen);
return buf;
}
static const unsigned char *agent_sign_data(pool *p, const char *agent_path,
const unsigned char *key_data, uint32_t key_datalen,
const unsigned char *data, size_t datalen, size_t *siglen, int flags) {
unsigned char *sig_data;
uint32_t sig_datalen = 0;
pr_trace_msg(trace_channel, 15,
"asking SSH agent at '%s' to sign data", agent_path);
/* Ask the agent to sign the data for this hostkey for us. */
sig_data = (unsigned char *) proxy_ssh_agent_sign_data(p, agent_path,
key_data, key_datalen, data, datalen, &sig_datalen, flags);
if (sig_data == NULL) {
int xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"SSH agent at '%s' could not sign data: %s", agent_path,
strerror(xerrno));
errno = xerrno;
return NULL;
}
/* The SSH agent already provides the signed data in the correct
* SSH2-style.
*/
*siglen = sig_datalen;
return sig_data;
}
static const unsigned char *get_rsa_signed_data(pool *p,
const unsigned char *data, size_t datalen, size_t *siglen,
const char *sig_name, const EVP_MD *md) {
RSA *rsa;
#if OPENSSL_VERSION_NUMBER < 0x10100000L || \
defined(HAVE_LIBRESSL)
EVP_MD_CTX ctx;
#endif /* prior to OpenSSL-1.1.0 */
EVP_MD_CTX *pctx;
unsigned char dgst[EVP_MAX_MD_SIZE], *sig_data;
unsigned char *buf, *ptr;
size_t bufsz;
uint32_t buflen, dgstlen = 0, sig_datalen = 0, sig_rsalen = 0;
int res;
rsa = EVP_PKEY_get1_RSA(rsa_hostkey->pkey);
if (rsa == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error using RSA hostkey: %s", proxy_ssh_crypto_get_errors());
return NULL;
}
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
pctx = EVP_MD_CTX_new();
#else
pctx = &ctx;
#endif /* prior to OpenSSL-1.1.0 */
EVP_DigestInit(pctx, md);
EVP_DigestUpdate(pctx, data, datalen);
EVP_DigestFinal(pctx, dgst, &dgstlen);
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
EVP_MD_CTX_free(pctx);
#endif /* OpenSSL-1.1.0 and later */
sig_rsalen = RSA_size(rsa);
sig_data = pcalloc(p, sig_rsalen);
res = RSA_sign(EVP_MD_type(md), dgst, dgstlen, sig_data, &sig_datalen, rsa);
/* Regardless of whether the RSA signing succeeds or fails, we are done
* with the digest buffer.
*/
pr_memscrub(dgst, dgstlen);
if (res != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error signing data using RSA: %s", proxy_ssh_crypto_get_errors());
RSA_free(rsa);
return NULL;
}
/* XXX Is this buffer large enough? Too large? */
buflen = bufsz = PROXY_SSH_MAX_SIG_SZ;
ptr = buf = palloc(p, bufsz);
/* Now build up the signature, SSH2-style */
proxy_ssh_msg_write_string(&buf, &buflen, sig_name);
proxy_ssh_msg_write_data(&buf, &buflen, sig_data, sig_datalen, TRUE);
pr_memscrub(sig_data, sig_datalen);
RSA_free(rsa);
/* At this point, buflen is the amount remaining in the allocated buffer.
* So the total length of the signed data is the buffer size, minus those
* remaining unused bytes.
*/
*siglen = (bufsz - buflen);
return ptr;
}
static const unsigned char *rsa_sign_data(pool *p, const unsigned char *data,
size_t datalen, size_t *siglen) {
if (rsa_hostkey->agent_path != NULL) {
return agent_sign_data(p, rsa_hostkey->agent_path,
rsa_hostkey->key_data, rsa_hostkey->key_datalen, data, datalen,
siglen, 0);
}
return get_rsa_signed_data(p, data, datalen, siglen, "ssh-rsa", EVP_sha1());
}
/* RFC 4253, Section 6.6, is quite specific about the length of a DSA
* ("ssh-dss") signature blob. It is comprised of two integers R and S,
* each 160 bits (20 bytes), so that the total signature blob is 40 bytes
* long.
*/
#define PROXY_SSH_DSA_INTEGER_LEN 20
#define PROXY_SSH_DSA_SIGNATURE_LEN (PROXY_SSH_DSA_INTEGER_LEN * 2)
#if !defined(OPENSSL_NO_DSA)
static const unsigned char *dsa_sign_data(pool *p, const unsigned char *data,
size_t datalen, size_t *siglen) {
DSA *dsa;
DSA_SIG *sig;
const BIGNUM *sig_r = NULL, *sig_s = NULL;
#if OPENSSL_VERSION_NUMBER < 0x10100000L || \
defined(HAVE_LIBRESSL)
EVP_MD_CTX ctx;
#endif /* prior to OpenSSL-1.1.0 */
EVP_MD_CTX *pctx;
const EVP_MD *sha1 = EVP_sha1();
unsigned char dgst[EVP_MAX_MD_SIZE], *sig_data;
unsigned char *buf, *ptr;
size_t bufsz;
uint32_t buflen, dgstlen = 0;
unsigned int rlen = 0, slen = 0;
if (dsa_hostkey->agent_path != NULL) {
return agent_sign_data(p, dsa_hostkey->agent_path,
dsa_hostkey->key_data, dsa_hostkey->key_datalen, data, datalen,
siglen, 0);
}
dsa = EVP_PKEY_get1_DSA(dsa_hostkey->pkey);
if (dsa == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error using DSA hostkey: %s", proxy_ssh_crypto_get_errors());
return NULL;
}
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
pctx = EVP_MD_CTX_new();
#else
pctx = &ctx;
#endif /* prior to OpenSSL-1.1.0 */
EVP_DigestInit(pctx, sha1);
EVP_DigestUpdate(pctx, data, datalen);
EVP_DigestFinal(pctx, dgst, &dgstlen);
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
EVP_MD_CTX_free(pctx);
#endif /* OpenSSL-1.1.0 and later */
sig = DSA_do_sign(dgst, dgstlen, dsa);
if (sig == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error obtaining DSA signature: %s", proxy_ssh_crypto_get_errors());
pr_memscrub(dgst, dgstlen);
DSA_free(dsa);
return NULL;
}
/* Got the signature, no need for the digest memory. */
pr_memscrub(dgst, dgstlen);
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
DSA_SIG_get0(sig, &sig_r, &sig_s);
#else
sig_r = sig->r;
sig_s = sig->s;
#endif /* prior to OpenSSL-1.1.0 */
rlen = BN_num_bytes(sig_r);
slen = BN_num_bytes(sig_s);
/* Make sure the values of R and S are big enough. */
if (rlen > PROXY_SSH_DSA_INTEGER_LEN ||
slen > PROXY_SSH_DSA_INTEGER_LEN) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"bad DSA signature size (%u, %u)", rlen, slen);
DSA_SIG_free(sig);
DSA_free(dsa);
return NULL;
}
sig_data = pcalloc(p, PROXY_SSH_MAX_SIG_SZ);
/* These may look strange, but the pointer arithmetic is necessary to
* ensure the correct placement of the R and S values in the signature,
* per RFC 4253 Section 6.6 requirements.
*/
BN_bn2bin(sig_r,
sig_data + PROXY_SSH_DSA_SIGNATURE_LEN - PROXY_SSH_DSA_INTEGER_LEN - rlen);
BN_bn2bin(sig_s, sig_data + PROXY_SSH_DSA_SIGNATURE_LEN - slen);
/* Done with the signature. */
DSA_SIG_free(sig);
DSA_free(dsa);
/* XXX Is this buffer large enough? Too large? */
buflen = bufsz = PROXY_SSH_MAX_SIG_SZ;
ptr = buf = palloc(p, bufsz);
/* Now build up the signature, SSH2-style */
proxy_ssh_msg_write_string(&buf, &buflen, "ssh-dss");
proxy_ssh_msg_write_data(&buf, &buflen, sig_data,
PROXY_SSH_DSA_SIGNATURE_LEN, TRUE);
/* At this point, buflen is the amount remaining in the allocated buffer.
* So the total length of the signed data is the buffer size, minus those
* remaining unused bytes.
*/
*siglen = (bufsz - buflen);
return ptr;
}
#endif /* !OPENSSL_NO_DSA */
#if defined(PR_USE_OPENSSL_ECC)
static const unsigned char *ecdsa_sign_data(pool *p, const unsigned char *data,
size_t datalen, size_t *siglen, int nid) {
EVP_PKEY *pkey = NULL;
EC_KEY *ec = NULL;
ECDSA_SIG *sig;
const BIGNUM *sig_r = NULL, *sig_s = NULL;
#if OPENSSL_VERSION_NUMBER < 0x10100000L || \
defined(HAVE_LIBRESSL)
EVP_MD_CTX ctx;
#endif /* prior to OpenSSL-1.1.0 */
EVP_MD_CTX *pctx;
const EVP_MD *md;
unsigned char dgst[EVP_MAX_MD_SIZE];
unsigned char *buf, *ptr, *sig_buf, *sig_ptr;
uint32_t bufsz, buflen, dgstlen = 0, sig_buflen, sig_bufsz;
switch (nid) {
case NID_X9_62_prime256v1:
if (ecdsa256_hostkey->agent_path != NULL) {
return agent_sign_data(p, ecdsa256_hostkey->agent_path,
ecdsa256_hostkey->key_data, ecdsa256_hostkey->key_datalen,
data, datalen, siglen, 0);
}
ec = EVP_PKEY_get1_EC_KEY(ecdsa256_hostkey->pkey);
if (ec == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error using ECDSA-256 hostkey: %s", proxy_ssh_crypto_get_errors());
return NULL;
}
pkey = ecdsa256_hostkey->pkey;
md = EVP_sha256();
break;
case NID_secp384r1:
if (ecdsa384_hostkey->agent_path != NULL) {
return agent_sign_data(p, ecdsa384_hostkey->agent_path,
ecdsa384_hostkey->key_data, ecdsa384_hostkey->key_datalen,
data, datalen, siglen, 0);
}
ec = EVP_PKEY_get1_EC_KEY(ecdsa384_hostkey->pkey);
if (ec == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error using ECDSA-384 hostkey: %s", proxy_ssh_crypto_get_errors());
return NULL;
}
pkey = ecdsa384_hostkey->pkey;
md = EVP_sha384();
break;
case NID_secp521r1:
if (ecdsa521_hostkey->agent_path != NULL) {
return agent_sign_data(p, ecdsa521_hostkey->agent_path,
ecdsa521_hostkey->key_data, ecdsa521_hostkey->key_datalen,
data, datalen, siglen, 0);
}
ec = EVP_PKEY_get1_EC_KEY(ecdsa521_hostkey->pkey);
if (ec == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error using ECDSA-521 hostkey: %s", proxy_ssh_crypto_get_errors());
return NULL;
}
pkey = ecdsa521_hostkey->pkey;
md = EVP_sha512();
break;
default:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unknown/unsupported ECDSA NID (%d) requested", nid);
return NULL;
}
buflen = bufsz = PROXY_SSH_MAX_SIG_SZ;
ptr = buf = palloc(p, bufsz);
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
pctx = EVP_MD_CTX_new();
#else
pctx = &ctx;
#endif /* prior to OpenSSL-1.1.0 */
EVP_DigestInit(pctx, md);
EVP_DigestUpdate(pctx, data, datalen);
EVP_DigestFinal(pctx, dgst, &dgstlen);
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
EVP_MD_CTX_free(pctx);
#endif /* OpenSSL-1.1.0 and later */
sig = ECDSA_do_sign(dgst, dgstlen, ec);
if (sig == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error obtaining ECDSA signature: %s", proxy_ssh_crypto_get_errors());
pr_memscrub(dgst, dgstlen);
EC_KEY_free(ec);
return NULL;
}
/* Got the signature, no need for the digest memory. */
pr_memscrub(dgst, dgstlen);
/* Unlike DSA, the R and S lengths for ECDSA are dependent on the curve
* selected, so we do no sanity checking of their lengths.
*/
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
ECDSA_SIG_get0(sig, &sig_r, &sig_s);
#else
sig_r = sig->r;
sig_s = sig->s;
#endif /* prior to OpenSSL-1.1.0 */
/* XXX Is this buffer large enough? Too large? */
sig_buflen = sig_bufsz = 256;
sig_ptr = sig_buf = palloc(p, sig_bufsz);
proxy_ssh_msg_write_mpint(&sig_buf, &sig_buflen, sig_r);
proxy_ssh_msg_write_mpint(&sig_buf, &sig_buflen, sig_s);
/* Done with the signature. */
ECDSA_SIG_free(sig);
EC_KEY_free(ec);
/* XXX Is this buffer large enough? Too large? */
buflen = bufsz = PROXY_SSH_MAX_SIG_SZ;
ptr = buf = palloc(p, bufsz);
/* Now build up the signature, SSH2-style */
switch (nid) {
case NID_X9_62_prime256v1:
proxy_ssh_msg_write_string(&buf, &buflen, "ecdsa-sha2-nistp256");
break;
case NID_secp384r1:
proxy_ssh_msg_write_string(&buf, &buflen, "ecdsa-sha2-nistp384");
break;
case NID_secp521r1:
proxy_ssh_msg_write_string(&buf, &buflen, "ecdsa-sha2-nistp521");
break;
}
proxy_ssh_msg_write_data(&buf, &buflen, sig_ptr, (sig_bufsz - sig_buflen),
TRUE);
pr_memscrub(sig_ptr, sig_bufsz);
/* At this point, buflen is the amount remaining in the allocated buffer.
* So the total length of the signed data is the buffer size, minus those
* remaining unused bytes.
*/
*siglen = (bufsz - buflen);
return ptr;
}
#endif /* PR_USE_OPENSSL_ECC */
#if defined(PR_USE_SODIUM)
static const unsigned char *ed25519_sign_data(pool *p,
const unsigned char *data, size_t datalen, size_t *siglen) {
unsigned char *buf, *ptr, *sig_buf, *sig_ptr;
uint32_t bufsz, buflen, sig_buflen, sig_bufsz;
unsigned long long slen;
int res;
/* XXX TODO ED25519: Test this! */
if (ed25519_hostkey->agent_path != NULL) {
return agent_sign_data(p, ed25519_hostkey->agent_path,
ed25519_hostkey->ed25519_public_key,
ed25519_hostkey->ed25519_public_keylen,
data, datalen, siglen, 0);
}
sig_buflen = sig_bufsz = slen = datalen + crypto_sign_ed25519_BYTES;
sig_ptr = sig_buf = palloc(p, sig_bufsz);
res = crypto_sign_ed25519(sig_buf, &slen, data, datalen,
ed25519_hostkey->ed25519_secret_key);
if (res != 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"failed to sign data using Ed25519 (%d)", res);
pr_memscrub(sig_ptr, sig_bufsz);
return NULL;
}
sig_buflen = slen;
if (sig_buflen <= datalen) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"invalid Ed25519 signature (%lu bytes) generated, expected more than "
"%lu bytes", (unsigned long) sig_buflen, (unsigned long) datalen);
pr_memscrub(sig_ptr, sig_bufsz);
return NULL;
}
/* XXX Is this buffer large enough? Too large? */
buflen = bufsz = PROXY_SSH_MAX_SIG_SZ;
ptr = buf = palloc(p, bufsz);
/* Now build up the signature, SSH2-style */
proxy_ssh_msg_write_string(&buf, &buflen, "ssh-ed25519");
proxy_ssh_msg_write_data(&buf, &buflen, sig_ptr, sig_buflen - datalen, TRUE);
pr_memscrub(sig_ptr, sig_bufsz);
/* At this point, buflen is the amount remaining in the allocated buffer.
* So the total length of the signed data is the buffer size, minus those
* remaining unused bytes.
*/
*siglen = (bufsz - buflen);
return ptr;
}
#endif /* PR_USE_SODIUM */
#if defined(HAVE_X448_OPENSSL)
static const unsigned char *ed448_sign_data(pool *p,
const unsigned char *data, size_t datalen, size_t *siglen) {
unsigned char *buf, *ptr, *sig_buf;
uint32_t bufsz, buflen, sig_buflen, sig_bufsz;
EVP_MD_CTX *md_ctx;
EVP_PKEY *pkey;
/* XXX TODO ED448: Test this! */
if (ed448_hostkey->agent_path != NULL) {
return agent_sign_data(p, ed448_hostkey->agent_path,
ed448_hostkey->ed448_public_key, ed448_hostkey->ed448_public_keylen,
data, datalen, siglen, 0);
}
sig_buflen = sig_bufsz = datalen + 256;
sig_buf = palloc(p, sig_bufsz);
md_ctx = EVP_MD_CTX_new();
pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED448, NULL,
ed448_hostkey->ed448_secret_key, ed448_hostkey->ed448_secret_keylen);
if (pkey == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error initializing Ed448 private key: %s",
proxy_ssh_crypto_get_errors());
EVP_MD_CTX_free(md_ctx);
return NULL;
}
if (EVP_DigestSignInit(md_ctx, NULL, NULL, NULL, pkey) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error initializing Ed448 signature: %s", proxy_ssh_crypto_get_errors());
EVP_PKEY_free(pkey);
EVP_MD_CTX_free(md_ctx);
return NULL;
}
if (EVP_DigestSign(md_ctx, sig_buf, (size_t *) &sig_buflen, data,
datalen) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"failed to sign data using Ed448: %s", proxy_ssh_crypto_get_errors());
EVP_PKEY_free(pkey);
EVP_MD_CTX_free(md_ctx);
return NULL;
}
EVP_PKEY_free(pkey);
EVP_MD_CTX_free(md_ctx);
/* XXX Is this buffer large enough? Too large? */
buflen = bufsz = PROXY_SSH_MAX_SIG_SZ;
ptr = buf = palloc(p, bufsz);
/* Now build up the signature, SSH2-style */
proxy_ssh_msg_write_string(&buf, &buflen, "ssh-ed448");
proxy_ssh_msg_write_data(&buf, &buflen, sig_buf, sig_buflen, TRUE);
pr_memscrub(sig_buf, sig_bufsz);
/* At this point, buflen is the amount remaining in the allocated buffer.
* So the total length of the signed data is the buffer size, minus those
* remaining unused bytes.
*/
*siglen = (bufsz - buflen);
return ptr;
}
#endif /* HAVE_X448_OPENSSL */
const unsigned char *proxy_ssh_keys_sign_data(pool *p,
enum proxy_ssh_key_type_e key_type, const unsigned char *data,
size_t datalen, size_t *siglen) {
const unsigned char *res;
switch (key_type) {
case PROXY_SSH_KEY_RSA:
res = rsa_sign_data(p, data, datalen, siglen);
break;
#if !defined(OPENSSL_NO_DSA)
case PROXY_SSH_KEY_DSA:
res = dsa_sign_data(p, data, datalen, siglen);
break;
#endif /* !OPENSSL_NO_DSA */
#ifdef PR_USE_OPENSSL_ECC
case PROXY_SSH_KEY_ECDSA_256:
res = ecdsa_sign_data(p, data, datalen, siglen, NID_X9_62_prime256v1);
break;
case PROXY_SSH_KEY_ECDSA_384:
res = ecdsa_sign_data(p, data, datalen, siglen, NID_secp384r1);
break;
case PROXY_SSH_KEY_ECDSA_521:
res = ecdsa_sign_data(p, data, datalen, siglen, NID_secp521r1);
break;
#endif /* PR_USE_OPENSSL_ECC */
#if defined(PR_USE_SODIUM)
case PROXY_SSH_KEY_ED25519:
res = ed25519_sign_data(p, data, datalen, siglen);
break;
#endif /* PR_USE_SODIUM */
#if defined(HAVE_X448_OPENSSL)
case PROXY_SSH_KEY_ED448:
res = ed448_sign_data(p, data, datalen, siglen);
break;
#endif /* HAVE_X448_OPENSSL */
default:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unknown key type (%d) requested for signing, ignoring", key_type);
return NULL;
}
if (res != NULL &&
p != NULL) {
unsigned char *buf;
buf = palloc(p, *siglen);
memcpy(buf, res, *siglen);
pr_memscrub((char *) res, *siglen);
return buf;
}
return res;
}
int proxy_ssh_keys_verify_pubkey_type(pool *p, unsigned char *pubkey_data,
uint32_t pubkey_len, enum proxy_ssh_key_type_e pubkey_type) {
EVP_PKEY *pkey = NULL;
int res = FALSE;
uint32_t len;
if (pubkey_data == NULL ||
pubkey_len == 0) {
errno = EINVAL;
return -1;
}
len = read_pkey_from_data(p, pubkey_data, pubkey_len, &pkey, NULL, FALSE);
if (len == 0) {
return -1;
}
switch (pubkey_type) {
case PROXY_SSH_KEY_RSA:
res = (get_pkey_type(pkey) == EVP_PKEY_RSA);
break;
case PROXY_SSH_KEY_DSA:
res = (get_pkey_type(pkey) == EVP_PKEY_DSA);
break;
#ifdef PR_USE_OPENSSL_ECC
case PROXY_SSH_KEY_ECDSA_256:
case PROXY_SSH_KEY_ECDSA_384:
case PROXY_SSH_KEY_ECDSA_521:
if (get_pkey_type(pkey) == EVP_PKEY_EC) {
EC_KEY *ec;
int ec_nid;
ec = EVP_PKEY_get1_EC_KEY(pkey);
ec_nid = get_ecdsa_nid(ec);
EC_KEY_free(ec);
switch (ec_nid) {
case NID_X9_62_prime256v1:
res = (pubkey_type == PROXY_SSH_KEY_ECDSA_256);
break;
case NID_secp384r1:
res = (pubkey_type == PROXY_SSH_KEY_ECDSA_384);
break;
case NID_secp521r1:
res = (pubkey_type == PROXY_SSH_KEY_ECDSA_521);
break;
}
}
break;
#endif /* PR_USE_OPENSSL_ECC */
#if defined(PR_USE_SODIUM)
case PROXY_SSH_KEY_ED25519: {
char *pkey_type;
len = proxy_ssh_msg_read_string(p, &pubkey_data, &pubkey_len, &pkey_type);
if (strcmp(pkey_type, "ssh-ed25519") != 0) {
pr_trace_msg(trace_channel, 8,
"invalid public key type '%s' for Ed25519 key", pkey_type);
res = FALSE;
} else {
uint32_t pklen;
len = proxy_ssh_msg_read_int(p, &pubkey_data, &pubkey_len, &pklen);
res = (pklen == (uint32_t) crypto_sign_ed25519_PUBLICKEYBYTES);
if (res == FALSE) {
pr_trace_msg(trace_channel, 8,
"Ed25519 public key length (%lu bytes) does not match expected "
"length (%lu bytes)", (unsigned long) pklen,
(unsigned long) crypto_sign_ed25519_PUBLICKEYBYTES);
}
}
break;
}
#endif /* PR_USE_SODIUM */
default:
/* No matching public key type/algorithm. */
errno = ENOENT;
res = FALSE;
break;
}
if (pkey != NULL) {
EVP_PKEY_free(pkey);
}
return res;
}
static int verify_rsa_signed_data(pool *p, EVP_PKEY *pkey,
unsigned char *signature, uint32_t signature_len,
unsigned char *sig_data, size_t sig_datalen, const EVP_MD *md) {
#if OPENSSL_VERSION_NUMBER < 0x10100000L || \
defined(HAVE_LIBRESSL)
EVP_MD_CTX ctx;
#endif /* prior to OpenSSL-1.1.0 */
EVP_MD_CTX *pctx;
RSA *rsa;
uint32_t len, sig_len;
unsigned char digest[EVP_MAX_MD_SIZE], *sig;
unsigned int digest_len = 0, modulus_len = 0;
int ok = FALSE, res = 0;
len = proxy_ssh_msg_read_int(p, &signature, &signature_len, &sig_len);
if (len == 0) {
errno = EINVAL;
return -1;
}
len = proxy_ssh_msg_read_data(p, &signature, &signature_len, sig_len, &sig);
if (len == 0) {
errno = EINVAL;
return -1;
}
if (sig == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error verifying RSA signature: missing signature data");
errno = EINVAL;
return -1;
}
rsa = EVP_PKEY_get1_RSA(pkey);
modulus_len = RSA_size(rsa);
/* If the signature provided by the server is more than the expected
* key length, the verification will fail.
*/
if (sig_len > modulus_len) {
RSA_free(rsa);
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error verifying RSA signature: signature len (%lu) > RSA modulus "
"len (%u)", (unsigned long) sig_len, modulus_len);
errno = EINVAL;
return -1;
}
/* If the signature provided by the server is less than the expected
* key length, the verification will fail. In such cases, we need to
* pad the provided signature with leading zeros (Bug#3992).
*/
if (sig_len < modulus_len) {
unsigned int padding_len;
unsigned char *padded_sig;
padding_len = modulus_len - sig_len;
padded_sig = pcalloc(p, modulus_len);
pr_trace_msg(trace_channel, 12, "padding server-sent RSA signature "
"(%lu) bytes with %u bytes of zeroed data", (unsigned long) sig_len,
padding_len);
memmove(padded_sig + padding_len, sig, sig_len);
sig = padded_sig;
sig_len = (uint32_t) modulus_len;
}
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
pctx = EVP_MD_CTX_new();
#else
pctx = &ctx;
#endif /* prior to OpenSSL-1.1.0 */
EVP_DigestInit(pctx, md);
EVP_DigestUpdate(pctx, sig_data, sig_datalen);
EVP_DigestFinal(pctx, digest, &digest_len);
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
EVP_MD_CTX_free(pctx);
#endif /* OpenSSL-1.1.0 and later */
ok = RSA_verify(EVP_MD_type(md), digest, digest_len, sig, sig_len, rsa);
if (ok == 1) {
res = 0;
} else {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error verifying RSA signature: %s", proxy_ssh_crypto_get_errors());
errno = EINVAL;
res = -1;
}
pr_memscrub(digest, digest_len);
RSA_free(rsa);
return res;
}
static int rsa_verify_signed_data(pool *p, EVP_PKEY *pkey,
unsigned char *signature, uint32_t signature_len,
unsigned char *sig_data, size_t sig_datalen) {
return verify_rsa_signed_data(p, pkey, signature, signature_len,
sig_data, sig_datalen, EVP_sha1());
}
#if defined(HAVE_SHA256_OPENSSL)
static int rsa_sha256_verify_signed_data(pool *p, EVP_PKEY *pkey,
unsigned char *signature, uint32_t signature_len,
unsigned char *sig_data, size_t sig_datalen) {
return verify_rsa_signed_data(p, pkey, signature, signature_len,
sig_data, sig_datalen, EVP_sha256());
}
#endif /* HAVE_SHA256_OPENSSL */
#if defined(HAVE_SHA512_OPENSSL)
static int rsa_sha512_verify_signed_data(pool *p, EVP_PKEY *pkey,
unsigned char *signature, uint32_t signature_len,
unsigned char *sig_data, size_t sig_datalen) {
return verify_rsa_signed_data(p, pkey, signature, signature_len,
sig_data, sig_datalen, EVP_sha512());
}
#endif /* HAVE_SHA512_OPENSSL */
#if !defined(OPENSSL_NO_DSA)
static int dsa_verify_signed_data(pool *p, EVP_PKEY *pkey,
unsigned char *signature, uint32_t signature_len,
unsigned char *sig_data, size_t sig_datalen) {
#if OPENSSL_VERSION_NUMBER < 0x10100000L || \
defined(HAVE_LIBRESSL)
EVP_MD_CTX ctx;
#endif /* prior to OpenSSL-1.1.0 */
EVP_MD_CTX *pctx;
DSA *dsa;
DSA_SIG *dsa_sig;
const BIGNUM *sig_r, *sig_s;
uint32_t len, sig_len;
unsigned char digest[EVP_MAX_MD_SIZE], *sig;
unsigned int digest_len = 0;
int ok = FALSE, res = 0;
len = proxy_ssh_msg_read_int(p, &signature, &signature_len, &sig_len);
if (len == 0) {
errno = EINVAL;
return -1;
}
/* A DSA signature string is composed of 2 20 character parts. */
if (sig_len != 40) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"bad DSA signature len (%lu)", (unsigned long) sig_len);
}
len = proxy_ssh_msg_read_data(p, &signature, &signature_len, sig_len, &sig);
if (len == 0) {
errno = EINVAL;
return -1;
}
if (sig == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error verifying DSA signature: missing signature data");
errno = EINVAL;
return -1;
}
dsa = EVP_PKEY_get1_DSA(pkey);
dsa_sig = DSA_SIG_new();
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
DSA_SIG_get0(dsa_sig, &sig_r, &sig_s);
#else
sig_r = dsa_sig->r;
sig_s = dsa_sig->s;
#endif /* prior to OpenSSL-1.1.0 */
sig_r = BN_bin2bn(sig, 20, (BIGNUM *) sig_r);
if (sig_r == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error obtaining 'r' DSA signature component: %s",
proxy_ssh_crypto_get_errors());
DSA_free(dsa);
DSA_SIG_free(dsa_sig);
return -1;
}
sig_s = BN_bin2bn(sig + 20, 20, (BIGNUM *) sig_s);
if (sig_s == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error obtaining 's' DSA signature component: %s",
proxy_ssh_crypto_get_errors());
BN_clear_free((BIGNUM *) sig_r);
DSA_free(dsa);
DSA_SIG_free(dsa_sig);
return -1;
}
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
pctx = EVP_MD_CTX_new();
#else
pctx = &ctx;
#endif /* prior to OpenSSL-1.1.0 */
EVP_DigestInit(pctx, EVP_sha1());
EVP_DigestUpdate(pctx, sig_data, sig_datalen);
EVP_DigestFinal(pctx, digest, &digest_len);
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
EVP_MD_CTX_free(pctx);
#endif /* OpenSSL-1.1.0 and later */
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
# if OPENSSL_VERSION_NUMBER >= 0x10100006L
DSA_SIG_set0(dsa_sig, (BIGNUM *) sig_r, (BIGNUM *) sig_s);
# else
/* XXX What to do here? */
# endif /* prior to OpenSSL-1.1.0-pre6 */
#else
dsa_sig->r = sig_r;
dsa_sig->s = sig_s;
#endif /* prior to OpenSSL-1.1.0 */
ok = DSA_do_verify(digest, digest_len, dsa_sig, dsa);
if (ok == 1) {
res = 0;
} else {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error verifying DSA signature: %s", proxy_ssh_crypto_get_errors());
errno = EINVAL;
res = -1;
}
pr_memscrub(digest, digest_len);
DSA_free(dsa);
DSA_SIG_free(dsa_sig);
return res;
}
#endif /* !OPENSSL_NO_DSA */
#if defined(PR_USE_OPENSSL_ECC)
static int ecdsa_verify_signed_data(pool *p, EVP_PKEY *pkey,
unsigned char *signature, uint32_t signature_len,
unsigned char *sig_data, size_t sig_datalen, char *sig_type) {
#if OPENSSL_VERSION_NUMBER < 0x10100000L || \
defined(HAVE_LIBRESSL)
EVP_MD_CTX ctx;
#endif /* prior to OpenSSL-1.1.0 */
EVP_MD_CTX *pctx;
const EVP_MD *md = NULL;
EC_KEY *ec;
ECDSA_SIG *ecdsa_sig;
const BIGNUM *sig_r, *sig_s;
uint32_t len, sig_len;
unsigned char digest[EVP_MAX_MD_SIZE], *sig;
unsigned int digest_len = 0;
int ok = FALSE, res = 0;
len = proxy_ssh_msg_read_int(p, &signature, &signature_len, &sig_len);
if (len == 0) {
errno = EINVAL;
return -1;
}
len = proxy_ssh_msg_read_data(p, &signature, &signature_len, sig_len, &sig);
if (len == 0) {
errno = EINVAL;
return -1;
}
if (sig == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error verifying ECDSA signature: missing signature data");
errno = EINVAL;
return -1;
}
ecdsa_sig = ECDSA_SIG_new();
if (ecdsa_sig == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error allocating new ECDSA_SIG: %s", proxy_ssh_crypto_get_errors());
return -1;
}
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
ECDSA_SIG_get0(ecdsa_sig, &sig_r, &sig_s);
#else
sig_r = ecdsa_sig->r;
sig_s = ecdsa_sig->s;
#endif /* prior to OpenSSL-1.1.0 */
len = proxy_ssh_msg_read_mpint(p, &sig, &sig_len, &sig_r);
if (len == 0) {
ECDSA_SIG_free(ecdsa_sig);
errno = EINVAL;
return -1;
}
if (sig_r == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error reading 'r' ECDSA signature component: %s",
proxy_ssh_crypto_get_errors());
ECDSA_SIG_free(ecdsa_sig);
return -1;
}
len = proxy_ssh_msg_read_mpint(p, &sig, &sig_len, &sig_s);
if (len == 0) {
ECDSA_SIG_free(ecdsa_sig);
errno = EINVAL;
return -1;
}
if (sig_s == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error reading 's' ECDSA signature component: %s",
proxy_ssh_crypto_get_errors());
ECDSA_SIG_free(ecdsa_sig);
return -1;
}
/* Skip past the common leading prefix "ecdsa-sha2-" to compare just
* last 9 characters.
*/
if (strcmp(sig_type + 11, "nistp256") == 0) {
md = EVP_sha256();
} else if (strcmp(sig_type + 11, "nistp384") == 0) {
md = EVP_sha384();
} else if (strcmp(sig_type + 11, "nistp521") == 0) {
md = EVP_sha512();
}
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
pctx = EVP_MD_CTX_new();
#else
pctx = &ctx;
#endif /* prior to OpenSSL-1.1.0 */
EVP_DigestInit(pctx, md);
EVP_DigestUpdate(pctx, sig_data, sig_datalen);
EVP_DigestFinal(pctx, digest, &digest_len);
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
EVP_MD_CTX_free(pctx);
#endif /* OpenSSL-1.1.0 and later */
ec = EVP_PKEY_get1_EC_KEY(pkey);
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
# if OPENSSL_VERSION_NUMBER >= 0x10100006L
ECDSA_SIG_set0(ecdsa_sig, (BIGNUM *) sig_r, (BIGNUM *) sig_s);
# else
/* XXX What to do here? */
# endif /* prior to OpenSSL-1.1.0-pre6 */
#else
ecdsa_sig->r = sig_r;
ecdsa_sig->s = sig_s;
#endif /* prior to OpenSSL-1.1.0 */
ok = ECDSA_do_verify(digest, digest_len, ecdsa_sig, ec);
if (ok == 1) {
res = 0;
} else {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error verifying ECDSA signature: %s", proxy_ssh_crypto_get_errors());
errno = EINVAL;
res = -1;
}
pr_memscrub(digest, digest_len);
EC_KEY_free(ec);
ECDSA_SIG_free(ecdsa_sig);
return res;
}
#endif /* PR_USE_OPENSSL_ECC */
#if defined(PR_USE_SODIUM)
static int ed25519_verify_signed_data(pool *p,
unsigned char *pubkey_data, uint32_t pubkey_datalen,
unsigned char *signature, uint32_t signature_len,
unsigned char *sig_data, size_t sig_datalen) {
char *pkey_type;
uint32_t len, public_keylen, sig_len;
unsigned char *msg, *public_key, *signed_msg, *sig;
unsigned long long msg_len, signed_msglen;
int res;
len = proxy_ssh_msg_read_string(p, &pubkey_data, &pubkey_datalen, &pkey_type);
if (len == 0) {
errno = EINVAL;
return -1;
}
if (strcmp(pkey_type, "ssh-ed25519") != 0) {
pr_trace_msg(trace_channel, 17,
"public key type '%s' does not match expected key type 'ssh-ed25519'",
pkey_type);
errno = EINVAL;
return -1;
}
len = proxy_ssh_msg_read_int(p, &pubkey_data, &pubkey_datalen,
&public_keylen);
if (len == 0) {
errno = EINVAL;
return -1;
}
if (public_keylen != crypto_sign_ed25519_PUBLICKEYBYTES) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"invalid Ed25519 public key length (%lu bytes), expected %lu bytes",
(unsigned long) public_keylen,
(unsigned long) crypto_sign_ed25519_PUBLICKEYBYTES);
errno = EINVAL;
return -1;
}
len = proxy_ssh_msg_read_data(p, &pubkey_data, &pubkey_datalen, public_keylen,
&public_key);
if (len == 0) {
errno = EINVAL;
return -1;
}
len = proxy_ssh_msg_read_int(p, &signature, &signature_len, &sig_len);
if (len == 0) {
errno = EINVAL;
return -1;
}
len = proxy_ssh_msg_read_data(p, &signature, &signature_len, sig_len, &sig);
if (len == 0) {
errno = EINVAL;
return -1;
}
if (sig == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error verifying Ed25519 signature: missing signature data");
errno = EINVAL;
return -1;
}
if (sig_len > crypto_sign_ed25519_BYTES) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"Ed25519 signature length (%lu bytes) exceeds valid length (%lu bytes)",
(unsigned long) sig_len, (unsigned long) crypto_sign_ed25519_BYTES);
errno = EINVAL;
return -1;
}
signed_msglen = sig_len + sig_datalen;
signed_msg = palloc(p, signed_msglen);
memcpy(signed_msg, sig, sig_len);
memcpy(signed_msg + sig_len, sig_data, sig_datalen);
msg_len = signed_msglen;
msg = palloc(p, msg_len);
res = crypto_sign_ed25519_open(msg, &msg_len, signed_msg, signed_msglen,
public_key);
if (res != 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"failed Ed25519 signature verification (%d)", res);
res = -1;
}
if (res == 0) {
if (msg_len != sig_datalen) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"invalid Ed25519 signature length (%lu bytes), expected %lu bytes",
(unsigned long) sig_datalen, (unsigned long) msg_len);
errno = EINVAL;
res = -1;
}
}
if (res == 0) {
if (sodium_memcmp(msg, sig_data, msg_len) != 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"invalid Ed25519 signature (mismatched data)");
errno = EINVAL;
res = -1;
}
}
pr_memscrub(signed_msg, signed_msglen);
pr_memscrub(msg, msg_len);
return res;
}
#endif /* PR_USE_SODIUM */
int proxy_ssh_keys_verify_signed_data(pool *p, const char *pubkey_algo,
unsigned char *pubkey_data, uint32_t pubkey_datalen,
unsigned char *signature, uint32_t signature_len,
unsigned char *sig_data, size_t sig_datalen) {
EVP_PKEY *pkey = NULL;
char *sig_type;
uint32_t len;
int res = 0;
if (pubkey_algo == NULL ||
pubkey_data == NULL ||
signature == NULL ||
sig_data == NULL ||
sig_datalen == 0) {
errno = EINVAL;
return -1;
}
len = read_pkey_from_data(p, pubkey_data, pubkey_datalen, &pkey, NULL, FALSE);
if (len == 0) {
return -1;
}
if (strcmp(pubkey_algo, "ssh-dss") == 0) {
if (proxy_ssh_interop_supports_feature(PROXY_SSH_FEAT_HAVE_PUBKEY_ALGO_IN_DSA_SIG)) {
len = proxy_ssh_msg_read_string(p, &signature, &signature_len, &sig_type);
if (len == 0) {
errno = EINVAL;
return -1;
}
} else {
/* The server did not prepend the public key algorithm name to their
* signature data, so there is no need to extract that string.
* We will ASSUME that the public key algorithm provided elsewhere
* in the 'publickey' USERAUTH_REQUEST is accurate.
*/
pr_trace_msg(trace_channel, 9, "assuming server did not prepend public "
"key algorithm name to DSA signature");
sig_type = "ssh-dss";
}
} else {
len = proxy_ssh_msg_read_string(p, &signature, &signature_len, &sig_type);
if (len == 0) {
errno = EINVAL;
return -1;
}
}
if (strcmp(sig_type, "ssh-rsa") == 0) {
res = rsa_verify_signed_data(p, pkey, signature, signature_len, sig_data,
sig_datalen);
#if defined(HAVE_SHA256_OPENSSL)
} else if (strcmp(sig_type, "rsa-sha2-256") == 0) {
res = rsa_sha256_verify_signed_data(p, pkey, signature, signature_len,
sig_data, sig_datalen);
#endif /* HAVE_SHA256_OPENSSL */
#if defined(HAVE_SHA512_OPENSSL)
} else if (strcmp(sig_type, "rsa-sha2-512") == 0) {
res = rsa_sha512_verify_signed_data(p, pkey, signature, signature_len,
sig_data, sig_datalen);
#endif /* HAVE_SHA512_OPENSSL */
#if !defined(OPENSSL_NO_DSA)
} else if (strcmp(sig_type, "ssh-dss") == 0) {
res = dsa_verify_signed_data(p, pkey, signature, signature_len, sig_data,
sig_datalen);
#endif /* !OPENSSL_NO_DSA */
#ifdef PR_USE_OPENSSL_ECC
} else if (strcmp(sig_type, "ecdsa-sha2-nistp256") == 0 ||
strcmp(sig_type, "ecdsa-sha2-nistp384") == 0 ||
strcmp(sig_type, "ecdsa-sha2-nistp521") == 0) {
if (strcmp(pubkey_algo, sig_type) != 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to verify signed data: public key algorithm '%s' does not "
"match signature algorithm '%s'", pubkey_algo, sig_type);
return -1;
}
res = ecdsa_verify_signed_data(p, pkey, signature, signature_len, sig_data,
sig_datalen, sig_type);
#endif /* PR_USE_OPENSSL_ECC */
#if defined(PR_USE_SODIUM)
} else if (strcmp(sig_type, "ssh-ed25519") == 0) {
res = ed25519_verify_signed_data(p, pubkey_data, pubkey_datalen, signature,
signature_len, sig_data, sig_datalen);
#endif /* PR_USE_SODIUM */
} else {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to verify signed data: unsupported signature algorithm '%s'",
sig_type);
errno = EINVAL;
return -1;
}
if (pkey != NULL) {
EVP_PKEY_free(pkey);
}
return res;
}
int proxy_ssh_keys_set_passphrase_provider(const char *provider) {
if (provider == NULL) {
errno = EINVAL;
return -1;
}
passphrase_provider = provider;
return 0;
}
void proxy_ssh_keys_get_passphrases(void) {
server_rec *s = NULL;
for (s = (server_rec *) server_list->xas_list; s; s = s->next) {
config_rec *c;
struct proxy_ssh_pkey *k;
c = find_config(s->conf, CONF_PARAM, "ProxySFTPHostKey", FALSE);
while (c != NULL) {
int flags;
pr_signals_handle();
flags = *((int *) c->argv[1]);
/* Skip any agent-provided ProxySFTPHostKey directives, as well as any
* "disabling key" directives.
*/
if (flags != 0 ||
strncmp(c->argv[0], "agent:", 6) == 0) {
c = find_config_next(c, c->next, CONF_PARAM, "ProxySFTPHostKey",
FALSE);
continue;
}
k = pcalloc(s->pool, sizeof(struct proxy_ssh_pkey));
k->pkeysz = PEM_BUFSIZE-1;
k->server = s;
if (get_passphrase(k, c->argv[0]) < 0) {
int xerrno = errno;
const char *errstr;
errstr = proxy_ssh_crypto_get_errors();
pr_log_pri(PR_LOG_WARNING, MOD_PROXY_VERSION
": error reading passphrase for ProxySFTPHostKey '%s': %s",
(const char *) c->argv[0], errstr ? errstr : strerror(xerrno));
pr_log_pri(PR_LOG_ERR, MOD_PROXY_VERSION
": unable to use key in ProxySFTPHostKey '%s', exiting",
(const char *) c->argv[0]);
pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BAD_CONFIG,
NULL);
}
k->next = pkey_list;
pkey_list = k;
npkeys++;
c = find_config_next(c, c->next, CONF_PARAM, "ProxySFTPHostKey", FALSE);
}
}
}
static int clear_dsa_hostkey(void) {
if (dsa_hostkey == NULL) {
errno = ENOENT;
return -1;
}
if (dsa_hostkey->pkey != NULL) {
EVP_PKEY_free(dsa_hostkey->pkey);
}
dsa_hostkey = NULL;
return 0;
}
static int clear_ecdsa_hostkey(void) {
#if defined(PR_USE_OPENSSL_ECC)
int count = 0;
if (ecdsa256_hostkey != NULL) {
if (ecdsa256_hostkey->pkey != NULL) {
EVP_PKEY_free(ecdsa256_hostkey->pkey);
}
ecdsa256_hostkey = NULL;
count++;
}
if (ecdsa384_hostkey != NULL) {
if (ecdsa384_hostkey->pkey != NULL) {
EVP_PKEY_free(ecdsa384_hostkey->pkey);
}
ecdsa384_hostkey = NULL;
count++;
}
if (ecdsa521_hostkey != NULL) {
if (ecdsa521_hostkey->pkey != NULL) {
EVP_PKEY_free(ecdsa521_hostkey->pkey);
}
ecdsa521_hostkey = NULL;
count++;
}
if (count > 0) {
return 0;
}
#endif /* PR_USE_OPENSSL_ECC */
errno = ENOENT;
return -1;
}
static int clear_ed25519_hostkey(void) {
#if defined(PR_USE_SODIUM)
if (ed25519_hostkey == NULL) {
errno = ENOENT;
return -1;
}
if (ed25519_hostkey->ed25519_secret_key != NULL) {
pr_memscrub(ed25519_hostkey->ed25519_secret_key,
ed25519_hostkey->ed25519_secret_keylen);
ed25519_hostkey->ed25519_secret_key = NULL;
ed25519_hostkey->ed25519_secret_keylen = 0;
}
if (ed25519_hostkey->ed25519_public_key != NULL) {
pr_memscrub(ed25519_hostkey->ed25519_public_key,
ed25519_hostkey->ed25519_public_keylen);
ed25519_hostkey->ed25519_public_key = NULL;
ed25519_hostkey->ed25519_public_keylen = 0;
}
ed25519_hostkey = NULL;
#endif /* PR_USE_SODIUM */
return 0;
}
static int clear_rsa_hostkey(void) {
if (rsa_hostkey == NULL) {
errno = ENOENT;
return -1;
}
if (rsa_hostkey->pkey != NULL) {
EVP_PKEY_free(rsa_hostkey->pkey);
}
rsa_hostkey = NULL;
return 0;
}
/* Make sure that no valuable information can be inadvertently written
* out to swap.
*/
void proxy_ssh_keys_free(void) {
scrub_pkeys();
clear_dsa_hostkey();
clear_ecdsa_hostkey();
clear_ed25519_hostkey();
clear_rsa_hostkey();
}
#endif /* PR_USE_OPENSSL */
proftpd-mod_proxy-0.9.5/lib/proxy/ssh/mac.c 0000664 0000000 0000000 00000076657 14757370167 0020717 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH MACs
* Copyright (c) 2021-2022 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "proxy/ssh/ssh2.h"
#include "proxy/ssh/msg.h"
#include "proxy/ssh/packet.h"
#include "proxy/ssh/crypto.h"
#include "proxy/ssh/cipher.h"
#include "proxy/ssh/mac.h"
#include "proxy/ssh/umac.h"
#include "proxy/ssh/session.h"
#include "proxy/ssh/disconnect.h"
#include "proxy/ssh/interop.h"
#if defined(PR_USE_OPENSSL)
#include
#include
struct proxy_ssh_mac {
pool *pool;
const char *algo;
int algo_type;
int is_etm;
const EVP_MD *digest;
unsigned char *key;
/* The keysz and key_len are usually the same; they can differ if, for
* example, the client always truncates the MAC key len to 16 bits.
*/
size_t keysz;
uint32_t key_len;
uint32_t mac_len;
};
#define PROXY_SSH_MAC_ALGO_TYPE_HMAC 1
#define PROXY_SSH_MAC_ALGO_TYPE_UMAC64 2
#define PROXY_SSH_MAC_ALGO_TYPE_UMAC128 3
#define PROXY_SSH_MAC_FL_READ_MAC 1
#define PROXY_SSH_MAC_FL_WRITE_MAC 2
/* We need to keep the old MACs around, so that we can handle N arbitrary
* packets to/from the client using the old keys, as during rekeying.
* Thus we have two read MAC contexts, two write MAC contexts.
* The cipher idx variable indicates which of the MACs is currently in use.
*/
static struct proxy_ssh_mac read_macs[] = {
{ NULL, NULL, 0, FALSE, NULL, NULL, 0, 0, 0 },
{ NULL, NULL, 0, FALSE, NULL, NULL, 0, 0, 0 }
};
static HMAC_CTX *hmac_read_ctxs[2];
static struct umac_ctx *umac_read_ctxs[2];
static struct proxy_ssh_mac write_macs[] = {
{ NULL, NULL, 0, FALSE, NULL, NULL, 0, 0, 0 },
{ NULL, NULL, 0, FALSE, NULL, NULL, 0, 0, 0 }
};
static HMAC_CTX *hmac_write_ctxs[2];
static struct umac_ctx *umac_write_ctxs[2];
static size_t mac_blockszs[2] = { 0, 0 };
static unsigned int read_mac_idx = 0;
static unsigned int write_mac_idx = 0;
static void clear_mac(struct proxy_ssh_mac *);
static unsigned int get_next_read_index(void) {
if (read_mac_idx == 1) {
return 0;
}
return 1;
}
static unsigned int get_next_write_index(void) {
if (write_mac_idx == 1) {
return 0;
}
return 1;
}
static void switch_read_mac(void) {
/* First we can clear the read MAC, kept from rekeying. */
if (read_macs[read_mac_idx].key) {
clear_mac(&(read_macs[read_mac_idx]));
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
HMAC_CTX_reset(hmac_read_ctxs[read_mac_idx]);
#elif OPENSSL_VERSION_NUMBER > 0x000907000L
HMAC_CTX_cleanup(hmac_read_ctxs[read_mac_idx]);
#else
HMAC_cleanup(hmac_read_ctxs[read_mac_idx]);
#endif
if (read_macs[read_mac_idx].algo_type == PROXY_SSH_MAC_ALGO_TYPE_UMAC64) {
proxy_ssh_umac_reset(umac_read_ctxs[read_mac_idx]);
} else if (read_macs[read_mac_idx].algo_type == PROXY_SSH_MAC_ALGO_TYPE_UMAC128) {
proxy_ssh_umac128_reset(umac_read_ctxs[read_mac_idx]);
}
mac_blockszs[read_mac_idx] = 0;
/* Now we can switch the index. */
if (read_mac_idx == 1) {
read_mac_idx = 0;
return;
}
read_mac_idx = 1;
}
}
static void switch_write_mac(void) {
/* First we can clear the write MAC, kept from rekeying. */
if (write_macs[write_mac_idx].key) {
clear_mac(&(write_macs[write_mac_idx]));
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
HMAC_CTX_reset(hmac_write_ctxs[write_mac_idx]);
#elif OPENSSL_VERSION_NUMBER > 0x000907000L
HMAC_CTX_cleanup(hmac_write_ctxs[write_mac_idx]);
#else
HMAC_cleanup(hmac_write_ctxs[write_mac_idx]);
#endif
if (write_macs[write_mac_idx].algo_type == PROXY_SSH_MAC_ALGO_TYPE_UMAC64) {
proxy_ssh_umac_reset(umac_write_ctxs[write_mac_idx]);
} else if (write_macs[write_mac_idx].algo_type == PROXY_SSH_MAC_ALGO_TYPE_UMAC128) {
proxy_ssh_umac128_reset(umac_write_ctxs[write_mac_idx]);
}
/* Now we can switch the index. */
if (write_mac_idx == 1) {
write_mac_idx = 0;
return;
}
write_mac_idx = 1;
}
}
static void clear_mac(struct proxy_ssh_mac *mac) {
if (mac->key != NULL) {
pr_memscrub(mac->key, mac->keysz);
free(mac->key);
mac->key = NULL;
mac->keysz = 0;
mac->key_len = 0;
}
mac->digest = NULL;
mac->algo = NULL;
}
static int init_mac(pool *p, struct proxy_ssh_mac *mac, HMAC_CTX *hmac_ctx,
struct umac_ctx *umac_ctx) {
/* Currently unused. */
(void) p;
if (strcmp(mac->algo, "none") == 0) {
return 0;
}
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
HMAC_CTX_reset(hmac_ctx);
#elif OPENSSL_VERSION_NUMBER > 0x000907000L
HMAC_CTX_init(hmac_ctx);
#else
/* Reset the HMAC context. */
HMAC_Init(hmac_ctx, NULL, 0, NULL);
#endif
if (mac->algo_type == PROXY_SSH_MAC_ALGO_TYPE_HMAC) {
#if OPENSSL_VERSION_NUMBER > 0x000907000L
# if OPENSSL_VERSION_NUMBER >= 0x10000001L
if (HMAC_Init_ex(hmac_ctx, mac->key, mac->key_len, mac->digest,
NULL) != 1) {
pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error initializing HMAC: %s", proxy_ssh_crypto_get_errors());
errno = EPERM;
return -1;
}
# else
HMAC_Init_ex(hmac_ctx, mac->key, mac->key_len, mac->digest, NULL);
# endif /* OpenSSL-1.0.0 and later */
#else
HMAC_Init(hmac_ctx, mac->key, mac->key_len, mac->digest);
#endif
} else if (mac->algo_type == PROXY_SSH_MAC_ALGO_TYPE_UMAC64) {
proxy_ssh_umac_reset(umac_ctx);
proxy_ssh_umac_init(umac_ctx, mac->key);
} else if (mac->algo_type == PROXY_SSH_MAC_ALGO_TYPE_UMAC128) {
proxy_ssh_umac128_reset(umac_ctx);
proxy_ssh_umac128_init(umac_ctx, mac->key);
}
return 0;
}
static int get_mac(struct proxy_ssh_packet *pkt, struct proxy_ssh_mac *mac,
HMAC_CTX *hmac_ctx, struct umac_ctx *umac_ctx, int etm_mac, int flags) {
unsigned char *mac_data = NULL;
unsigned char *buf, *ptr;
uint32_t buflen, bufsz = 0, mac_len = 0, len = 0;
if (mac->algo_type == PROXY_SSH_MAC_ALGO_TYPE_HMAC) {
/* Always leave a little extra room in the buffer. */
bufsz = (sizeof(uint32_t) * 2) + pkt->packet_len + 64;
mac_data = pcalloc(pkt->pool, EVP_MAX_MD_SIZE);
if (etm_mac == TRUE) {
bufsz += proxy_ssh_mac_get_block_size();
}
buflen = bufsz;
ptr = buf = palloc(pkt->pool, bufsz);
len += proxy_ssh_msg_write_int(&buf, &buflen, pkt->seqno);
len += proxy_ssh_msg_write_int(&buf, &buflen, pkt->packet_len);
if (etm_mac == FALSE) {
/* For Encrypt-Then-Mac modes, padding and its length will be part of
* the encrypted payload.
*/
len += proxy_ssh_msg_write_byte(&buf, &buflen, pkt->padding_len);
}
len += proxy_ssh_msg_write_data(&buf, &buflen, pkt->payload,
pkt->payload_len, FALSE);
if (etm_mac == FALSE) {
len += proxy_ssh_msg_write_data(&buf, &buflen, pkt->padding,
pkt->padding_len, FALSE);
}
#if OPENSSL_VERSION_NUMBER > 0x000907000L
# if OPENSSL_VERSION_NUMBER >= 0x10000001L
if (HMAC_Init_ex(hmac_ctx, NULL, 0, NULL, NULL) != 1) {
pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error resetting HMAC context: %s", proxy_ssh_crypto_get_errors());
errno = EPERM;
return -1;
}
# else
HMAC_Init_ex(hmac_ctx, NULL, 0, NULL, NULL);
# endif /* OpenSSL-1.0.0 and later */
#else
HMAC_Init(hmac_ctx, NULL, 0, NULL);
#endif /* OpenSSL-0.9.7 and later */
#if OPENSSL_VERSION_NUMBER >= 0x10000001L
if (HMAC_Update(hmac_ctx, ptr, len) != 1) {
pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error adding %lu bytes of data to HMAC context: %s",
(unsigned long) len, proxy_ssh_crypto_get_errors());
errno = EPERM;
return -1;
}
if (HMAC_Final(hmac_ctx, mac_data, &mac_len) != 1) {
pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error finalizing HMAC context: %s", proxy_ssh_crypto_get_errors());
errno = EPERM;
return -1;
}
#else
HMAC_Update(hmac_ctx, ptr, len);
HMAC_Final(hmac_ctx, mac_data, &mac_len);
#endif /* OpenSSL-1.0.0 and later */
} else if (mac->algo_type == PROXY_SSH_MAC_ALGO_TYPE_UMAC64 ||
mac->algo_type == PROXY_SSH_MAC_ALGO_TYPE_UMAC128) {
unsigned char nonce[8], *nonce_ptr;
uint32_t nonce_len = 0;
/* Always leave a little extra room in the buffer. */
bufsz = sizeof(uint32_t) + pkt->packet_len + 64;
mac_data = pcalloc(pkt->pool, EVP_MAX_MD_SIZE);
if (etm_mac == TRUE) {
bufsz += proxy_ssh_mac_get_block_size();
}
buflen = bufsz;
ptr = buf = palloc(pkt->pool, bufsz);
len += proxy_ssh_msg_write_int(&buf, &buflen, pkt->packet_len);
if (etm_mac == FALSE) {
/* For Encrypt-Then-Mac modes, padding and its length will be part of
* the encrypted payload.
*/
len += proxy_ssh_msg_write_byte(&buf, &buflen, pkt->padding_len);
}
len += proxy_ssh_msg_write_data(&buf, &buflen, pkt->payload,
pkt->payload_len, FALSE);
if (etm_mac == FALSE) {
len += proxy_ssh_msg_write_data(&buf, &buflen, pkt->padding,
pkt->padding_len, FALSE);
}
nonce_ptr = nonce;
nonce_len = sizeof(nonce);
(void) proxy_ssh_msg_write_long(&nonce_ptr, &nonce_len, pkt->seqno);
if (mac->algo_type == PROXY_SSH_MAC_ALGO_TYPE_UMAC64) {
proxy_ssh_umac_reset(umac_ctx);
proxy_ssh_umac_update(umac_ctx, ptr, len);
proxy_ssh_umac_final(umac_ctx, mac_data, nonce);
mac_len = 8;
} else if (mac->algo_type == PROXY_SSH_MAC_ALGO_TYPE_UMAC128) {
proxy_ssh_umac128_reset(umac_ctx);
proxy_ssh_umac128_update(umac_ctx, ptr, len);
proxy_ssh_umac128_final(umac_ctx, mac_data, nonce);
mac_len = 16;
}
}
if (mac_len == 0) {
pkt->mac = NULL;
pkt->mac_len = 0;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error computing MAC using %s: %s", mac->algo,
proxy_ssh_crypto_get_errors());
errno = EIO;
return -1;
}
if (mac->mac_len != 0) {
mac_len = mac->mac_len;
}
if (flags & PROXY_SSH_MAC_FL_READ_MAC) {
if (memcmp(mac_data, pkt->mac, mac_len) != 0) {
unsigned int i = 0;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"MAC from server differs from expected MAC using %s", mac->algo);
#ifdef SFTP_DEBUG_PACKET
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"client MAC (len %lu):", (unsigned long) pkt->mac_len);
for (i = 0; i < mac_len;) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
" %02x%02x %02x%02x %02x%02x %02x%02x",
((unsigned char *) pkt->mac)[i], ((unsigned char *) pkt->mac)[i+1],
((unsigned char *) pkt->mac)[i+2], ((unsigned char *) pkt->mac)[i+3],
((unsigned char *) pkt->mac)[i+4], ((unsigned char *) pkt->mac)[i+5],
((unsigned char *) pkt->mac)[i+6], ((unsigned char *) pkt->mac)[i+7]);
i += 8;
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"client MAC (len %lu):", (unsigned long) mac_len);
for (i = 0; i < mac_len;) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
" %02x%02x %02x%02x %02x%02x %02x%02x",
((unsigned char *) mac)[i], ((unsigned char *) mac)[i+1],
((unsigned char *) mac)[i+2], ((unsigned char *) mac)[i+3],
((unsigned char *) mac)[i+4], ((unsigned char *) mac)[i+5],
((unsigned char *) mac)[i+6], ((unsigned char *) mac)[i+7]);
i += 8;
}
#else
/* Avoid compiler warning. */
(void) i;
#endif
errno = EINVAL;
return -1;
}
} else if (flags & PROXY_SSH_MAC_FL_WRITE_MAC) {
/* Debugging. */
#ifdef SFTP_DEBUG_PACKET
if (pkt->mac_len > 0) {
unsigned int i = 0;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"client MAC (len %lu, seqno %lu):",
(unsigned long) pkt->mac_len, (unsigned long) pkt->seqno);
for (i = 0; i < mac_len;) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
" %02x%02x %02x%02x %02x%02x %02x%02x",
((unsigned char *) pkt->mac)[i], ((unsigned char *) pkt->mac)[i+1],
((unsigned char *) pkt->mac)[i+2], ((unsigned char *) pkt->mac)[i+3],
((unsigned char *) pkt->mac)[i+4], ((unsigned char *) pkt->mac)[i+5],
((unsigned char *) pkt->mac)[i+6], ((unsigned char *) pkt->mac)[i+7]);
i += 8;
}
}
#endif
}
pkt->mac_len = mac_len;
pkt->mac = pcalloc(pkt->pool, pkt->mac_len);
memcpy(pkt->mac, mac_data, mac_len);
return 0;
}
static int set_mac_key(struct proxy_ssh_mac *mac, const EVP_MD *md,
const unsigned char *k, uint32_t klen, const char *h,
uint32_t hlen, char *letter, const unsigned char *id, uint32_t id_len) {
#if OPENSSL_VERSION_NUMBER < 0x10100000L || \
defined(HAVE_LIBRESSL)
EVP_MD_CTX ctx;
#endif /* prior to OpenSSL-1.1.0 */
EVP_MD_CTX *pctx;
unsigned char *key = NULL;
size_t key_sz;
uint32_t key_len = 0;
key_sz = proxy_ssh_crypto_get_size(EVP_MD_block_size(mac->digest),
EVP_MD_size(md));
if (key_sz == 0) {
if (strcmp(mac->algo, "none") == 0) {
return 0;
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to determine key length for MAC '%s'", mac->algo);
errno = EINVAL;
return -1;
}
key = malloc(key_sz);
if (key == NULL) {
pr_log_pri(PR_LOG_ALERT, MOD_PROXY_VERSION ": Out of memory!");
_exit(1);
}
#if OPENSSL_VERSION_NUMBER < 0x10100000L || \
defined(HAVE_LIBRESSL)
pctx = &ctx;
#else
pctx = EVP_MD_CTX_new();
#endif /* prior to OpenSSL-1.1.0 */
/* In OpenSSL 0.9.6, many of the EVP_Digest* functions returned void, not
* int. Without these ugly OpenSSL version preprocessor checks, the
* compiler will error out with "void value not ignored as it ought to be".
*/
#if OPENSSL_VERSION_NUMBER >= 0x000907000L
if (EVP_DigestInit(pctx, md) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error initializing message digest: %s", proxy_ssh_crypto_get_errors());
free(key);
# if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
EVP_MD_CTX_free(pctx);
# endif /* OpenSSL-1.1.0 and later */
return -1;
}
#else
EVP_DigestInit(pctx, md);
#endif
#if OPENSSL_VERSION_NUMBER >= 0x000907000L
if (EVP_DigestUpdate(pctx, k, klen) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error updating message digest with K: %s",
proxy_ssh_crypto_get_errors());
free(key);
# if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
EVP_MD_CTX_free(pctx);
# endif /* OpenSSL-1.1.0 and later */
return -1;
}
#else
EVP_DigestUpdate(pctx, k, klen);
#endif
#if OPENSSL_VERSION_NUMBER >= 0x000907000L
if (EVP_DigestUpdate(pctx, h, hlen) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error updating message digest with H: %s",
proxy_ssh_crypto_get_errors());
free(key);
# if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
EVP_MD_CTX_free(pctx);
# endif /* OpenSSL-1.1.0 and later */
return -1;
}
#else
EVP_DigestUpdate(pctx, h, hlen);
#endif
#if OPENSSL_VERSION_NUMBER >= 0x000907000L
if (EVP_DigestUpdate(pctx, letter, sizeof(char)) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error updating message digest with '%c': %s", *letter,
proxy_ssh_crypto_get_errors());
free(key);
# if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
EVP_MD_CTX_free(pctx);
# endif /* OpenSSL-1.1.0 and later */
return -1;
}
#else
EVP_DigestUpdate(pctx, letter, sizeof(char));
#endif
#if OPENSSL_VERSION_NUMBER >= 0x000907000L
if (EVP_DigestUpdate(pctx, (char *) id, id_len) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error updating message digest with ID: %s",
proxy_ssh_crypto_get_errors());
free(key);
# if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
EVP_MD_CTX_free(pctx);
# endif /* OpenSSL-1.1.0 and later */
return -1;
}
#else
EVP_DigestUpdate(pctx, (char *) id, id_len);
#endif
#if OPENSSL_VERSION_NUMBER >= 0x000907000L
if (EVP_DigestFinal(pctx, key, &key_len) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error finalizing message digest: %s", proxy_ssh_crypto_get_errors());
pr_memscrub(key, key_sz);
free(key);
# if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
EVP_MD_CTX_free(pctx);
# endif /* OpenSSL-1.1.0 and later */
return -1;
}
#else
EVP_DigestFinal(pctx, key, &key_len);
#endif
/* If we need more, keep hashing, as per RFC, until we have enough
* material.
*/
while (key_sz > key_len) {
uint32_t len = key_len;
pr_signals_handle();
#if OPENSSL_VERSION_NUMBER >= 0x000907000L
if (EVP_DigestInit(pctx, md) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error initializing message digest: %s", proxy_ssh_crypto_get_errors());
pr_memscrub(key, key_sz);
free(key);
# if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
EVP_MD_CTX_free(pctx);
# endif /* OpenSSL-1.1.0 and later */
return -1;
}
#else
EVP_DigestInit(pctx, md);
#endif
#if OPENSSL_VERSION_NUMBER >= 0x000907000L
if (EVP_DigestUpdate(pctx, k, klen) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error updating message digest with K: %s",
proxy_ssh_crypto_get_errors());
pr_memscrub(key, key_sz);
free(key);
# if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
EVP_MD_CTX_free(pctx);
# endif /* OpenSSL-1.1.0 and later */
return -1;
}
#else
EVP_DigestUpdate(pctx, k, klen);
#endif
#if OPENSSL_VERSION_NUMBER >= 0x000907000L
if (EVP_DigestUpdate(pctx, h, hlen) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error updating message digest with H: %s",
proxy_ssh_crypto_get_errors());
pr_memscrub(key, key_sz);
free(key);
# if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
EVP_MD_CTX_free(pctx);
# endif /* OpenSSL-1.1.0 and later */
return -1;
}
#else
EVP_DigestUpdate(pctx, h, hlen);
#endif
#if OPENSSL_VERSION_NUMBER >= 0x000907000L
if (EVP_DigestUpdate(pctx, key, len) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error updating message digest with data: %s",
proxy_ssh_crypto_get_errors());
pr_memscrub(key, key_sz);
free(key);
# if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
EVP_MD_CTX_free(pctx);
# endif /* OpenSSL-1.1.0 and later */
return -1;
}
#else
EVP_DigestUpdate(pctx, key, len);
#endif
#if OPENSSL_VERSION_NUMBER >= 0x000907000L
if (EVP_DigestFinal(pctx, key + len, &len) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error finalizing message digest: %s", proxy_ssh_crypto_get_errors());
pr_memscrub(key, key_sz);
free(key);
# if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
EVP_MD_CTX_free(pctx);
# endif /* OpenSSL-1.1.0 and later */
return -1;
}
#else
EVP_DigestFinal(pctx, key + len, &len);
#endif
key_len += len;
}
mac->key = key;
mac->keysz = key_sz;
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
EVP_MD_CTX_free(pctx);
#endif /* OpenSSL-1.1.0 and later */
if (mac->algo_type == PROXY_SSH_MAC_ALGO_TYPE_HMAC) {
mac->key_len = EVP_MD_size(mac->digest);
} else if (mac->algo_type == PROXY_SSH_MAC_ALGO_TYPE_UMAC64 ||
mac->algo_type == PROXY_SSH_MAC_ALGO_TYPE_UMAC128) {
mac->key_len = EVP_MD_block_size(mac->digest);
}
if (!proxy_ssh_interop_supports_feature(PROXY_SSH_FEAT_MAC_LEN)) {
mac->key_len = 16;
}
return 0;
}
size_t proxy_ssh_mac_get_block_size(void) {
return mac_blockszs[read_mac_idx];
}
void proxy_ssh_mac_set_block_size(size_t blocksz) {
if (blocksz > mac_blockszs[read_mac_idx]) {
mac_blockszs[read_mac_idx] = blocksz;
}
}
const char *proxy_ssh_mac_get_read_algo(void) {
if (read_macs[read_mac_idx].key != NULL ||
strcmp(read_macs[read_mac_idx].algo, "none") == 0) {
return read_macs[read_mac_idx].algo;
}
/* It is possible for there to be no MAC, as for some ciphers such as
* AES-GCM. Rather than returning NULL here, we indicate this by returning
* a string (see Issue #1411).
*/
return "implicit";
}
int proxy_ssh_mac_is_read_etm(void) {
if (read_macs[read_mac_idx].key != NULL) {
return read_macs[read_mac_idx].is_etm;
}
return FALSE;
}
int proxy_ssh_mac_set_read_algo(pool *p, const char *algo) {
const char *etm_suffix;
size_t algo_len, etm_len;
uint32_t mac_len;
unsigned int idx = read_mac_idx;
/* For authenticated encryption ciphers, there is no separate MAC. */
if (proxy_ssh_cipher_get_read_auth_size() > 0) {
return 0;
}
if (read_macs[idx].key != NULL) {
/* If we have an existing key, it means that we are currently rekeying. */
idx = get_next_read_index();
}
/* Clear any potential UMAC contexts at this index. */
if (umac_read_ctxs[idx] != NULL) {
switch (read_macs[idx].algo_type) {
case PROXY_SSH_MAC_ALGO_TYPE_UMAC64:
proxy_ssh_umac_delete(umac_read_ctxs[idx]);
umac_read_ctxs[idx] = NULL;
break;
case PROXY_SSH_MAC_ALGO_TYPE_UMAC128:
proxy_ssh_umac128_delete(umac_read_ctxs[idx]);
umac_read_ctxs[idx] = NULL;
break;
}
}
read_macs[idx].digest = proxy_ssh_crypto_get_digest(algo, &mac_len);
if (read_macs[idx].digest == NULL) {
return -1;
}
/* Note that we use a new pool, each time the algorithm is set (which
* happens during key exchange) to prevent undue memory growth for
* long-lived sessions with many rekeys.
*/
if (read_macs[idx].pool != NULL) {
destroy_pool(read_macs[idx].pool);
}
read_macs[idx].pool = make_sub_pool(p);
pr_pool_tag(read_macs[idx].pool, "Proxy SFTP MAC read pool");
read_macs[idx].algo = pstrdup(read_macs[idx].pool, algo);
if (strcmp(read_macs[idx].algo, "umac-64@openssh.com") == 0 ||
strcmp(read_macs[idx].algo, "umac-64-etm@openssh.com") == 0) {
read_macs[idx].algo_type = PROXY_SSH_MAC_ALGO_TYPE_UMAC64;
umac_read_ctxs[idx] = proxy_ssh_umac_alloc();
} else if (strcmp(read_macs[idx].algo, "umac-128@openssh.com") == 0 ||
strcmp(read_macs[idx].algo, "umac-128-etm@openssh.com") == 0) {
read_macs[idx].algo_type = PROXY_SSH_MAC_ALGO_TYPE_UMAC128;
umac_read_ctxs[idx] = proxy_ssh_umac128_alloc();
} else {
read_macs[idx].algo_type = PROXY_SSH_MAC_ALGO_TYPE_HMAC;
}
read_macs[idx].mac_len = mac_len;
algo_len = strlen(algo);
etm_suffix = "-etm@openssh.com";
etm_len = strlen(etm_suffix);
if (pr_strnrstr(algo, algo_len, etm_suffix, etm_len, 0) == TRUE) {
read_macs[idx].is_etm = TRUE;
}
return 0;
}
int proxy_ssh_mac_set_read_key(pool *p, const EVP_MD *md,
const unsigned char *k, uint32_t klen, const char *h, uint32_t hlen,
int role) {
const unsigned char *id = NULL;
uint32_t id_len;
char letter;
size_t blocksz;
struct proxy_ssh_mac *mac;
HMAC_CTX *hmac_ctx;
struct umac_ctx *umac_ctx;
/* For authenticated encryption ciphers, there is no separate MAC. */
if (proxy_ssh_cipher_get_read_auth_size() > 0) {
return 0;
}
switch_read_mac();
mac = &(read_macs[read_mac_idx]);
hmac_ctx = hmac_read_ctxs[read_mac_idx];
umac_ctx = umac_read_ctxs[read_mac_idx];
id_len = proxy_ssh_session_get_id(&id);
/* The letters used depend on the role; see:
* https://tools.ietf.org/html/rfc4253#section-7.2
*
* If we are the CLIENT, then we use the letters for the "server to client"
* flows, since we are READING from the server.
*/
/* client-to-server HASH(K || H || "E" || session_id)
* server-to-client HASH(K || H || "F" || session_id)
*/
letter = (role == PROXY_SSH_ROLE_CLIENT ? 'F' : 'E');
set_mac_key(mac, md, k, klen, h, hlen, &letter, id, id_len);
if (init_mac(p, mac, hmac_ctx, umac_ctx) < 0) {
return -1;
}
if (mac->mac_len == 0) {
blocksz = EVP_MD_size(mac->digest);
} else {
blocksz = mac->mac_len;
}
proxy_ssh_mac_set_block_size(blocksz);
return 0;
}
int proxy_ssh_mac_read_data(struct proxy_ssh_packet *pkt) {
struct proxy_ssh_mac *mac;
HMAC_CTX *hmac_ctx;
struct umac_ctx *umac_ctx;
int res, etm_mac = FALSE;
/* For authenticated encryption ciphers, there is no separate MAC. */
if (proxy_ssh_cipher_get_read_auth_size() > 0) {
return 0;
}
etm_mac = proxy_ssh_mac_is_read_etm();
mac = &(read_macs[read_mac_idx]);
hmac_ctx = hmac_read_ctxs[read_mac_idx];
umac_ctx = umac_read_ctxs[read_mac_idx];
if (mac->key == NULL) {
pkt->mac = NULL;
pkt->mac_len = 0;
return 0;
}
res = get_mac(pkt, mac, hmac_ctx, umac_ctx, etm_mac,
PROXY_SSH_MAC_FL_READ_MAC);
if (res < 0) {
return -1;
}
return 0;
}
const char *proxy_ssh_mac_get_write_algo(void) {
if (write_macs[write_mac_idx].key != NULL) {
return write_macs[write_mac_idx].algo;
}
/* It is possible for there to be no MAC, as for some ciphers such as
* AES-GCM. Rather than returning NULL here, we indicate this by returning
* a string (see Issue #1411).
*/
return "implicit";
}
int proxy_ssh_mac_is_write_etm(void) {
if (write_macs[write_mac_idx].key != NULL) {
return write_macs[write_mac_idx].is_etm;
}
return FALSE;
}
int proxy_ssh_mac_set_write_algo(pool *p, const char *algo) {
const char *etm_suffix;
size_t algo_len, etm_len;
uint32_t mac_len;
unsigned int idx = write_mac_idx;
/* For authenticated encryption ciphers, there is no separate MAC. */
if (proxy_ssh_cipher_get_write_auth_size() > 0) {
return 0;
}
if (write_macs[idx].key != NULL) {
/* If we have an existing key, it means that we are currently rekeying. */
idx = get_next_write_index();
}
/* Clear any potential UMAC contexts at this index. */
if (umac_write_ctxs[idx] != NULL) {
switch (write_macs[idx].algo_type) {
case PROXY_SSH_MAC_ALGO_TYPE_UMAC64:
proxy_ssh_umac_delete(umac_write_ctxs[idx]);
umac_write_ctxs[idx] = NULL;
break;
case PROXY_SSH_MAC_ALGO_TYPE_UMAC128:
proxy_ssh_umac128_delete(umac_write_ctxs[idx]);
umac_write_ctxs[idx] = NULL;
break;
}
}
write_macs[idx].digest = proxy_ssh_crypto_get_digest(algo, &mac_len);
if (write_macs[idx].digest == NULL) {
return -1;
}
/* Note that we use a new pool, each time the algorithm is set (which
* happens during key exchange) to prevent undue memory growth for
* long-lived sessions with many rekeys.
*/
if (write_macs[idx].pool != NULL) {
destroy_pool(write_macs[idx].pool);
}
write_macs[idx].pool = make_sub_pool(p);
pr_pool_tag(write_macs[idx].pool, "Proxy SFTP MAC write pool");
write_macs[idx].algo = pstrdup(write_macs[idx].pool, algo);
if (strcmp(write_macs[idx].algo, "umac-64@openssh.com") == 0 ||
strcmp(write_macs[idx].algo, "umac-64-etm@openssh.com") == 0) {
write_macs[idx].algo_type = PROXY_SSH_MAC_ALGO_TYPE_UMAC64;
umac_write_ctxs[idx] = proxy_ssh_umac_alloc();
} else if (strcmp(write_macs[idx].algo, "umac-128@openssh.com") == 0 ||
strcmp(write_macs[idx].algo, "umac-128-etm@openssh.com") == 0) {
write_macs[idx].algo_type = PROXY_SSH_MAC_ALGO_TYPE_UMAC128;
umac_write_ctxs[idx] = proxy_ssh_umac128_alloc();
} else {
write_macs[idx].algo_type = PROXY_SSH_MAC_ALGO_TYPE_HMAC;
}
write_macs[idx].mac_len = mac_len;
algo_len = strlen(algo);
etm_suffix = "-etm@openssh.com";
etm_len = strlen(etm_suffix);
if (pr_strnrstr(algo, algo_len, etm_suffix, etm_len, 0) == TRUE) {
write_macs[idx].is_etm = TRUE;
}
return 0;
}
int proxy_ssh_mac_set_write_key(pool *p, const EVP_MD *md,
const unsigned char *k, uint32_t klen, const char *h, uint32_t hlen,
int role) {
const unsigned char *id = NULL;
uint32_t id_len;
char letter;
struct proxy_ssh_mac *mac;
HMAC_CTX *hmac_ctx;
struct umac_ctx *umac_ctx;
/* For authenticated encryption ciphers, there is no separate MAC. */
if (proxy_ssh_cipher_get_write_auth_size() > 0) {
return 0;
}
switch_write_mac();
mac = &(write_macs[write_mac_idx]);
hmac_ctx = hmac_write_ctxs[write_mac_idx];
umac_ctx = umac_write_ctxs[write_mac_idx];
id_len = proxy_ssh_session_get_id(&id);
/* The letters used depend on the role; see:
* https://tools.ietf.org/html/rfc4253#section-7.2
*
* If we are the CLIENT, then we use the letters for the "client to server"
* flows, since we are WRITING to the server.
*/
/* client-to-server HASH(K || H || "E" || session_id)
* server-to-client HASH(K || H || "F" || session_id)
*/
letter = (role == PROXY_SSH_ROLE_CLIENT ? 'E' : 'F');
set_mac_key(mac, md, k, klen, h, hlen, &letter, id, id_len);
if (init_mac(p, mac, hmac_ctx, umac_ctx) < 0) {
return -1;
}
return 0;
}
int proxy_ssh_mac_write_data(struct proxy_ssh_packet *pkt) {
struct proxy_ssh_mac *mac;
HMAC_CTX *hmac_ctx;
struct umac_ctx *umac_ctx;
int res, etm_mac = FALSE;
/* For authenticated encryption ciphers, there is no separate MAC. */
if (proxy_ssh_cipher_get_write_auth_size() > 0) {
return 0;
}
etm_mac = proxy_ssh_mac_is_write_etm();
mac = &(write_macs[write_mac_idx]);
hmac_ctx = hmac_write_ctxs[write_mac_idx];
umac_ctx = umac_write_ctxs[write_mac_idx];
if (mac->key == NULL) {
pkt->mac = NULL;
pkt->mac_len = 0;
return 0;
}
res = get_mac(pkt, mac, hmac_ctx, umac_ctx, etm_mac,
PROXY_SSH_MAC_FL_WRITE_MAC);
if (res < 0) {
return -1;
}
return 0;
}
#if OPENSSL_VERSION_NUMBER < 0x10100000L || \
defined(HAVE_LIBRESSL)
/* In older versions of OpenSSL, there was not a way to dynamically allocate
* an HMAC_CTX object. Thus we have these static objects for those
* older versions.
*/
static HMAC_CTX read_ctx1, read_ctx2;
static HMAC_CTX write_ctx1, write_ctx2;
#endif /* prior to OpenSSL-1.1.0 */
int proxy_ssh_mac_init(void) {
#if OPENSSL_VERSION_NUMBER < 0x10100000L || \
defined(HAVE_LIBRESSL)
hmac_read_ctxs[0] = &read_ctx1;
hmac_read_ctxs[1] = &read_ctx2;
hmac_write_ctxs[0] = &write_ctx1;
hmac_write_ctxs[1] = &write_ctx2;
#else
hmac_read_ctxs[0] = HMAC_CTX_new();
hmac_read_ctxs[1] = HMAC_CTX_new();
hmac_write_ctxs[0] = HMAC_CTX_new();
hmac_write_ctxs[1] = HMAC_CTX_new();
#endif /* OpenSSL-1.1.0 and later */
umac_read_ctxs[0] = NULL;
umac_read_ctxs[1] = NULL;
umac_write_ctxs[0] = NULL;
umac_write_ctxs[1] = NULL;
return 0;
}
int proxy_ssh_mac_free(void) {
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
HMAC_CTX_free(hmac_read_ctxs[0]);
HMAC_CTX_free(hmac_read_ctxs[1]);
HMAC_CTX_free(hmac_write_ctxs[0]);
HMAC_CTX_free(hmac_write_ctxs[1]);
#endif /* OpenSSL-1.1.0 and later */
return 0;
}
#endif /* PR_USE_OPENSSL */
proftpd-mod_proxy-0.9.5/lib/proxy/ssh/misc.c 0000664 0000000 0000000 00000005075 14757370167 0021074 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH miscellany
* Copyright (c) 2021 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "proxy/ssh/misc.h"
#if defined(PR_USE_OPENSSL)
int proxy_ssh_misc_namelist_contains(pool *p, const char *namelist,
const char *name) {
register unsigned int i;
int res = FALSE;
pool *tmp_pool;
array_header *list;
const char **elts;
tmp_pool = make_sub_pool(p);
pr_pool_tag(tmp_pool, "Contains name pool");
list = pr_str_text_to_array(tmp_pool, namelist, ',');
elts = (const char **) list->elts;
for (i = 0; i < list->nelts; i++) {
if (strcmp(elts[i], name) == 0) {
res = TRUE;
break;
}
}
destroy_pool(tmp_pool);
return res;
}
const char *proxy_ssh_misc_namelist_shared(pool *p, const char *c2s_names,
const char *s2c_names) {
register unsigned int i;
const char *name = NULL, **client_names, **server_names;
pool *tmp_pool;
array_header *client_list, *server_list;
tmp_pool = make_sub_pool(p);
pr_pool_tag(tmp_pool, "Share name pool");
client_list = pr_str_text_to_array(tmp_pool, c2s_names, ',');
client_names = (const char **) client_list->elts;
server_list = pr_str_text_to_array(tmp_pool, s2c_names, ',');
server_names = (const char **) server_list->elts;
for (i = 0; i < client_list->nelts; i++) {
register unsigned int j;
if (name != NULL) {
break;
}
for (j = 0; j < server_list->nelts; j++) {
if (strcmp(client_names[i], server_names[j]) == 0) {
name = client_names[i];
break;
}
}
}
name = pstrdup(p, name);
destroy_pool(tmp_pool);
return name;
}
#endif /* PR_USE_OPENSSL */
proftpd-mod_proxy-0.9.5/lib/proxy/ssh/msg.c 0000664 0000000 0000000 00000041170 14757370167 0020723 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH message format
* Copyright (c) 2021-2024 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "proxy/session.h"
#include "proxy/ssh/ssh2.h"
#include "proxy/ssh/msg.h"
#include "proxy/ssh/crypto.h"
#include "proxy/ssh/disconnect.h"
#if defined(PR_USE_OPENSSL)
#if defined(PR_USE_OPENSSL_ECC)
/* Max GFp field length = 528 bits. SEC1 uncompressed encoding uses 2
* bitstring points. SEC1 specifies a 1 byte point type header.
*/
# define MAX_ECPOINT_LEN ((528*2 / 8) + 1)
#endif /* PR_USE_OPENSSL_ECC */
static const char *trace_channel = "proxy.ssh.msg";
static conn_t *get_backend_conn(void) {
const struct proxy_session *proxy_sess;
proxy_sess = pr_table_get(session.notes, "mod_proxy.proxy-session", NULL);
if (proxy_sess == NULL) {
return NULL;
}
return proxy_sess->backend_ctrl_conn;
}
uint32_t proxy_ssh_msg_read_byte(pool *p, unsigned char **buf, uint32_t *buflen,
unsigned char *byte) {
(void) p;
if (*buflen < sizeof(char)) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"message format error: unable to read byte (buflen = %lu)",
(unsigned long) *buflen);
return 0;
}
memcpy(byte, *buf, sizeof(unsigned char));
(*buf) += sizeof(unsigned char);
(*buflen) -= sizeof(unsigned char);
return sizeof(unsigned char);
}
uint32_t proxy_ssh_msg_read_bool(pool *p, unsigned char **buf, uint32_t *buflen,
int *b) {
unsigned char byte = 0;
uint32_t len;
(void) p;
len = proxy_ssh_msg_read_byte(p, buf, buflen, &byte);
if (len == 0) {
return 0;
}
*b = byte;
return len;
}
uint32_t proxy_ssh_msg_read_data(pool *p, unsigned char **buf,
uint32_t *buflen, size_t datalen, unsigned char **data) {
if (datalen == 0) {
return 0;
}
if (*buflen < datalen) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"message format error: unable to read %lu bytes of raw data "
"(buflen = %lu)", (unsigned long) datalen, (unsigned long) *buflen);
return 0;
}
*data = palloc(p, datalen);
memcpy(*data, *buf, datalen);
(*buf) += datalen;
(*buflen) -= datalen;
return datalen;
}
uint32_t proxy_ssh_msg_read_int(pool *p, unsigned char **buf, uint32_t *buflen,
uint32_t *val) {
(void) p;
if (*buflen < sizeof(uint32_t)) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"message format error: unable to read int (buflen = %lu)",
(unsigned long) *buflen);
return 0;
}
memcpy(val, *buf, sizeof(uint32_t));
(*buf) += sizeof(uint32_t);
(*buflen) -= sizeof(uint32_t);
*val = ntohl(*val);
return sizeof(uint32_t);
}
uint32_t proxy_ssh_msg_read_long(pool *p, unsigned char **buf, uint32_t *buflen,
uint64_t *val) {
unsigned char data[8];
if (*buflen < sizeof(data)) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"message format error: unable to read long (buflen = %lu)",
(unsigned long) *buflen);
return 0;
}
memcpy(data, *buf, sizeof(data));
(*buf) += sizeof(data);
(*buflen) -= sizeof(data);
(*val) = (uint64_t) data[0] << 56;
(*val) |= (uint64_t) data[1] << 48;
(*val) |= (uint64_t) data[2] << 40;
(*val) |= (uint64_t) data[3] << 32;
(*val) |= (uint64_t) data[4] << 24;
(*val) |= (uint64_t) data[5] << 16;
(*val) |= (uint64_t) data[6] << 8;
(*val) |= (uint64_t) data[7];
return sizeof(data);
}
uint32_t proxy_ssh_msg_read_mpint(pool *p, unsigned char **buf,
uint32_t *buflen, const BIGNUM **mpint) {
unsigned char *mpint_data = NULL;
const unsigned char *data = NULL, *ptr = NULL;
uint32_t datalen = 0, mpint_len = 0, len = 0, total_len = 0;
len = proxy_ssh_msg_read_int(p, buf, buflen, &mpint_len);
if (len == 0) {
return 0;
}
if (*buflen < mpint_len) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"message format error: unable to read %lu bytes of mpint (buflen = %lu)",
(unsigned long) len, (unsigned long) *buflen);
return 0;
}
if (len > (1024 * 16)) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"message format error: unable to handle mpint of %lu bytes",
(unsigned long) len);
return 0;
}
total_len += len;
len = proxy_ssh_msg_read_data(p, buf, buflen, mpint_len, &mpint_data);
if (len == 0) {
return 0;
}
total_len += len;
ptr = (const unsigned char *) mpint_data;
if ((ptr[0] & 0x80) != 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"message format error: negative mpint numbers not supported");
return 0;
}
/* Trim any leading zeros. */
data = ptr;
datalen = mpint_len;
while (datalen > 0 &&
*data == 0x00) {
pr_signals_handle();
data++;
datalen--;
}
*mpint = BN_bin2bn(data, (int) datalen, NULL);
if (*mpint == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"message format error: unable to convert binary mpint: %s",
proxy_ssh_crypto_get_errors());
return 0;
}
return total_len;
}
uint32_t proxy_ssh_msg_read_string(pool *p, unsigned char **buf,
uint32_t *buflen, char **text) {
uint32_t data_len = 0, len = 0;
/* If there is no data remaining, treat this as if the string is empty
* (see Bug#4093).
*/
if (*buflen == 0) {
pr_trace_msg(trace_channel, 9,
"malformed message format (buflen = %lu) for reading text, using \"\"",
(unsigned long) *buflen);
*text = pstrdup(p, "");
return 1;
}
len = proxy_ssh_msg_read_int(p, buf, buflen, &data_len);
if (len == 0) {
return 0;
}
/* We can't use proxy_ssh_msg_read_data() here, since we need to allocate and
* populate a buffer that is one byte longer than the len just read in,
* for the terminating NUL.
*/
if (*buflen < data_len) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"message format error: unable to read %lu bytes of string data "
"(buflen = %lu)", (unsigned long) data_len, (unsigned long) *buflen);
return 0;
}
*text = palloc(p, data_len + 1);
if (data_len > 0) {
memcpy(*text, *buf, data_len);
(*buf) += data_len;
(*buflen) -= data_len;
}
(*text)[data_len] = '\0';
return len + data_len;
}
#if defined(PR_USE_OPENSSL) && defined(PR_USE_OPENSSL_ECC)
uint32_t proxy_ssh_msg_read_ecpoint(pool *p, unsigned char **buf,
uint32_t *buflen, const EC_GROUP *curve, EC_POINT **point) {
BN_CTX *bn_ctx;
unsigned char *data = NULL;
uint32_t datalen = 0, len = 0, total_len = 0;
len = proxy_ssh_msg_read_int(p, buf, buflen, &datalen);
if (len == 0) {
return 0;
}
if (*buflen < datalen) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"message format error: unable to read %lu bytes of EC point"
" (buflen = %lu)", (unsigned long) datalen, (unsigned long) *buflen);
return 0;
}
if (datalen > MAX_ECPOINT_LEN) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"message format error: EC point length too long (%lu > max %lu)",
(unsigned long) datalen, (unsigned long) MAX_ECPOINT_LEN);
return 0;
}
total_len += len;
len = proxy_ssh_msg_read_data(p, buf, buflen, datalen, &data);
if (len == 0) {
return 0;
}
if (data == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"message format error: unable to read %lu bytes of EC point data",
(unsigned long) datalen);
return 0;
}
total_len += len;
if (data[0] != POINT_CONVERSION_UNCOMPRESSED) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"message format error: EC point data formatted incorrectly "
"(leading byte 0x%02x should be 0x%02x)", data[0],
POINT_CONVERSION_UNCOMPRESSED);
return 0;
}
bn_ctx = BN_CTX_new();
if (bn_ctx == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error allocating new BN_CTX: %s", proxy_ssh_crypto_get_errors());
return 0;
}
if (EC_POINT_oct2point(curve, *point, data, datalen, bn_ctx) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"message format error: unable to convert binary EC point data: %s",
proxy_ssh_crypto_get_errors());
BN_CTX_free(bn_ctx);
return 0;
}
BN_CTX_free(bn_ctx);
pr_memscrub(data, datalen);
return total_len;
}
#endif /* PR_USE_OPENSSL_ECC */
uint32_t proxy_ssh_msg_write_byte(unsigned char **buf, uint32_t *buflen,
unsigned char byte) {
uint32_t len = 0;
if (*buflen < sizeof(char)) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"message format error: unable to write byte (buflen = %lu)",
(unsigned long) *buflen);
pr_log_stacktrace(proxy_logfd, MOD_PROXY_VERSION);
PROXY_SSH_DISCONNECT_CONN(get_backend_conn(),
PROXY_SSH_DISCONNECT_BY_APPLICATION, NULL);
}
len = sizeof(unsigned char);
memcpy(*buf, &byte, len);
(*buf) += len;
(*buflen) -= len;
return len;
}
uint32_t proxy_ssh_msg_write_bool(unsigned char **buf, uint32_t *buflen,
unsigned char b) {
return proxy_ssh_msg_write_byte(buf, buflen, b == 0 ? 0 : 1);
}
uint32_t proxy_ssh_msg_write_data(unsigned char **buf, uint32_t *buflen,
const unsigned char *data, size_t datalen, int write_len) {
uint32_t len = 0;
if (write_len) {
len += proxy_ssh_msg_write_int(buf, buflen, datalen);
}
if (*buflen < datalen) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"message format error: unable to write %lu bytes of raw data "
"(buflen = %lu)", (unsigned long) datalen, (unsigned long) *buflen);
pr_log_stacktrace(proxy_logfd, MOD_PROXY_VERSION);
PROXY_SSH_DISCONNECT_CONN(get_backend_conn(),
PROXY_SSH_DISCONNECT_BY_APPLICATION, NULL);
}
if (datalen > 0) {
memcpy(*buf, data, datalen);
(*buf) += datalen;
(*buflen) -= datalen;
len += datalen;
}
return len;
}
uint32_t proxy_ssh_msg_write_int(unsigned char **buf, uint32_t *buflen,
uint32_t val) {
uint32_t len;
if (*buflen < sizeof(uint32_t)) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"message format error: unable to write int (buflen = %lu)",
(unsigned long) *buflen);
pr_log_stacktrace(proxy_logfd, MOD_PROXY_VERSION);
PROXY_SSH_DISCONNECT_CONN(get_backend_conn(),
PROXY_SSH_DISCONNECT_BY_APPLICATION, NULL);
}
len = sizeof(uint32_t);
val = htonl(val);
memcpy(*buf, &val, len);
(*buf) += len;
(*buflen) -= len;
return len;
}
uint32_t proxy_ssh_msg_write_long(unsigned char **buf, uint32_t *buflen,
uint64_t val) {
unsigned char data[8];
if (*buflen < sizeof(uint64_t)) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"message format error: unable to write long (buflen = %lu)",
(unsigned long) *buflen);
pr_log_stacktrace(proxy_logfd, MOD_PROXY_VERSION);
PROXY_SSH_DISCONNECT_CONN(get_backend_conn(),
PROXY_SSH_DISCONNECT_BY_APPLICATION, NULL);
}
data[0] = (unsigned char) (val >> 56) & 0xFF;
data[1] = (unsigned char) (val >> 48) & 0xFF;
data[2] = (unsigned char) (val >> 40) & 0xFF;
data[3] = (unsigned char) (val >> 32) & 0xFF;
data[4] = (unsigned char) (val >> 24) & 0xFF;
data[5] = (unsigned char) (val >> 16) & 0xFF;
data[6] = (unsigned char) (val >> 8) & 0xFF;
data[7] = (unsigned char) val & 0xFF;
return proxy_ssh_msg_write_data(buf, buflen, data, sizeof(data), FALSE);
}
uint32_t proxy_ssh_msg_write_mpint(unsigned char **buf, uint32_t *buflen,
const BIGNUM *mpint) {
unsigned char *data = NULL;
size_t datalen = 0;
int res = 0;
uint32_t len = 0;
if (BN_is_zero(mpint)) {
return proxy_ssh_msg_write_int(buf, buflen, 0);
}
#if OPENSSL_VERSION_NUMBER >= 0x0090801fL
if (BN_is_negative(mpint)) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"message format error: unable to write mpint (negative numbers not "
"supported)");
pr_log_stacktrace(proxy_logfd, MOD_PROXY_VERSION);
PROXY_SSH_DISCONNECT_CONN(get_backend_conn(),
PROXY_SSH_DISCONNECT_BY_APPLICATION, NULL);
}
#endif /* OpenSSL-0.9.8a or later */
datalen = BN_num_bytes(mpint) + 1;
if (*buflen < datalen) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"message format error: unable to write %lu bytes of mpint (buflen = %lu)",
(unsigned long) datalen, (unsigned long) *buflen);
pr_log_stacktrace(proxy_logfd, MOD_PROXY_VERSION);
PROXY_SSH_DISCONNECT_CONN(get_backend_conn(),
PROXY_SSH_DISCONNECT_BY_APPLICATION, NULL);
}
data = malloc(datalen);
if (data == NULL) {
pr_log_pri(PR_LOG_ALERT, MOD_PROXY_VERSION ": Out of memory!");
_exit(1);
}
data[0] = 0;
res = BN_bn2bin(mpint, data + 1);
if (res < 0 ||
res != (int) (datalen - 1)) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"message format error: BN_bn2bin() failed: expected %lu bytes, got %d",
(unsigned long) (datalen - 1), res);
pr_memscrub(data, datalen);
free(data);
PROXY_SSH_DISCONNECT_CONN(get_backend_conn(),
PROXY_SSH_DISCONNECT_BY_APPLICATION, NULL);
/* Needed to avoid compiler (and static code analysis) complaints. */
return 0;
}
if (data[1] & 0x80) {
len += proxy_ssh_msg_write_data(buf, buflen, data, datalen, TRUE);
} else {
len += proxy_ssh_msg_write_data(buf, buflen, data + 1, datalen - 1,
TRUE);
}
pr_memscrub(data, datalen);
free(data);
return len;
}
uint32_t proxy_ssh_msg_write_string(unsigned char **buf, uint32_t *buflen,
const char *text) {
uint32_t text_len = 0;
text_len = strlen(text);
return proxy_ssh_msg_write_data(buf, buflen, (const unsigned char *) text,
text_len, TRUE);
}
#if defined(PR_USE_OPENSSL) && defined(PR_USE_OPENSSL_ECC)
uint32_t proxy_ssh_msg_write_ecpoint(unsigned char **buf, uint32_t *buflen,
const EC_GROUP *curve, const EC_POINT *point) {
unsigned char *data = NULL;
size_t datalen = 0;
uint32_t len = 0;
BN_CTX *bn_ctx;
bn_ctx = BN_CTX_new();
if (bn_ctx == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error allocating new BN_CTX: %s", proxy_ssh_crypto_get_errors());
pr_log_stacktrace(proxy_logfd, MOD_PROXY_VERSION);
PROXY_SSH_DISCONNECT_CONN(get_backend_conn(),
PROXY_SSH_DISCONNECT_BY_APPLICATION, NULL);
}
datalen = EC_POINT_point2oct(curve, point, POINT_CONVERSION_UNCOMPRESSED,
NULL, 0, bn_ctx);
if (datalen > MAX_ECPOINT_LEN) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"message format error: EC point length too long (%lu > max %lu)",
(unsigned long) datalen, (unsigned long) MAX_ECPOINT_LEN);
pr_log_stacktrace(proxy_logfd, MOD_PROXY_VERSION);
PROXY_SSH_DISCONNECT_CONN(get_backend_conn(),
PROXY_SSH_DISCONNECT_BY_APPLICATION, NULL);
}
if (*buflen < datalen) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"message format error: unable to write %lu bytes of EC point "
"(buflen = %lu)", (unsigned long) datalen, (unsigned long) *buflen);
pr_log_stacktrace(proxy_logfd, MOD_PROXY_VERSION);
PROXY_SSH_DISCONNECT_CONN(get_backend_conn(),
PROXY_SSH_DISCONNECT_BY_APPLICATION, NULL);
}
data = malloc(datalen);
if (data == NULL) {
pr_log_pri(PR_LOG_ALERT, MOD_PROXY_VERSION ": Out of memory!");
_exit(1);
}
if (EC_POINT_point2oct(curve, point, POINT_CONVERSION_UNCOMPRESSED, data,
datalen, bn_ctx) != datalen) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error writing EC point data: Length mismatch");
pr_memscrub(data, datalen);
free(data);
BN_CTX_free(bn_ctx);
PROXY_SSH_DISCONNECT_CONN(get_backend_conn(),
PROXY_SSH_DISCONNECT_BY_APPLICATION, NULL);
/* Needed to avoid compiler (and static code analysis) complaints. */
return 0;
}
len = proxy_ssh_msg_write_data(buf, buflen, (const unsigned char *) data,
datalen, TRUE);
pr_memscrub(data, datalen);
free(data);
BN_CTX_free(bn_ctx);
return len;
}
#endif /* PR_USE_OPENSSL_ECC */
#endif /* PR_USE_OPENSSL */
proftpd-mod_proxy-0.9.5/lib/proxy/ssh/packet.c 0000664 0000000 0000000 00000205204 14757370167 0021404 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH packet IO
* Copyright (c) 2021-2023 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "proxy/ssh/ssh2.h"
#include "proxy/ssh/packet.h"
#include "proxy/ssh/msg.h"
#include "proxy/ssh/disconnect.h"
#include "proxy/ssh/cipher.h"
#include "proxy/ssh/mac.h"
#include "proxy/ssh/compress.h"
#include "proxy/ssh/kex.h"
#include "proxy/ssh/auth.h"
#include "proxy/ssh/service.h"
#if HAVE_SYS_UIO_H
# include
#endif
#ifndef MAX
# define MAX(x, y) (((x) > (y)) ? (x) : (y))
# define MIN(x, y) (((x) < (y)) ? (x) : (y))
#endif
#if defined(PR_USE_OPENSSL)
extern pr_response_t *resp_list, *resp_err_list;
static int (*frontend_packet_write)(int, void *) = NULL;
static uint32_t packet_client_seqno = 0;
static uint32_t packet_server_seqno = 0;
/* Maximum length of the payload data of an SSH2 packet we're willing to
* accept. Any packets reporting a payload length longer than this will be
* ignored/dropped.
*/
#define PROXY_SSH_PACKET_MAX_PAYLOAD_LEN (256 * 1024)
static int poll_timeout_secs = -1;
static unsigned long poll_timeout_ms = 0;
static const char *client_version = PROXY_SSH_ID_DEFAULT_STRING;
static const char *version_id = PROXY_SSH_ID_DEFAULT_STRING "\r\n";
static int sent_version_id = FALSE;
static void is_server_alive(conn_t *conn);
/* Count of the number of "server alive" messages sent without a response. */
static unsigned int server_alive_max = 0, server_alive_count = 0;
static unsigned int server_alive_interval = 0;
static const char *trace_channel = "proxy.ssh.packet";
static const char *timing_channel = "timing";
#define DEFAULT_POLL_ATTEMPTS 3
static unsigned long poll_attempts = DEFAULT_POLL_ATTEMPTS;
int proxy_ssh_packet_conn_mpoll(conn_t *frontend_conn, conn_t *backend_conn,
int io) {
fd_set rfds, wfds;
struct timeval tv;
int res, frontend_sockfd = -1, backend_sockfd = -1, maxfd = -1, timeout_sec,
using_server_alive = FALSE;
unsigned int ntimeouts = 0;
unsigned long timeout_usec = 0;
if (poll_timeout_secs == -1) {
/* If we have "server alive" timeout interval configured, use that --
* but only if we have already done the key exchange, and are not
* rekeying.
*
* Otherwise, we use the default (i.e. TimeoutIdle).
*/
if (server_alive_interval > 0 &&
(!(proxy_sess_state & PROXY_SESS_STATE_SSH_REKEYING) &&
(proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_AUTH))) {
timeout_sec = server_alive_interval;
using_server_alive = TRUE;
} else {
timeout_sec = pr_data_get_timeout(PR_DATA_TIMEOUT_IDLE);
}
timeout_usec = 0;
} else {
timeout_sec = poll_timeout_secs;
timeout_usec = (poll_timeout_ms * 1000);
}
tv.tv_sec = timeout_sec;
tv.tv_usec = timeout_usec;
if (io == PROXY_SSH_PACKET_IO_READ) {
if (frontend_conn != NULL) {
frontend_sockfd = frontend_conn->rfd;
}
if (backend_conn != NULL) {
backend_sockfd = backend_conn->rfd;
}
} else {
if (frontend_conn != NULL) {
frontend_sockfd = frontend_conn->wfd;
}
if (backend_conn != NULL) {
backend_sockfd = backend_conn->wfd;
}
}
pr_trace_msg(trace_channel, 19,
"waiting for max of %lu secs %lu ms while polling sockets %d/%d for %s "
"using select(2)", (unsigned long) tv.tv_sec,
(unsigned long) (tv.tv_usec / 1000), frontend_sockfd, backend_sockfd,
io == PROXY_SSH_PACKET_IO_READ ? "reading" : "writing");
while (TRUE) {
pr_signals_handle();
FD_ZERO(&rfds);
FD_ZERO(&wfds);
switch (io) {
case PROXY_SSH_PACKET_IO_READ: {
if (frontend_conn != NULL) {
FD_SET(frontend_sockfd, &rfds);
if (frontend_sockfd > maxfd) {
maxfd = frontend_sockfd;
}
}
if (backend_conn != NULL) {
FD_SET(backend_sockfd, &rfds);
if (backend_sockfd > maxfd) {
maxfd = backend_sockfd;
}
}
res = select(maxfd + 1, &rfds, NULL, NULL, &tv);
break;
}
case PROXY_SSH_PACKET_IO_WRITE: {
if (frontend_conn != NULL) {
FD_SET(frontend_sockfd, &wfds);
if (frontend_sockfd > maxfd) {
maxfd = frontend_sockfd;
}
}
if (backend_conn != NULL) {
FD_SET(backend_sockfd, &wfds);
if (backend_sockfd > maxfd) {
maxfd = backend_sockfd;
}
}
res = select(maxfd + 1, NULL, &wfds, NULL, &tv);
break;
}
default:
errno = EINVAL;
return -1;
}
if (res < 0) {
int xerrno = errno;
if (xerrno == EINTR) {
pr_signals_handle();
continue;
}
pr_trace_msg(trace_channel, 18, "error calling select(2) on fd %d/%d: %s",
frontend_sockfd, backend_sockfd, strerror(xerrno));
errno = xerrno;
return -1;
} else if (res == 0) {
tv.tv_sec = timeout_sec;
tv.tv_usec = timeout_usec;
ntimeouts++;
if (ntimeouts > poll_attempts) {
pr_trace_msg(trace_channel, 18,
"polling on socket %d/%d timed out after %lu sec %lu ms "
"(%u attempts)", frontend_sockfd, backend_sockfd,
(unsigned long) tv.tv_sec, (unsigned long) (tv.tv_usec / 1000),
ntimeouts);
errno = ETIMEDOUT;
return -1;
}
if (using_server_alive == TRUE) {
if (backend_conn != NULL) {
is_server_alive(backend_conn);
}
} else {
pr_trace_msg(trace_channel, 18,
"polling on socket %d/%d timed out after %lu sec %lu ms, "
"trying again (timeout #%u)", frontend_sockfd, backend_sockfd,
(unsigned long) tv.tv_sec, (unsigned long) (tv.tv_usec / 1000),
ntimeouts);
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"polling on socket %d/%d timed out after %lu sec %lu ms, "
"trying again (timeout #%u)", frontend_sockfd, backend_sockfd,
(unsigned long) tv.tv_sec, (unsigned long) (tv.tv_usec / 1000),
ntimeouts);
}
continue;
}
break;
}
/* Which connection has data? Return 0 if it's the frontend connection,
* otherwise return 1 for the backend connection.
*/
if (frontend_conn != NULL) {
if (io == PROXY_SSH_PACKET_IO_READ) {
if (FD_ISSET(frontend_sockfd, &rfds)) {
res = 0;
}
} else {
if (FD_ISSET(frontend_sockfd, &wfds)) {
res = 0;
}
}
}
if (backend_conn != NULL) {
if (io == PROXY_SSH_PACKET_IO_READ) {
if (FD_ISSET(backend_sockfd, &rfds)) {
res = 1;
}
} else {
if (FD_ISSET(backend_sockfd, &wfds)) {
res = 1;
}
}
}
return res;
}
int proxy_ssh_packet_conn_poll(conn_t *conn, int io) {
int res;
/* This is only ever called on the backend connection. */
res = proxy_ssh_packet_conn_mpoll(NULL, conn, io);
if (res < 0) {
return -1;
}
return 0;
}
/* The purpose of conn_read() is to loop until either we have read in the
* requested reqlen from the socket, or the socket gives us an I/O error.
* We want to prevent short reads from causing problems elsewhere (e.g.
* in the decipher or MAC code).
*
* It is the caller's responsibility to ensure that buf is large enough to
* hold reqlen bytes.
*/
int proxy_ssh_packet_conn_read(conn_t *conn, void *buf, size_t reqlen,
int flags) {
void *ptr;
size_t remainlen;
if (reqlen == 0) {
return 0;
}
errno = 0;
ptr = buf;
remainlen = reqlen;
while (remainlen > 0) {
int res;
if (proxy_ssh_packet_conn_poll(conn, PROXY_SSH_PACKET_IO_READ) < 0) {
return -1;
}
/* The socket we accept is blocking, thus there's no need to handle
* EAGAIN/EWOULDBLOCK errors.
*/
res = read(conn->rfd, ptr, remainlen);
while (res <= 0) {
if (res < 0) {
int xerrno = errno;
if (xerrno == EINTR) {
pr_signals_handle();
res = read(conn->rfd, ptr, remainlen);
continue;
}
pr_trace_msg(trace_channel, 16,
"error reading from server (fd %d): %s", conn->rfd, strerror(xerrno));
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error reading from server (fd %d): %s", conn->rfd, strerror(xerrno));
errno = xerrno;
/* We explicitly disconnect the server here, rather than sending
* a DISCONNECT message, because the errors below all indicate
* a problem with the TCP connection, such that trying to write
* more data on that connection would cause problems.
*/
if (errno == ECONNRESET ||
errno == ECONNABORTED ||
#ifdef ETIMEDOUT
errno == ETIMEDOUT ||
#endif /* ETIMEDOUT */
#ifdef ENOTCONN
errno == ENOTCONN ||
#endif /* ENOTCONN */
#ifdef ESHUTDOWN
errno == ESHUTDOWN ||
#endif /* ESHUTDOWNN */
errno == EPIPE) {
xerrno = errno;
pr_trace_msg(trace_channel, 16,
"disconnecting server (%s)", strerror(xerrno));
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"disconnecting server (%s)", strerror(xerrno));
pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_CLIENT_EOF,
strerror(xerrno));
}
return -1;
} else {
/* If we read zero bytes here, treat it as an EOF and hang up on
* the uncommunicative client.
*/
pr_trace_msg(trace_channel, 16, "%s",
"disconnecting server (received EOF)");
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"disconnecting server (received EOF)");
pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_CLIENT_EOF,
NULL);
}
}
session.total_raw_in += reqlen;
if ((size_t) res == remainlen) {
break;
}
if (flags & PROXY_SSH_PACKET_READ_FL_PESSIMISTIC) {
pr_trace_msg(trace_channel, 20, "read %lu bytes, expected %lu bytes; "
"pessimistically returning", (unsigned long) res,
(unsigned long) remainlen);
break;
}
pr_trace_msg(trace_channel, 20, "read %lu bytes, expected %lu bytes; "
"reading more", (unsigned long) res, (unsigned long) remainlen);
ptr = ((char *) ptr + res);
remainlen -= res;
}
return reqlen;
}
static const char *get_msg_cmd_desc(unsigned char msg_type) {
const char *desc;
desc = proxy_ssh_packet_get_msg_type_desc(msg_type);
if (strncmp(desc, "SSH_MSG_", 8) == 0) {
desc += 8;
}
return desc;
}
void proxy_ssh_packet_log_cmd(struct proxy_ssh_packet *pkt, int from_frontend) {
cmd_rec *cmd;
const char *pkt_cmd, *pkt_note, *pkt_note_text;
/* Get a short version of the packet type for our cmd_rec/logging. */
pkt_cmd = get_msg_cmd_desc(proxy_ssh_packet_peek_msg_type(pkt));
/* XXX What to use as the cmd_rec arg? channel ID for CHANNEL_ commands;
* what else? Or maybe just hardcode "-" for now?
*/
cmd = pr_cmd_alloc(pkt->pool, 1, pstrdup(pkt->pool, pkt_cmd));
cmd->arg = pstrdup(pkt->pool, "-");
cmd->cmd_class = CL_MISC|CL_SSH;
/* Add a note to indicate the destination/target for this packet, be it
* "frontend" or "backend".
*/
pkt_note = "proxy.ssh.direction";
pkt_note_text = from_frontend == TRUE ? "backend" : "frontend";
if (pr_table_add_dup(cmd->notes, pkt_note, pkt_note_text, 0) < 0) {
int xerrno = errno;
if (xerrno != EEXIST) {
pr_trace_msg(trace_channel, 8,
"error setting '%s' note: %s", pkt_note, strerror(xerrno));
}
}
pr_cmd_dispatch_phase(cmd, LOG_CMD, 0);
destroy_pool(cmd->pool);
}
static void handle_global_request_msg(struct proxy_ssh_packet *pkt,
const struct proxy_session *proxy_sess, int from_frontend) {
unsigned char *buf, *ptr;
uint32_t buflen, len;
char *request_name;
int want_reply;
buf = pkt->payload;
buflen = pkt->payload_len;
/* Skip past the message type. */
buf += sizeof(char);
buflen -= sizeof(char);
len = proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &request_name);
if (proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_KEX) {
/* For most GLOBAL_REQUEST packets, once we have completed our kex,
* we proxy the packet to the frontend client.
*
* However, some GLOBAL_REQUEST types, such as the OpenSSH hostkey rotation
* extension, is NOT suitable for proxying to the frontend client, for
* the frontend client is concerned with our hostkeys, not the backend
* hostkeys.
*/
if (strcmp(request_name, "hostkeys-00@openssh.com") != 0) {
proxy_ssh_packet_proxied(proxy_sess, pkt, from_frontend);
return;
}
}
len = proxy_ssh_msg_read_bool(pkt->pool, &buf, &buflen, &want_reply);
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"server sent GLOBAL_REQUEST for '%s', %s", request_name,
want_reply ? "denying" : "ignoring");
if (want_reply == TRUE) {
struct proxy_ssh_packet *pkt2;
uint32_t bufsz;
int res;
buflen = bufsz = 1024;
ptr = buf = palloc(pkt->pool, bufsz);
len = proxy_ssh_msg_write_byte(&buf, &buflen,
PROXY_SSH_MSG_REQUEST_FAILURE);
pkt2 = proxy_ssh_packet_create(pkt->pool);
pkt2->payload = ptr;
pkt2->payload_len = len;
res = proxy_ssh_packet_write(proxy_sess->backend_ctrl_conn, pkt2);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error writing REQUEST_FAILURE message: %s", strerror(errno));
}
}
destroy_pool(pkt->pool);
}
static void handle_server_alive_msg(struct proxy_ssh_packet *pkt,
char msg_type) {
const char *msg_desc;
msg_desc = proxy_ssh_packet_get_msg_type_desc(msg_type);
pr_trace_msg(trace_channel, 12,
"server sent %s message, considering server alive", msg_desc);
server_alive_count = 0;
destroy_pool(pkt->pool);
}
static int handle_frontend_rekey(struct proxy_ssh_packet *pkt,
const struct proxy_session *proxy_sess) {
/* Reset the mod_sftp internal machinery, such that it handles this
* frontend-requested rekey.
*/
pr_trace_msg("proxy.ssh", 19,
"frontend-initiated rekeying STARTED, resetting mod_sftp packet handler");
proxy_ssh_packet_set_frontend_packet_handle(pkt->pool, NULL);
/* Make sure to remove our listener for mod_sftp's read-loop, until such
* time as the frontend rekeying completes.
*/
pr_event_unregister(&proxy_module, "mod_sftp.ssh2.read-poll", NULL);
/* Do NOT destroy this packet's pool! */
errno = ENOSYS;
return -1;
}
static void is_server_alive(conn_t *conn) {
unsigned char *buf, *ptr;
uint32_t bufsz, buflen, len = 0;
struct proxy_ssh_packet *pkt;
pool *tmp_pool;
if (++server_alive_count > server_alive_max) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"ProxySFTPServerAlive threshold (max %u checks, %u sec interval) "
"reached, disconnecting client", server_alive_max, server_alive_interval);
PROXY_SSH_DISCONNECT_CONN(conn, PROXY_SSH_DISCONNECT_BY_APPLICATION,
"server alive threshold reached");
}
tmp_pool = make_sub_pool(session.pool);
bufsz = buflen = 64;
ptr = buf = palloc(tmp_pool, bufsz);
pr_trace_msg(trace_channel, 9,
"sending GLOBAL_REQUEST (keepalive@proftpd.org)");
len += proxy_ssh_msg_write_byte(&buf, &buflen, PROXY_SSH_MSG_GLOBAL_REQUEST);
len += proxy_ssh_msg_write_string(&buf, &buflen, "keepalive@proftpd.org");
len += proxy_ssh_msg_write_bool(&buf, &buflen, TRUE);
pkt = proxy_ssh_packet_create(tmp_pool);
pkt->payload = ptr;
pkt->payload_len = len;
(void) proxy_ssh_packet_write(conn, pkt);
destroy_pool(tmp_pool);
}
/* Attempt to read in a random amount of data (up to the maximum amount of
* SSH2 packet data we support) from the socket. This is used to help
* mitigate the plaintext recovery attack described by CPNI-957037.
*
* Technically this is only necessary if a CBC mode cipher is in use, but
* there should be no harm in using for any cipher; we are going to
* disconnect the client after reading this data anyway.
*/
static void read_packet_discard(conn_t *conn) {
size_t buflen;
buflen = PROXY_SSH_MAX_PACKET_LEN -
((int) (PROXY_SSH_MAX_PACKET_LEN * (rand() / (RAND_MAX + 1.0))));
pr_trace_msg(trace_channel, 3, "reading %lu bytes of data for discarding",
(unsigned long) buflen);
if (buflen > 0) {
char buf[PROXY_SSH_MAX_PACKET_LEN];
int flags;
/* We don't necessarily want to wait for the entire random amount of data
* to be read in.
*/
flags = PROXY_SSH_PACKET_READ_FL_PESSIMISTIC;
proxy_ssh_packet_conn_read(conn, buf, buflen, flags);
}
return;
}
static int read_packet_len(conn_t *conn, struct proxy_ssh_packet *pkt,
unsigned char *buf, size_t *offset, size_t *buflen, size_t bufsz,
int etm_mac) {
uint32_t packet_len = 0, len = 0;
size_t readsz;
int res;
unsigned char *ptr = NULL;
readsz = proxy_ssh_cipher_get_read_block_size();
/* Since the packet length may be encrypted, we need to read in the first
* cipher_block_size bytes from the socket, and try to decrypt them, to know
* how many more bytes there are in the packet.
*/
if (pkt->aad_len > 0) {
/* If we are dealing with an authenticated encryption algorithm, or an
* ETM mode, read enough to include the AAD. For ETM modes, leave the
* first block for later.
*/
if (etm_mac == TRUE) {
readsz = pkt->aad_len;
} else {
readsz += pkt->aad_len;
}
}
res = proxy_ssh_packet_conn_read(conn, buf, readsz, 0);
if (res < 0) {
return res;
}
len = res;
if (proxy_ssh_cipher_read_data(pkt, buf, readsz, &ptr, &len) < 0) {
return -1;
}
memmove(&packet_len, ptr, sizeof(uint32_t));
pkt->packet_len = ntohl(packet_len);
ptr += sizeof(uint32_t);
len -= sizeof(uint32_t);
/* Copy the remaining unencrypted bytes from the block into the given
* buffer.
*/
if (len > 0) {
memmove(buf, ptr, len);
*buflen = (size_t) len;
}
*offset = 0;
return 0;
}
static int read_packet_padding_len(conn_t *conn, struct proxy_ssh_packet *pkt,
unsigned char *buf, size_t *offset, size_t *buflen, size_t bufsz) {
if (*buflen > sizeof(char)) {
/* XXX Assume the data in the buffer is unencrypted, and thus usable. */
memmove(&pkt->padding_len, buf + *offset, sizeof(char));
/* Advance the buffer past the byte we just read off. */
*offset += sizeof(char);
*buflen -= sizeof(char);
return 0;
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to read padding len: not enough data in buffer (%u bytes)",
(unsigned int) *buflen);
return -1;
}
static int read_packet_payload(conn_t *conn, struct proxy_ssh_packet *pkt,
unsigned char *buf, size_t *offset, size_t *buflen, size_t bufsz,
int etm_mac) {
unsigned char *ptr = NULL;
int res;
uint32_t payload_len = pkt->payload_len, padding_len = 0, auth_len = 0,
data_len, len = 0;
/* For authenticated encryption or ETM modes, we will NOT have the
* pkt->padding_len field yet.
*
* For authenticated encryption, we need to read in the first block, then
* decrypt it, to find the padding.
*
* For ETM, we only want to find the payload and padding AFTER we've read
* the entire (encrypted) payload, MAC'd it, THEN decrypt it.
*/
if (pkt->padding_len > 0) {
padding_len = pkt->padding_len;
}
auth_len = proxy_ssh_cipher_get_read_auth_size();
if (payload_len + padding_len + auth_len == 0 &&
etm_mac == FALSE) {
return 0;
}
if (payload_len > 0) {
/* We don't want to reject the packet outright yet; but we can ignore
* the payload data we're going to read in. This packet will fail
* eventually anyway.
*/
if (payload_len > PROXY_SSH_PACKET_MAX_PAYLOAD_LEN) {
pr_trace_msg(trace_channel, 20,
"payload len (%lu bytes) exceeds max payload len (%lu), "
"ignoring payload", (unsigned long) payload_len,
(unsigned long) PROXY_SSH_PACKET_MAX_PAYLOAD_LEN);
pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"server sent buggy/malicious packet payload length, ignoring");
errno = EPERM;
return -1;
}
pkt->payload = pcalloc(pkt->pool, payload_len);
}
/* If there's data in the buffer we received, it's probably already part
* of the payload, unencrypted. That will leave the remaining payload
* data, if any, to be read in and decrypted.
*/
if (*buflen > 0) {
if (*buflen < payload_len) {
memmove(pkt->payload, buf + *offset, *buflen);
payload_len -= *buflen;
*offset = 0;
*buflen = 0;
} else {
/* There's enough already for the payload length. Nice. */
memmove(pkt->payload, buf + *offset, payload_len);
*offset += payload_len;
*buflen -= payload_len;
payload_len = 0;
}
}
/* The padding length is required to be greater than zero. However, we may
* not know the padding length yet, as for authenticated encryption or ETM
* modes.
*/
if (padding_len > 0) {
pkt->padding = pcalloc(pkt->pool, padding_len);
}
/* If there's data in the buffer we received, it's probably already part
* of the padding, unencrypted. That will leave the remaining padding
* data, if any, to be read in and decrypted.
*/
if (*buflen > 0 &&
padding_len > 0) {
if (*buflen < padding_len) {
memmove(pkt->padding, buf + *offset, *buflen);
padding_len -= *buflen;
*offset = 0;
*buflen = 0;
} else {
/* There's enough already for the padding length. Nice. */
memmove(pkt->padding, buf + *offset, padding_len);
*offset += padding_len;
*buflen -= padding_len;
padding_len = 0;
}
}
if (etm_mac == TRUE) {
data_len = pkt->packet_len;
} else {
data_len = payload_len + padding_len + auth_len;
}
if (data_len == 0) {
return 0;
}
if (data_len > bufsz) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"remaining packet data (%lu bytes) exceeds packet buffer size (%lu "
"bytes)", (unsigned long) data_len, (unsigned long) bufsz);
errno = EPERM;
return -1;
}
res = proxy_ssh_packet_conn_read(conn, buf + *offset, data_len, 0);
if (res < 0) {
return res;
}
len = res;
/* For ETM modes, we do NOT want to decrypt the data yet; we need to read/
* compare MACs first.
*/
if (etm_mac == TRUE) {
*buflen = res;
} else {
if (proxy_ssh_cipher_read_data(pkt, buf + *offset, data_len, &ptr,
&len) < 0) {
return -1;
}
if (payload_len > 0) {
memmove(pkt->payload + (pkt->payload_len - payload_len), ptr,
payload_len);
}
memmove(pkt->padding + (pkt->padding_len - padding_len), ptr + payload_len,
padding_len);
}
return 0;
}
static int read_packet_mac(conn_t *conn, struct proxy_ssh_packet *pkt,
unsigned char *buf) {
int res;
uint32_t mac_len = pkt->mac_len;
if (mac_len == 0) {
return 0;
}
res = proxy_ssh_packet_conn_read(conn, buf, mac_len, 0);
if (res < 0) {
return res;
}
pkt->mac = palloc(pkt->pool, pkt->mac_len);
memmove(pkt->mac, buf, res);
return 0;
}
struct proxy_ssh_packet *proxy_ssh_packet_create(pool *p) {
pool *tmp_pool;
struct proxy_ssh_packet *pkt;
tmp_pool = make_sub_pool(p);
pr_pool_tag(tmp_pool, "Proxy SSH2 packet pool");
pkt = pcalloc(tmp_pool, sizeof(struct proxy_ssh_packet));
pkt->pool = tmp_pool;
pkt->m = &proxy_module;
pkt->packet_len = 0;
pkt->payload = NULL;
pkt->payload_len = 0;
pkt->padding_len = 0;
pkt->aad = NULL;
pkt->aad_len = 0;
return pkt;
}
char proxy_ssh_packet_get_msg_type(struct proxy_ssh_packet *pkt) {
char msg_type;
memmove(&msg_type, pkt->payload, sizeof(char));
pkt->payload += sizeof(char);
pkt->payload_len -= sizeof(char);
return msg_type;
}
char proxy_ssh_packet_peek_msg_type(const struct proxy_ssh_packet *pkt) {
char msg_type;
memmove(&msg_type, pkt->payload, sizeof(char));
return msg_type;
}
const char *proxy_ssh_packet_get_msg_type_desc(unsigned char msg_type) {
switch (msg_type) {
case PROXY_SSH_MSG_DISCONNECT:
return "SSH_MSG_DISCONNECT";
case PROXY_SSH_MSG_IGNORE:
return "SSH_MSG_IGNORE";
case PROXY_SSH_MSG_UNIMPLEMENTED:
return "SSH_MSG_UNIMPLEMENTED";
case PROXY_SSH_MSG_DEBUG:
return "SSH_MSG_DEBUG";
case PROXY_SSH_MSG_SERVICE_REQUEST:
return "SSH_MSG_SERVICE_REQUEST";
case PROXY_SSH_MSG_SERVICE_ACCEPT:
return "SSH_MSG_SERVICE_ACCEPT";
case PROXY_SSH_MSG_EXT_INFO:
return "SSH_MSG_EXT_INFO";
case PROXY_SSH_MSG_KEXINIT:
return "SSH_MSG_KEXINIT";
case PROXY_SSH_MSG_NEWKEYS:
return "SSH_MSG_NEWKEYS";
case PROXY_SSH_MSG_KEX_DH_INIT:
return "SSH_MSG_KEX_DH_INIT";
case PROXY_SSH_MSG_KEX_DH_REPLY:
return "SSH_MSG_KEX_DH_REPLY";
case PROXY_SSH_MSG_KEX_DH_GEX_INIT:
return "SSH_MSG_KEX_DH_GEX_INIT";
case PROXY_SSH_MSG_KEX_DH_GEX_REPLY:
return "SSH_MSG_KEX_DH_GEX_REPLY";
case PROXY_SSH_MSG_KEX_DH_GEX_REQUEST:
return "SSH_MSG_KEX_DH_GEX_REQUEST";
case PROXY_SSH_MSG_USER_AUTH_REQUEST:
return "SSH_MSG_USERAUTH_REQUEST";
case PROXY_SSH_MSG_USER_AUTH_FAILURE:
return "SSH_MSG_USERAUTH_FAILURE";
case PROXY_SSH_MSG_USER_AUTH_SUCCESS:
return "SSH_MSG_USERAUTH_SUCCESS";
case PROXY_SSH_MSG_USER_AUTH_BANNER:
return "SSH_MSG_USERAUTH_BANNER";
case PROXY_SSH_MSG_USER_AUTH_PASSWD:
return "SSH_MSG_USERAUTH_PASSWD";
case PROXY_SSH_MSG_USER_AUTH_INFO_RESP:
return "SSH_MSG_USERAUTH_INFO_RESP";
case PROXY_SSH_MSG_GLOBAL_REQUEST:
return "SSH_MSG_GLOBAL_REQUEST";
case PROXY_SSH_MSG_REQUEST_SUCCESS:
return "SSH_MSG_REQUEST_SUCCESS";
case PROXY_SSH_MSG_REQUEST_FAILURE:
return "SSH_MSG_REQUEST_FAILURE";
case PROXY_SSH_MSG_CHANNEL_OPEN:
return "SSH_MSG_CHANNEL_OPEN";
case PROXY_SSH_MSG_CHANNEL_OPEN_CONFIRMATION:
return "SSH_MSG_CHANNEL_OPEN_CONFIRMATION";
case PROXY_SSH_MSG_CHANNEL_OPEN_FAILURE:
return "SSH_MSG_CHANNEL_OPEN_FAILURE";
case PROXY_SSH_MSG_CHANNEL_WINDOW_ADJUST:
return "SSH_MSG_CHANNEL_WINDOW_ADJUST";
case PROXY_SSH_MSG_CHANNEL_DATA:
return "SSH_MSG_CHANNEL_DATA";
case PROXY_SSH_MSG_CHANNEL_EXTENDED_DATA:
return "SSH_MSG_CHANNEL_EXTENDED_DATA";
case PROXY_SSH_MSG_CHANNEL_EOF:
return "SSH_MSG_CHANNEL_EOF";
case PROXY_SSH_MSG_CHANNEL_CLOSE:
return "SSH_MSG_CHANNEL_CLOSE";
case PROXY_SSH_MSG_CHANNEL_REQUEST:
return "SSH_MSG_CHANNEL_REQUEST";
case PROXY_SSH_MSG_CHANNEL_SUCCESS:
return "SSH_MSG_CHANNEL_SUCCESS";
case PROXY_SSH_MSG_CHANNEL_FAILURE:
return "SSH_MSG_CHANNEL_FAILURE";
}
return "(unknown)";
}
int proxy_ssh_packet_get_poll_attempts(unsigned int *nattempts) {
if (nattempts == NULL) {
errno = EINVAL;
return -1;
}
*nattempts = poll_attempts;
return 0;
}
int proxy_ssh_packet_set_poll_attempts(unsigned int nattempts) {
if (nattempts == 0) {
poll_attempts = DEFAULT_POLL_ATTEMPTS;
} else {
poll_attempts = nattempts;
}
return 0;
}
int proxy_ssh_packet_get_poll_timeout(int *secs, unsigned long *ms) {
if (secs == NULL ||
ms == NULL) {
errno = EINVAL;
return -1;
}
*secs = poll_timeout_secs;
*ms = poll_timeout_ms;
return 0;
}
int proxy_ssh_packet_set_poll_timeout(int secs, unsigned long ms) {
if (secs < 0) {
poll_timeout_secs = -1;
poll_timeout_ms = 0;
} else {
poll_timeout_secs = secs;
poll_timeout_ms = ms;
}
return 0;
}
int proxy_ssh_packet_set_server_alive(unsigned int max, unsigned int interval) {
server_alive_max = max;
server_alive_interval = interval;
return 0;
}
static void reset_timers(void) {
int res;
/* Handle the case where timers might be being processed at the moment. */
res = pr_timer_reset(PR_TIMER_IDLE, ANY_MODULE);
while (res < 0) {
if (errno == EINTR) {
pr_signals_handle();
res = pr_timer_reset(PR_TIMER_IDLE, ANY_MODULE);
}
break;
}
res = pr_timer_reset(PR_TIMER_STALLED, ANY_MODULE);
while (res < 0) {
if (errno == EINTR) {
pr_signals_handle();
res = pr_timer_reset(PR_TIMER_STALLED, ANY_MODULE);
}
break;
}
}
static int check_packet_lengths(conn_t *conn, struct proxy_ssh_packet *pkt) {
if (pkt->packet_len < 5) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"packet length too short (%lu), less than minimum packet length (5)",
(unsigned long) pkt->packet_len);
read_packet_discard(conn);
return -1;
}
if (pkt->packet_len > PROXY_SSH_MAX_PACKET_LEN) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"packet length too long (%lu), exceeds maximum packet length (%lu)",
(unsigned long) pkt->packet_len,
(unsigned long) PROXY_SSH_MAX_PACKET_LEN);
read_packet_discard(conn);
return -1;
}
/* Per Section 6 of RFC4253, the minimum padding length is 4, the
* maximum padding length is 255.
*/
if (pkt->padding_len < PROXY_SSH_MIN_PADDING_LEN) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"padding length too short (%u), less than minimum padding length (%u)",
(unsigned int) pkt->padding_len,
(unsigned int) PROXY_SSH_MIN_PADDING_LEN);
read_packet_discard(conn);
return -1;
}
if (pkt->padding_len > pkt->packet_len) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"padding length too long (%u), exceeds packet length (%lu)",
(unsigned int) pkt->padding_len, (unsigned long) pkt->packet_len);
read_packet_discard(conn);
return -1;
}
return 0;
}
int proxy_ssh_packet_read(conn_t *conn, struct proxy_ssh_packet *pkt) {
unsigned char buf[PROXY_SSH_MAX_PACKET_LEN];
size_t buflen, bufsz = PROXY_SSH_MAX_PACKET_LEN, offset = 0, auth_len = 0;
int etm_mac = FALSE;
pr_session_set_idle();
auth_len = proxy_ssh_cipher_get_read_auth_size();
if (auth_len > 0) {
/* Authenticated encryption ciphers do not encrypt the packet length,
* and instead use it as Additional Authenticated Data (AAD).
*/
pkt->aad_len = sizeof(uint32_t);
}
etm_mac = proxy_ssh_mac_is_read_etm();
if (etm_mac == TRUE) {
/* ETM modes do not encrypt the packet length, and instead use it as
* Additional Authenticated Data (AAD).
*/
pkt->aad_len = sizeof(uint32_t);
}
while (TRUE) {
uint32_t encrypted_datasz, req_blocksz;
unsigned char *buf2 = NULL;
size_t buflen2 = 0, bufsz2 = 0;
pr_signals_handle();
/* This is in a while loop in order to consume any debug/ignore
* messages which the client may send.
*/
buflen = 0;
memset(buf, 0, sizeof(buf));
if (read_packet_len(conn, pkt, buf, &offset, &buflen, bufsz, etm_mac) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"no data to be read from socket %d", conn->rfd);
return -1;
}
pr_trace_msg(trace_channel, 20, "SSH2 packet len = %lu bytes",
(unsigned long) pkt->packet_len);
/* In order to mitigate the plaintext recovery attack described in
* CPNI-957037:
*
* http://www.cpni.gov.uk/Docs/Vulnerability_Advisory_SSH.txt
*
* we do NOT check that the packet length is sane here; we have to
* wait until the MAC check succeeds.
*/
/* Note: Checking for the RFC4253-recommended minimum packet length
* of 16 bytes causes KEX to fail (the NEWKEYS packet is 12 bytes).
* Thus that particular check is omitted.
*/
if (etm_mac == FALSE) {
if (read_packet_padding_len(conn, pkt, buf, &offset, &buflen,
bufsz) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"no data to be read from socket %d", conn->rfd);
read_packet_discard(conn);
return -1;
}
pr_trace_msg(trace_channel, 20, "SSH2 packet padding len = %u bytes",
(unsigned int) pkt->padding_len);
if (check_packet_lengths(conn, pkt) < 0) {
return -1;
}
pkt->payload_len = (pkt->packet_len - pkt->padding_len - 1);
}
pr_trace_msg(trace_channel, 20, "SSH2 packet payload len = %lu bytes",
(unsigned long) pkt->payload_len);
/* Read both payload and padding, since we may need to have both before
* decrypting the data.
*/
if (read_packet_payload(conn, pkt, buf, &offset, &buflen, bufsz,
etm_mac) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to read payload from socket %d", conn->rfd);
read_packet_discard(conn);
return -1;
}
pkt->mac_len = proxy_ssh_mac_get_block_size();
pr_trace_msg(trace_channel, 20, "SSH2 packet MAC len = %lu bytes",
(unsigned long) pkt->mac_len);
if (etm_mac == TRUE) {
bufsz2 = buflen2 = pkt->mac_len;
buf2 = pcalloc(pkt->pool, bufsz2);
/* The MAC routines assume the presence of the necessary data in
* pkt->payload, so we temporarily put our encrypted packet data there.
*/
pkt->payload = buf;
pkt->payload_len = buflen;
pkt->seqno = packet_server_seqno;
if (read_packet_mac(conn, pkt, buf2) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to read MAC from socket %d", conn->rfd);
read_packet_discard(conn);
return -1;
}
if (proxy_ssh_mac_read_data(pkt) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to verify MAC on packet from socket %d", conn->rfd);
/* In order to further mitigate CPNI-957037, we will read in a
* random amount of more data from the network before closing
* the connection.
*/
read_packet_discard(conn);
return -1;
}
/* Now we can decrypt the payload; `buf/buflen` are the encrypted
* packet from read_packet_payload().
*/
bufsz2 = buflen2 = PROXY_SSH_MAX_PACKET_LEN;
buf2 = pcalloc(pkt->pool, bufsz2);
if (proxy_ssh_cipher_read_data(pkt, buf, buflen, &buf2,
(uint32_t *) &buflen2) < 0) {
return -1;
}
offset = 0;
if (read_packet_padding_len(conn, pkt, buf2, &offset, &buflen2,
bufsz2) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"no data to be read from socket %d", conn->rfd);
read_packet_discard(conn);
return -1;
}
pr_trace_msg(trace_channel, 20, "SSH2 packet padding len = %u bytes",
(unsigned int) pkt->padding_len);
} else {
memset(buf, 0, sizeof(buf));
if (read_packet_mac(conn, pkt, buf) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to read MAC from socket %d", conn->rfd);
read_packet_discard(conn);
return -1;
}
pkt->seqno = packet_server_seqno;
if (proxy_ssh_mac_read_data(pkt) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to verify MAC on packet from socket %d", conn->rfd);
/* In order to further mitigate CPNI-957037, we will read in a
* random amount of more data from the network before closing
* the connection.
*/
read_packet_discard(conn);
return -1;
}
}
/* Now that the MAC check has passed, we can do sanity checks based
* on the fields we have read in, and trust that those fields are
* correct.
*/
if (check_packet_lengths(conn, pkt) < 0) {
return -1;
}
if (etm_mac == TRUE) {
pkt->payload_len = (pkt->packet_len - pkt->padding_len - 1);
if (pkt->payload_len > 0) {
pkt->payload = pcalloc(pkt->pool, pkt->payload_len);
memmove(pkt->payload, buf2 + offset, pkt->payload_len);
}
pkt->padding = pcalloc(pkt->pool, pkt->padding_len);
memmove(pkt->padding, buf2 + offset + pkt->payload_len, pkt->padding_len);
}
/* From RFC4253, Section 6:
*
* random padding
* Arbitrary-length padding, such that the total length of
* (packet_length || padding_length || payload || random padding)
* is a multiple of the cipher block size or 8, whichever is
* larger.
*
* Thus packet_len + sizeof(uint32_t) (for the actual packet length field)
* is that "(packet_length || padding_length || payload || padding)"
* value.
*/
req_blocksz = MAX(8, proxy_ssh_cipher_get_read_block_size());
encrypted_datasz = pkt->packet_len + sizeof(uint32_t);
/* If AAD bytes are present, they are not encrypted. */
if (pkt->aad_len > 0) {
encrypted_datasz -= pkt->aad_len;
}
if (encrypted_datasz % req_blocksz != 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"packet length (%lu) not a multiple of the required block size (%lu)",
(unsigned long) encrypted_datasz, (unsigned long) req_blocksz);
read_packet_discard(conn);
return -1;
}
/* XXX I'm not so sure about this check; we SHOULD have a maximum
* payload check, but using the max packet length check for the payload
* length seems awkward.
*/
if (pkt->payload_len > PROXY_SSH_MAX_PACKET_LEN) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"payload length too long (%lu), exceeds maximum payload length (%lu) "
"(packet len %lu, padding len %u)", (unsigned long) pkt->payload_len,
(unsigned long) PROXY_SSH_MAX_PACKET_LEN,
(unsigned long) pkt->packet_len, (unsigned int) pkt->padding_len);
read_packet_discard(conn);
return -1;
}
/* Sanity checks passed; move on to the reading the packet payload. */
if (proxy_ssh_compress_read_data(pkt) < 0) {
return -1;
}
packet_server_seqno++;
reset_timers();
break;
}
return 0;
}
static int write_packet_padding(struct proxy_ssh_packet *pkt) {
register unsigned int i;
uint32_t packet_len = 0;
size_t blocksz;
blocksz = proxy_ssh_cipher_get_write_block_size();
/* RFC 4253, section 6, says that the random padding is calculated
* as follows:
*
* random padding
* Arbitrary-length padding, such that the total length of
* (packet_length || padding_length || payload || random padding)
* is a multiple of the cipher block size or 8, whichever is
* larger. There MUST be at least four bytes of padding. The
* padding SHOULD consist of random bytes. The maximum amount of
* padding is 255 bytes.
*
* This means:
*
* packet len = sizeof(packet_len field) + sizeof(padding_len field) +
* sizeof(payload field) + sizeof(padding field)
*/
packet_len = sizeof(uint32_t) + sizeof(char) + pkt->payload_len;
if (pkt->aad_len > 0) {
/* Packet length is not encrypted for encrypted authentication, or
* Encrypt-Then-MAC modes.
*/
packet_len -= pkt->aad_len;
}
pkt->padding_len = (char) (blocksz - (packet_len % blocksz));
if (pkt->padding_len < 4) {
/* As per RFC, there must be at least 4 bytes of padding. So if the
* above calculated less, then we need to add another block's worth
* of padding.
*/
pkt->padding_len += blocksz;
}
pkt->padding = palloc(pkt->pool, pkt->padding_len);
/* Fill the padding with pseudo-random data. */
for (i = 0; i < pkt->padding_len; i++) {
pkt->padding[i] = (unsigned char) pr_random_next(0, UCHAR_MAX);
}
return 0;
}
#define PROXY_SSH_PACKET_IOVSZ 12
static struct iovec packet_iov[PROXY_SSH_PACKET_IOVSZ];
static unsigned int packet_niov = 0;
int proxy_ssh_packet_send(conn_t *conn, struct proxy_ssh_packet *pkt) {
unsigned char buf[PROXY_SSH_MAX_PACKET_LEN * 2], msg_type;
size_t buflen = 0, bufsz = PROXY_SSH_MAX_PACKET_LEN;
uint32_t packet_len = 0, auth_len = 0;
int res, write_len = 0, block_alarms = FALSE, etm_mac = FALSE;
/* No interruptions, please. If, for example, we are interrupted here
* by the SFTPRekey timer, that timer will cause this same function to
* be called -- but the packet_iov/packet_niov values will be different.
* Which in turn leads to malformed packets, and thus badness (Bug#4216).
*/
if (proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_AUTH) {
block_alarms = TRUE;
}
if (block_alarms == TRUE) {
pr_alarms_block();
}
auth_len = proxy_ssh_cipher_get_write_auth_size();
if (auth_len > 0) {
/* Authenticated encryption ciphers do not encrypt the packet length,
* and instead use it as Additional Authenticated Data (AAD).
*/
pkt->aad_len = sizeof(uint32_t);
pkt->aad = NULL;
}
etm_mac = proxy_ssh_mac_is_write_etm();
if (etm_mac == TRUE) {
/* Encrypt-Then-Mac modes do not encrypt the packet length; treat it
* as Additional Authenticated Data (AAD).
*/
pkt->aad_len = sizeof(uint32_t);
pkt->aad = NULL;
}
/* Clear the iovec array before sending the data, if possible. */
if (packet_niov == 0) {
memset(packet_iov, 0, sizeof(packet_iov));
}
msg_type = proxy_ssh_packet_peek_msg_type(pkt);
if (proxy_ssh_compress_write_data(pkt) < 0) {
int xerrno = errno;
if (block_alarms == TRUE) {
pr_alarms_unblock();
}
errno = xerrno;
return -1;
}
if (write_packet_padding(pkt) < 0) {
int xerrno = errno;
if (block_alarms == TRUE) {
pr_alarms_unblock();
}
errno = xerrno;
return -1;
}
/* Packet length: padding len + payload + padding */
pkt->packet_len = packet_len = sizeof(char) + pkt->payload_len +
pkt->padding_len;
pkt->seqno = packet_client_seqno;
memset(buf, 0, sizeof(buf));
buflen = bufsz;
if (etm_mac == TRUE) {
if (proxy_ssh_cipher_write_data(pkt, buf, &buflen) < 0) {
int xerrno = errno;
if (block_alarms == TRUE) {
pr_alarms_unblock();
}
errno = xerrno;
return -1;
}
/* Once we have the encrypted data, overwrite the plaintext packet payload
* with it, so that the MAC is calculated from the encrypted data.
*/
pkt->payload = buf;
pkt->payload_len = buflen;
if (proxy_ssh_mac_write_data(pkt) < 0) {
int xerrno = errno;
if (block_alarms == TRUE) {
pr_alarms_unblock();
}
errno = xerrno;
return -1;
}
} else {
if (proxy_ssh_mac_write_data(pkt) < 0) {
int xerrno = errno;
if (block_alarms == TRUE) {
pr_alarms_unblock();
}
errno = xerrno;
return -1;
}
if (proxy_ssh_cipher_write_data(pkt, buf, &buflen) < 0) {
int xerrno = errno;
if (block_alarms == TRUE) {
pr_alarms_unblock();
}
errno = xerrno;
return -1;
}
}
if (buflen > 0) {
/* We have encrypted data, which means we don't need as many of the
* iovec slots as for unencrypted data.
*/
if (sent_version_id == FALSE) {
packet_iov[packet_niov].iov_base = (void *) version_id;
packet_iov[packet_niov].iov_len = strlen(version_id);
write_len += packet_iov[packet_niov].iov_len;
packet_niov++;
}
if (pkt->aad_len > 0) {
pr_trace_msg(trace_channel, 20, "sending %lu bytes of packet AAD data",
(unsigned long) pkt->aad_len);
packet_iov[packet_niov].iov_base = (void *) pkt->aad;
packet_iov[packet_niov].iov_len = pkt->aad_len;
write_len += packet_iov[packet_niov].iov_len;
packet_niov++;
}
pr_trace_msg(trace_channel, 20, "sending %lu bytes of packet payload data",
(unsigned long) buflen);
packet_iov[packet_niov].iov_base = (void *) buf;
packet_iov[packet_niov].iov_len = buflen;
write_len += packet_iov[packet_niov].iov_len;
packet_niov++;
if (pkt->mac_len > 0) {
pr_trace_msg(trace_channel, 20, "sending %lu bytes of packet MAC data",
(unsigned long) pkt->mac_len);
packet_iov[packet_niov].iov_base = (void *) pkt->mac;
packet_iov[packet_niov].iov_len = pkt->mac_len;
write_len += packet_iov[packet_niov].iov_len;
packet_niov++;
}
} else {
/* Don't forget to convert the packet len to network-byte order, since
* this length is sent over the wire.
*/
packet_len = htonl(packet_len);
if (sent_version_id == FALSE) {
packet_iov[packet_niov].iov_base = (void *) version_id;
packet_iov[packet_niov].iov_len = strlen(version_id);
write_len += packet_iov[packet_niov].iov_len;
packet_niov++;
}
packet_iov[packet_niov].iov_base = (void *) &packet_len;
packet_iov[packet_niov].iov_len = sizeof(uint32_t);
write_len += packet_iov[packet_niov].iov_len;
packet_niov++;
packet_iov[packet_niov].iov_base = (void *) &(pkt->padding_len);
packet_iov[packet_niov].iov_len = sizeof(char);
write_len += packet_iov[packet_niov].iov_len;
packet_niov++;
packet_iov[packet_niov].iov_base = (void *) pkt->payload;
packet_iov[packet_niov].iov_len = pkt->payload_len;
write_len += packet_iov[packet_niov].iov_len;
packet_niov++;
packet_iov[packet_niov].iov_base = (void *) pkt->padding;
packet_iov[packet_niov].iov_len = pkt->padding_len;
write_len += packet_iov[packet_niov].iov_len;
packet_niov++;
if (pkt->mac_len > 0) {
packet_iov[packet_niov].iov_base = (void *) pkt->mac;
packet_iov[packet_niov].iov_len = pkt->mac_len;
write_len += packet_iov[packet_niov].iov_len;
packet_niov++;
}
}
if (proxy_ssh_packet_conn_poll(conn, PROXY_SSH_PACKET_IO_WRITE) < 0) {
int xerrno = errno;
/* Socket not writable? Clear the array, and try again. */
memset(packet_iov, 0, sizeof(packet_iov));
packet_niov = 0;
if (block_alarms == TRUE) {
pr_alarms_unblock();
}
errno = xerrno;
return -1;
}
/* The socket we accept is blocking, thus there's no need to handle
* EAGAIN/EWOULDBLOCK errors.
*/
res = writev(conn->wfd, packet_iov, packet_niov);
while (res < 0) {
int xerrno = errno;
if (xerrno == EINTR) {
pr_signals_handle();
res = writev(conn->wfd, packet_iov, packet_niov);
continue;
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error writing packet (fd %d): %s", conn->wfd, strerror(xerrno));
if (xerrno == ECONNRESET ||
xerrno == ECONNABORTED ||
xerrno == EPIPE) {
if (block_alarms == TRUE) {
pr_alarms_unblock();
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"disconnecting server (%s)", strerror(xerrno));
pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION,
strerror(xerrno));
}
/* Always clear the iovec array after sending the data. */
memset(packet_iov, 0, sizeof(packet_iov));
packet_niov = 0;
if (block_alarms == TRUE) {
pr_alarms_unblock();
}
errno = xerrno;
return -1;
}
session.total_raw_out += res;
/* Always clear the iovec array after sending the data. */
memset(packet_iov, 0, sizeof(packet_iov));
packet_niov = 0;
if (sent_version_id == FALSE) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"sent client version '%s'", client_version);
sent_version_id = TRUE;
}
packet_client_seqno++;
pr_trace_msg(trace_channel, 3, "sent %s (%d) packet (%d bytes)",
proxy_ssh_packet_get_msg_type_desc(msg_type), msg_type, res);
if (block_alarms == TRUE) {
/* Now that we've written out the packet, we can be interrupted again. */
pr_alarms_unblock();
}
reset_timers();
return 0;
}
int proxy_ssh_packet_write(conn_t *conn, struct proxy_ssh_packet *pkt) {
/* Make sure that any frontend AAD ciphers/data are not leaked through to
* the backend IO routines.
*/
if (pkt->aad_len > 0) {
pkt->aad_len = 0;
pkt->aad = NULL;
}
return proxy_ssh_packet_send(conn, pkt);
}
int proxy_ssh_packet_write_frontend(conn_t *conn,
struct proxy_ssh_packet *pkt) {
if (frontend_packet_write == NULL) {
errno = ENOSYS;
return -1;
}
/* Make sure that any backend AAD ciphers/data are not leaked through to
* the frontend IO routines.
*/
if (pkt->aad_len > 0) {
pkt->aad_len = 0;
pkt->aad = NULL;
}
return (frontend_packet_write)(conn->wfd, pkt);
}
void proxy_ssh_packet_handle_debug(struct proxy_ssh_packet *pkt) {
register unsigned int i;
int always_display;
char *text, *lang;
uint32_t len;
len = proxy_ssh_msg_read_bool(pkt->pool, &pkt->payload, &pkt->payload_len,
&always_display);
len = proxy_ssh_msg_read_string(pkt->pool, &pkt->payload, &pkt->payload_len,
&text);
/* Ignore the language tag. */
(void) proxy_ssh_msg_read_string(pkt->pool, &pkt->payload, &pkt->payload_len,
&lang);
/* Sanity-check the message for control (and other non-printable)
* characters.
*/
for (i = 0; i < strlen(text); i++) {
if (PR_ISCNTRL(text[i]) ||
!PR_ISPRINT(text[i])) {
text[i] = '?';
}
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"server sent SSH_MSG_DEBUG message '%s'", text);
if (always_display == TRUE) {
pr_log_debug(DEBUG0, MOD_PROXY_VERSION
": server sent SSH_MSG_DEBUG message '%s'", text);
}
destroy_pool(pkt->pool);
}
void proxy_ssh_packet_handle_disconnect(struct proxy_ssh_packet *pkt) {
register unsigned int i;
char *explain = NULL, *lang = NULL;
const char *reason_text = NULL;
uint32_t reason_code, len;
len = proxy_ssh_msg_read_int(pkt->pool, &pkt->payload, &pkt->payload_len,
&reason_code);
reason_text = proxy_ssh_disconnect_get_text(reason_code);
if (reason_text == NULL) {
pr_trace_msg(trace_channel, 9,
"server sent unknown disconnect reason code %lu",
(unsigned long) reason_code);
reason_text = "Unknown reason code";
}
len = proxy_ssh_msg_read_string(pkt->pool, &pkt->payload, &pkt->payload_len,
&explain);
/* Not all clients send a language tag. */
if (pkt->payload_len > 0) {
len = proxy_ssh_msg_read_string(pkt->pool, &pkt->payload,
&pkt->payload_len, &lang);
}
/* Sanity-check the message for control characters. */
for (i = 0; i < strlen(explain); i++) {
if (PR_ISCNTRL(explain[i])) {
explain[i] = '?';
}
}
/* XXX Use the language tag somehow, if provided. */
if (lang != NULL) {
pr_trace_msg(trace_channel, 19, "server sent DISCONNECT language tag '%s'",
lang);
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"server at %s sent SSH_DISCONNECT message: %s (%s)",
pr_netaddr_get_ipstr(session.c->remote_addr), explain, reason_text);
pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_CLIENT_QUIT, explain);
}
void proxy_ssh_packet_handle_ext_info(struct proxy_ssh_packet *pkt) {
register unsigned int i;
unsigned char *buf;
uint32_t buflen, ext_count = 0;
buf = pkt->payload;
buflen = pkt->payload_len;
proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &ext_count);
pr_trace_msg(trace_channel, 9, "server sent EXT_INFO with %lu %s",
(unsigned long) ext_count, ext_count != 1 ? "extensions" : "extension");
for (i = 0; i < ext_count; i++) {
unsigned char *ext_data = NULL;
char *ext_name = NULL;
uint32_t ext_datalen = 0;
proxy_ssh_msg_read_string(pkt->pool, &buf, &buflen, &ext_name);
proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &ext_datalen);
proxy_ssh_msg_read_data(pkt->pool, &buf, &buflen, ext_datalen, &ext_data);
pr_trace_msg(trace_channel, 9,
"server extension: %s (value %lu bytes)", ext_name,
(unsigned long) ext_datalen);
}
destroy_pool(pkt->pool);
}
void proxy_ssh_packet_handle_ignore(struct proxy_ssh_packet *pkt) {
char *text;
size_t text_len;
uint32_t len;
len = proxy_ssh_msg_read_string(pkt->pool, &pkt->payload, &pkt->payload_len,
&text);
text_len = strlen(text);
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"server sent SSH_MSG_IGNORE message (%u bytes)", (unsigned int) text_len);
destroy_pool(pkt->pool);
}
void proxy_ssh_packet_handle_unimplemented(struct proxy_ssh_packet *pkt) {
uint32_t seqno, len;
len = proxy_ssh_msg_read_int(pkt->pool, &pkt->payload, &pkt->payload_len,
&seqno);
pr_trace_msg(trace_channel, 7, "received SSH_MSG_UNIMPLEMENTED for "
"packet #%lu", (unsigned long) seqno);
destroy_pool(pkt->pool);
}
int proxy_ssh_packet_proxied(const struct proxy_session *proxy_sess,
struct proxy_ssh_packet *pkt, int from_frontend) {
int res, xerrno = 0;
char msg_type;
msg_type = proxy_ssh_packet_peek_msg_type(pkt);
if (from_frontend == TRUE) {
/* Write the packet to the backend. */
pr_trace_msg(trace_channel, 17,
"proxying %s (%d) packet from frontend to backend",
proxy_ssh_packet_get_msg_type_desc(msg_type), msg_type);
res = proxy_ssh_packet_write(proxy_sess->backend_ctrl_conn, pkt);
xerrno = errno;
if (res < 0) {
pr_trace_msg(trace_channel, 2,
"error proxying packet from frontend to backend: %s", strerror(xerrno));
}
} else {
pr_trace_msg(trace_channel, 17,
"proxying %s (%d) packet from backend to frontend",
proxy_ssh_packet_get_msg_type_desc(msg_type), msg_type);
res = proxy_ssh_packet_write_frontend(proxy_sess->frontend_ctrl_conn, pkt);
xerrno = errno;
if (res < 0) {
if (xerrno != ENOSYS) {
pr_trace_msg(trace_channel, 2,
"error proxying packet from backend to frontend: %s",
strerror(xerrno));
} else {
/* Ignore the case where we are told not to write packets to the
* frontend client.
*/
res = 0;
xerrno = errno = 0;
}
}
}
errno = xerrno;
return res;
}
int proxy_ssh_packet_handle(void *data) {
const struct proxy_session *proxy_sess;
struct proxy_ssh_packet *pkt;
unsigned char msg_type;
int from_frontend = FALSE;
proxy_sess = pr_table_get(session.notes, "mod_proxy.proxy-session", NULL);
if (proxy_sess == NULL) {
/* Unlikely to occur. */
errno = EPERM;
return -1;
}
pkt = data;
/* We only peek at the message type here, so that we can properly proxy
* the entire packet if needed.
*/
msg_type = proxy_ssh_packet_peek_msg_type(pkt);
pr_trace_msg(trace_channel, 20, "received %s (%d) packet (from mod_%s.c)",
proxy_ssh_packet_get_msg_type_desc(msg_type), msg_type,
pkt->m->name);
/* Note: Some of the SSH messages will be handled regardless of the
* proxy_sess_state flags; this is intentional, and is the way that
* the protocol is supposed to work.
*/
if (pkt->m == &proxy_module) {
from_frontend = FALSE;
} else {
from_frontend = TRUE;
}
/* Create and dispatch cmd_recs for frontend/backend SSH packets, in order to
* support ExtendedLog logging.
*/
proxy_ssh_packet_log_cmd(pkt, from_frontend);
switch (msg_type) {
case PROXY_SSH_MSG_DEBUG:
if (proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_KEX) {
proxy_ssh_packet_proxied(proxy_sess, pkt, from_frontend);
} else {
(void) proxy_ssh_packet_get_msg_type(pkt);
proxy_ssh_packet_handle_debug(pkt);
}
break;
case PROXY_SSH_MSG_DISCONNECT:
if (proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_KEX) {
proxy_ssh_packet_proxied(proxy_sess, pkt, from_frontend);
} else {
(void) proxy_ssh_packet_get_msg_type(pkt);
proxy_ssh_packet_handle_disconnect(pkt);
}
break;
case PROXY_SSH_MSG_GLOBAL_REQUEST:
handle_global_request_msg(pkt, proxy_sess, from_frontend);
break;
case PROXY_SSH_MSG_REQUEST_SUCCESS:
case PROXY_SSH_MSG_REQUEST_FAILURE:
if (proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_KEX) {
proxy_ssh_packet_proxied(proxy_sess, pkt, from_frontend);
} else {
(void) proxy_ssh_packet_get_msg_type(pkt);
handle_server_alive_msg(pkt, msg_type);
}
break;
case PROXY_SSH_MSG_IGNORE:
if (proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_KEX) {
proxy_ssh_packet_proxied(proxy_sess, pkt, from_frontend);
} else {
(void) proxy_ssh_packet_get_msg_type(pkt);
proxy_ssh_packet_handle_ignore(pkt);
}
break;
case PROXY_SSH_MSG_UNIMPLEMENTED:
if (proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_KEX) {
proxy_ssh_packet_proxied(proxy_sess, pkt, from_frontend);
} else {
(void) proxy_ssh_packet_get_msg_type(pkt);
proxy_ssh_packet_handle_unimplemented(pkt);
}
break;
case PROXY_SSH_MSG_KEXINIT: {
uint64_t start_ms = 0;
if (from_frontend == TRUE) {
/* We should never see a frontend KEXINIT packet, except when the
* frontend client has requested a rekey; we do NOT want to interact
* with the backend anymore for this event.
*
* In addition, we need a way to get mod_sftp to handle this packet,
* and the rest of the KEX. Fun.
*/
return handle_frontend_rekey(pkt, proxy_sess);
}
(void) proxy_ssh_packet_get_msg_type(pkt);
if (pr_trace_get_level(timing_channel) > 0) {
pr_gettimeofday_millis(&start_ms);
}
if (proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_KEX) {
/* The server might be initiating a rekey; watch for this. We
* should not be receiving frontend KEXINIT packets here.
*/
if (from_frontend == FALSE) {
if (proxy_sess_state & PROXY_SESS_STATE_SSH_REKEYING) {
pr_trace_msg(trace_channel, 17,
"rekeying already in effect, ignoring rekey request");
break;
}
/* Reinitialize the KEX API for another rekeying. */
proxy_ssh_kex_init(session.pool, NULL, NULL);
}
} else {
if (pr_trace_get_level(timing_channel)) {
unsigned long elapsed_ms;
uint64_t finish_ms;
pr_gettimeofday_millis(&finish_ms);
elapsed_ms = (unsigned long) (finish_ms - session.connect_time_ms);
pr_trace_msg(timing_channel, 4,
"Time before first SSH key exchange: %lu ms", elapsed_ms);
}
}
proxy_sess_state |= PROXY_SESS_STATE_SSH_REKEYING;
/* Clear any current "have KEX" state. */
proxy_sess_state &= ~PROXY_SESS_STATE_SSH_HAVE_KEX;
if (proxy_ssh_kex_handle(pkt, proxy_sess) < 0) {
PROXY_SSH_DISCONNECT_CONN(proxy_sess->backend_ctrl_conn,
PROXY_SSH_DISCONNECT_BY_APPLICATION, NULL);
}
proxy_sess_state |= PROXY_SESS_STATE_SSH_HAVE_KEX;
if (pr_trace_get_level(timing_channel)) {
unsigned long elapsed_ms;
uint64_t finish_ms;
pr_gettimeofday_millis(&finish_ms);
elapsed_ms = (unsigned long) (finish_ms - start_ms);
pr_trace_msg(timing_channel, 4,
"SSH key exchange duration: %lu ms", elapsed_ms);
}
if (proxy_sess_state & PROXY_SESS_STATE_SSH_REKEYING) {
proxy_sess_state &= ~PROXY_SESS_STATE_SSH_REKEYING;
}
break;
}
case PROXY_SSH_MSG_EXT_INFO:
/* We expect any possible EXT_INFO message after NEWKEYS, and before
* anything else.
*/
if ((proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_KEX) &&
!(proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_SERVICE) &&
!(proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_EXT_INFO)) {
(void) proxy_ssh_packet_get_msg_type(pkt);
proxy_ssh_packet_handle_ext_info(pkt);
proxy_sess_state |= PROXY_SESS_STATE_SSH_HAVE_EXT_INFO;
break;
} else {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to handle %s (%d) message: wrong message order",
proxy_ssh_packet_get_msg_type_desc(msg_type), msg_type);
PROXY_SSH_DISCONNECT_CONN(proxy_sess->backend_ctrl_conn,
PROXY_SSH_DISCONNECT_BY_APPLICATION, "Unsupported protocol sequence");
}
break;
case PROXY_SSH_MSG_SERVICE_REQUEST:
if (proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_KEX) {
if (proxy_ssh_service_handle(pkt, proxy_sess) == 0) {
proxy_sess_state |= PROXY_SESS_STATE_SSH_HAVE_SERVICE;
}
} else {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to handle %s (%d) message: Key exchange required",
proxy_ssh_packet_get_msg_type_desc(msg_type), msg_type);
PROXY_SSH_DISCONNECT_CONN(proxy_sess->backend_ctrl_conn,
PROXY_SSH_DISCONNECT_BY_APPLICATION, "Unsupported protocol sequence");
}
break;
case PROXY_SSH_MSG_USER_AUTH_INFO_RESP:
case PROXY_SSH_MSG_USER_AUTH_REQUEST:
if (proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_SERVICE) {
/* If the client has already authenticated this connection, then
* silently ignore this additional auth request, per recommendation
* in RFC4252.
*/
if (proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_AUTH) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"ignoring %s (%d) message: Connection already authenticated",
proxy_ssh_packet_get_msg_type_desc(msg_type), msg_type);
} else {
int ok;
ok = proxy_ssh_auth_handle(pkt, proxy_sess);
if (ok == 1) {
proxy_sess_state |= PROXY_SESS_STATE_SSH_HAVE_AUTH;
} else if (ok < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error handling SSH authentication: %s", strerror(errno));
PROXY_SSH_DISCONNECT_CONN(proxy_sess->frontend_ctrl_conn,
PROXY_SSH_DISCONNECT_BY_APPLICATION, NULL);
}
}
} else {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to handle %s (%d) message: Service request required",
proxy_ssh_packet_get_msg_type_desc(msg_type), msg_type);
PROXY_SSH_DISCONNECT_CONN(proxy_sess->backend_ctrl_conn,
PROXY_SSH_DISCONNECT_BY_APPLICATION, "Unsupported protocol sequence");
}
break;
case PROXY_SSH_MSG_CHANNEL_OPEN:
case PROXY_SSH_MSG_CHANNEL_OPEN_CONFIRMATION:
case PROXY_SSH_MSG_CHANNEL_OPEN_FAILURE:
case PROXY_SSH_MSG_CHANNEL_REQUEST:
case PROXY_SSH_MSG_CHANNEL_DATA:
case PROXY_SSH_MSG_CHANNEL_EXTENDED_DATA:
case PROXY_SSH_MSG_CHANNEL_EOF:
case PROXY_SSH_MSG_CHANNEL_FAILURE:
case PROXY_SSH_MSG_CHANNEL_SUCCESS:
case PROXY_SSH_MSG_CHANNEL_WINDOW_ADJUST:
if (proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_AUTH) {
(void) pr_timer_reset(PR_TIMER_NOXFER, ANY_MODULE);
proxy_ssh_packet_proxied(proxy_sess, pkt, from_frontend);
} else {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to handle %s (%d) message: User authentication required",
proxy_ssh_packet_get_msg_type_desc(msg_type), msg_type);
PROXY_SSH_DISCONNECT_CONN(proxy_sess->backend_ctrl_conn,
PROXY_SSH_DISCONNECT_BY_APPLICATION, "Unsupported protocol sequence");
}
break;
case PROXY_SSH_MSG_CHANNEL_CLOSE:
if (proxy_sess_state & PROXY_SESS_STATE_SSH_HAVE_AUTH) {
proxy_ssh_packet_proxied(proxy_sess, pkt, from_frontend);
} else {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to handle %s (%d) message: User authentication required",
proxy_ssh_packet_get_msg_type_desc(msg_type), msg_type);
PROXY_SSH_DISCONNECT_CONN(proxy_sess->backend_ctrl_conn,
PROXY_SSH_DISCONNECT_BY_APPLICATION, "Unsupported protocol sequence");
}
break;
default:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unhandled %s (%d) message, disconnecting",
proxy_ssh_packet_get_msg_type_desc(msg_type), msg_type);
PROXY_SSH_DISCONNECT_CONN(proxy_sess->backend_ctrl_conn,
PROXY_SSH_DISCONNECT_BY_APPLICATION, "Unsupported protocol sequence");
}
return 0;
}
int proxy_ssh_packet_process(pool *p, const struct proxy_session *proxy_sess) {
struct proxy_ssh_packet *pkt;
int res;
pkt = proxy_ssh_packet_create(p);
res = proxy_ssh_packet_read(proxy_sess->backend_ctrl_conn, pkt);
if (res < 0) {
/* An ETIMEDOUT error here usually means that the read poll timed out;
* the backend host did not have any data for us, which is OK. Right?
*/
if (errno != ETIMEDOUT) {
PROXY_SSH_DISCONNECT_CONN(proxy_sess->backend_ctrl_conn,
PROXY_SSH_DISCONNECT_BY_APPLICATION, NULL);
}
}
pr_response_clear(&resp_list);
pr_response_clear(&resp_err_list);
pr_response_set_pool(pkt->pool);
proxy_ssh_packet_handle(pkt);
pr_response_set_pool(NULL);
return 0;
}
int proxy_ssh_packet_set_frontend_packet_handle(pool *p,
int (*packet_handle)(void *pkt)) {
const char *hook_symbol;
cmdtable *sftp_cmdtab;
cmd_rec *cmd;
modret_t *result;
hook_symbol = "sftp_set_packet_handler";
sftp_cmdtab = pr_stash_get_symbol2(PR_SYM_HOOK, hook_symbol, NULL, NULL,
NULL);
if (sftp_cmdtab == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to find SFTP hook symbol '%s'", hook_symbol);
errno = ENOENT;
return -1;
}
cmd = pr_cmd_alloc(p, 1, NULL);
cmd->argv[0] = (void *) packet_handle;
result = pr_module_call(sftp_cmdtab->m, sftp_cmdtab->handler, cmd);
if (result == NULL ||
MODRET_ISERROR(result)) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error setting Proxy SSH packet handler");
errno = EPERM;
return -1;
}
return 0;
}
void proxy_ssh_packet_set_frontend_packet_write(int (*packet_write)(int, void *)) {
frontend_packet_write = packet_write;
}
int proxy_ssh_packet_send_version(conn_t *conn) {
if (sent_version_id == FALSE) {
int res;
size_t version_len;
version_len = strlen(version_id);
res = write(conn->wfd, version_id, version_len);
while (res < 0) {
if (errno == EINTR) {
pr_signals_handle();
res = write(conn->wfd, version_id, version_len);
continue;
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error sending version to server wfd %d: %s", conn->wfd,
strerror(errno));
return res;
}
sent_version_id = TRUE;
session.total_raw_out += res;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"sent client version '%s'", client_version);
}
return 0;
}
uint32_t proxy_ssh_packet_get_server_seqno(void) {
return packet_server_seqno;
}
void proxy_ssh_packet_reset_client_seqno(void) {
packet_client_seqno = 0;
}
void proxy_ssh_packet_reset_server_seqno(void) {
packet_server_seqno = 0;
}
int proxy_ssh_packet_set_version(const char *version) {
if (client_version == NULL) {
errno = EINVAL;
return -1;
}
client_version = version;
version_id = pstrcat(proxy_pool, version, "\r\n", NULL);
return 0;
}
#endif /* PR_USE_OPENSSL */
proftpd-mod_proxy-0.9.5/lib/proxy/ssh/redis.c 0000664 0000000 0000000 00000017033 14757370167 0021244 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH Redis implementation
* Copyright (c) 2022 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "redis.h"
#include "proxy/ssh.h"
#include "proxy/ssh/crypto.h"
#include "proxy/ssh/redis.h"
#if defined(PR_USE_OPENSSL)
#include
extern xaset_t *server_list;
static const char *trace_channel = "proxy.ssh.redis";
static void *redis_prefix = NULL;
static size_t redis_prefixsz = 0;
static unsigned long redis_opts = 0UL;
static const char *redis_algo_field = "algo";
static const char *redis_blob_field = "blob";
static char *make_key(pool *p, const char *backend_uri) {
char *key;
size_t keysz;
keysz = strlen(backend_uri) + 64;
key = pcalloc(p, keysz + 1);
snprintf(key, keysz, "proxy_ssh_hostkeys:%s", backend_uri);
return key;
}
static int ssh_redis_update_hostkey(pool *p, void *dsh, unsigned int vhost_id,
const char *backend_uri, const char *algo,
const unsigned char *hostkey_data, uint32_t hostkey_datalen) {
int res, xerrno = 0;
pool *tmp_pool;
pr_redis_t *redis;
char *key, *data = NULL;
long datalen = 0;
size_t field_len;
redis = dsh;
tmp_pool = make_sub_pool(p);
data = palloc(tmp_pool, (2 * hostkey_datalen) + 1);
datalen = EVP_EncodeBlock((unsigned char *) data, hostkey_data,
(int) hostkey_datalen);
if (datalen == 0) {
pr_trace_msg(trace_channel, 3,
"error base640-encoding hostkey data: %s", proxy_ssh_crypto_get_errors());
destroy_pool(tmp_pool);
return 0;
}
key = make_key(tmp_pool, backend_uri);
field_len = strlen(algo);
res = pr_redis_hash_set(redis, &proxy_module, key, redis_algo_field,
(void *) algo, field_len);
xerrno = errno;
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error setting value for field '%s' in Redis hash '%s': %s",
redis_algo_field, key, strerror(xerrno));
destroy_pool(tmp_pool);
errno = xerrno;
return -1;
}
field_len = datalen;
res = pr_redis_hash_set(redis, &proxy_module, key, redis_blob_field,
(void *) data, field_len);
xerrno = errno;
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error setting value for field '%s' in Redis hash '%s': %s",
redis_blob_field, key, strerror(xerrno));
destroy_pool(tmp_pool);
errno = xerrno;
return -1;
}
destroy_pool(tmp_pool);
return 0;
}
static const unsigned char *ssh_redis_get_hostkey(pool *p, void *dsh,
unsigned int vhost_id, const char *backend_uri, const char **algo,
uint32_t *hostkey_datalen) {
int have_padding = FALSE, res, xerrno;
pool *tmp_pool;
pr_redis_t *redis;
pr_table_t *hostkey_tab;
char *key;
void *data = NULL;
const unsigned char *hostkey_data = NULL;
size_t blocklen = 0, datalen = 0, rem;
redis = dsh;
tmp_pool = make_sub_pool(p);
key = make_key(tmp_pool, backend_uri);
res = pr_redis_hash_getall(tmp_pool, redis, &proxy_module, key, &hostkey_tab);
xerrno = errno;
if (res < 0) {
if (xerrno != ENOENT) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error getting hash from Redis '%s': %s", key, strerror(xerrno));
}
destroy_pool(tmp_pool);
errno = xerrno;
return NULL;
}
if (hostkey_tab == NULL) {
destroy_pool(tmp_pool);
errno = ENOENT;
return NULL;
}
data = (void *) pr_table_kget(hostkey_tab, redis_algo_field,
strlen(redis_algo_field), &datalen);
if (data != NULL) {
*algo = pstrndup(p, data, datalen);
}
data = (void *) pr_table_kget(hostkey_tab, redis_blob_field,
strlen(redis_blob_field), &datalen);
if (data == NULL) {
pr_trace_msg(trace_channel, 3, "%s",
"missing base64-decoding hostkey data from Redis, skipping");
destroy_pool(tmp_pool);
errno = ENOENT;
return NULL;
}
/* Due to Base64's padding, we need to detect if the last block was padded
* with zeros; we do this by looking for '=' characters at the end of the
* text being decoded. If we see these characters, then we will "trim" off
* any trailing zero values in the decoded data, on the ASSUMPTION that they
* are the auto-added padding bytes.
*/
if (((char *) data)[datalen-1] == '=') {
have_padding = TRUE;
}
blocklen = datalen;
/* Ensure that the output buffer is divisible by 4, per OpenSSL
* requirements.
*/
rem = blocklen % 4;
if (rem != 0) {
blocklen += rem;
}
hostkey_data = pcalloc(p, blocklen);
res = EVP_DecodeBlock((unsigned char *) hostkey_data, (unsigned char *) data,
(int) datalen);
if (res <= 0) {
pr_trace_msg(trace_channel, 3,
"error base64-decoding hostkey data: %s", proxy_ssh_crypto_get_errors());
destroy_pool(tmp_pool);
errno = EINVAL;
return NULL;
}
if (have_padding == TRUE) {
/* Assume that only one or two zero bytes of padding were added. */
if (hostkey_data[res-1] == '\0') {
res -= 1;
if (hostkey_data[res-1] == '\0') {
res -= 1;
}
}
}
*hostkey_datalen = res;
pr_trace_msg(trace_channel, 19,
"retrieved hostkey (algo '%s', %lu bytes) for vhost ID %u, URI '%s'",
*algo, (unsigned long) *hostkey_datalen, vhost_id, backend_uri);
return hostkey_data;
}
/* Initialization routines */
static int ssh_redis_init(pool *p, const char *tables_path, int flags) {
/* We currently don't need to do anything, at init time, to any existing
* SSH Redis keys.
*/
return 0;
}
static int ssh_redis_close(pool *p, void *redis) {
if (redis != NULL) {
if (pr_redis_conn_close(redis) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error closing Redis connection: %s", strerror(errno));
}
}
return 0;
}
static void *ssh_redis_open(pool *p, const char *tables_dir,
unsigned long opts) {
int xerrno = 0;
pr_redis_t *redis;
redis = pr_redis_conn_new(p, &proxy_module, 0);
xerrno = errno;
if (redis == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error opening Redis connection: %s", strerror(xerrno));
errno = xerrno;
return NULL;
}
(void) pr_redis_conn_set_namespace(redis, &proxy_module, redis_prefix,
redis_prefixsz);
redis_opts = opts;
return redis;
}
int proxy_ssh_redis_as_datastore(struct proxy_ssh_datastore *ds, void *ds_data,
size_t ds_datasz) {
if (ds == NULL) {
errno = EINVAL;
return -1;
}
redis_prefix = ds_data;
redis_prefixsz = ds_datasz;
ds->hostkey_add = ssh_redis_update_hostkey;
ds->hostkey_get = ssh_redis_get_hostkey;
ds->hostkey_update = ssh_redis_update_hostkey;
ds->init = ssh_redis_init;
ds->open = ssh_redis_open;
ds->close = ssh_redis_close;
return 0;
}
#endif /* PR_USE_OPENSSL */
proftpd-mod_proxy-0.9.5/lib/proxy/ssh/service.c 0000664 0000000 0000000 00000007220 14757370167 0021573 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH service
* Copyright (c) 2021-2022 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "proxy/ssh/ssh2.h"
#include "proxy/ssh/packet.h"
#include "proxy/ssh/service.h"
#if defined(PR_USE_OPENSSL)
static const char *trace_channel = "proxy.ssh.service";
int proxy_ssh_service_handle(struct proxy_ssh_packet *pkt,
const struct proxy_session *proxy_sess) {
int poll_timeout_secs, res, xerrno = 0;
unsigned int poll_attempts;
unsigned long poll_timeout_ms;
char msg_type;
res = proxy_ssh_packet_write(proxy_sess->backend_ctrl_conn, pkt);
if (res < 0) {
destroy_pool(pkt->pool);
return -1;
}
destroy_pool(pkt->pool);
proxy_ssh_packet_get_poll_attempts(&poll_attempts);
proxy_ssh_packet_get_poll_timeout(&poll_timeout_secs, &poll_timeout_ms);
proxy_ssh_packet_set_poll_attempts(3);
proxy_ssh_packet_set_poll_timeout(0, 250);
while (TRUE) {
pr_signals_handle();
pkt = proxy_ssh_packet_create(proxy_pool);
res = proxy_ssh_packet_read(proxy_sess->backend_ctrl_conn, pkt);
if (res < 0) {
xerrno = errno;
destroy_pool(pkt->pool);
proxy_ssh_packet_set_poll_attempts(poll_attempts);
proxy_ssh_packet_set_poll_timeout(poll_timeout_secs, poll_timeout_ms);
errno = xerrno;
return -1;
}
msg_type = proxy_ssh_packet_peek_msg_type(pkt);
pr_trace_msg(trace_channel, 3, "received %s (%d) packet (from mod_%s.c)",
proxy_ssh_packet_get_msg_type_desc(msg_type), msg_type,
pkt->m->name);
/* Be sure to handle the messages that can come at any time as well. */
switch (msg_type) {
case PROXY_SSH_MSG_SERVICE_ACCEPT:
/* Expected */
break;
case PROXY_SSH_MSG_DEBUG:
case PROXY_SSH_MSG_DISCONNECT:
case PROXY_SSH_MSG_EXT_INFO:
case PROXY_SSH_MSG_IGNORE:
case PROXY_SSH_MSG_UNIMPLEMENTED:
proxy_ssh_packet_handle(pkt);
continue;
default:
proxy_ssh_packet_set_poll_attempts(poll_attempts);
proxy_ssh_packet_set_poll_timeout(poll_timeout_secs, poll_timeout_ms);
destroy_pool(pkt->pool);
/* Invalid protocol sequence */
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"received unexpected %s packet during SSH service setup, failing",
proxy_ssh_packet_get_msg_type_desc(msg_type));
errno = ENOSYS;
return -1;
}
break;
}
proxy_ssh_packet_set_poll_attempts(poll_attempts);
proxy_ssh_packet_set_poll_timeout(poll_timeout_secs, poll_timeout_ms);
proxy_ssh_packet_log_cmd(pkt, FALSE);
res = proxy_ssh_packet_proxied(proxy_sess, pkt, FALSE);
xerrno = errno;
destroy_pool(pkt->pool);
errno = xerrno;
return res;
}
#endif /* PR_USE_OPENSSL */
proftpd-mod_proxy-0.9.5/lib/proxy/ssh/session.c 0000664 0000000 0000000 00000004101 14757370167 0021611 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH session
* Copyright (c) 2021 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "proxy/ssh/session.h"
#if defined(PR_USE_OPENSSL)
#include
static unsigned char *session_id = NULL;
static uint32_t session_idlen = 0;
uint32_t proxy_ssh_session_get_id(const unsigned char **buf) {
if (session_id != NULL) {
*buf = session_id;
return session_idlen;
}
return 0;
}
int proxy_ssh_session_set_id(pool *p, const unsigned char *hash,
uint32_t hashlen) {
/* The session ID is only set once, regardless of how many times
* (re)keying occurs during the course of a session.
*/
if (session_id == NULL) {
session_id = palloc(p, hashlen);
memcpy(session_id, hash, hashlen);
session_idlen = hashlen;
#if OPENSSL_VERSION_NUMBER >= 0x000905000L
/* Since the session ID contains unknown information from the client,
* it can be used as a source of additional entropy. The amount
* of entropy is a rough guess.
*/
RAND_add(hash, hashlen, hashlen * 0.5);
#endif
return 0;
}
errno = EEXIST;
return -1;
}
#endif /* PR_USE_OPENSSL */
proftpd-mod_proxy-0.9.5/lib/proxy/ssh/umac.c 0000664 0000000 0000000 00000131612 14757370167 0021063 0 ustar 00root root 0000000 0000000 /* -----------------------------------------------------------------------
*
* umac.c -- C Implementation UMAC Message Authentication
*
* Version 0.93b of rfc4418.txt -- 2006 July 18
*
* For a full description of UMAC message authentication see the UMAC
* world-wide-web page at http://www.cs.ucdavis.edu/~rogaway/umac
* Please report bugs and suggestions to the UMAC webpage.
*
* Copyright (c) 1999-2006 Ted Krovetz
*
* Permission to use, copy, modify, and distribute this software and
* its documentation for any purpose and with or without fee, is hereby
* granted provided that the above copyright notice appears in all copies
* and in supporting documentation, and that the name of the copyright
* holder not be used in advertising or publicity pertaining to
* distribution of the software without specific, written prior permission.
*
* Comments should be directed to Ted Krovetz (tdk@acm.org)
*
* ---------------------------------------------------------------------- */
/* ////////////////////// IMPORTANT NOTES /////////////////////////////////
*
* 1) This version does not work properly on messages larger than 16MB
*
* 2) If you set the switch to use SSE2, then all data must be 16-byte
* aligned
*
* 3) When calling the function umac(), it is assumed that msg is in
* a writable buffer of length divisible by 32 bytes. The message itself
* does not have to fill the entire buffer, but bytes beyond msg may be
* zeroed.
*
* 4) Three free AES implementations are supported by this implementation of
* UMAC. Paulo Barreto's version is in the public domain and can be found
* at http://www.esat.kuleuven.ac.be/~rijmen/rijndael/ (search for
* "Barreto"). The only two files needed are rijndael-alg-fst.c and
* rijndael-alg-fst.h. Brian Gladman's version is distributed with the GNU
* Public Licence at http://fp.gladman.plus.com/AES/index.htm. It
* includes a fast IA-32 assembly version. The OpenSSL crypo library is
* the third.
*
* 5) With FORCE_C_ONLY flags set to 0, incorrect results are sometimes
* produced under gcc with optimizations set -O3 or higher. Dunno why.
*
/////////////////////////////////////////////////////////////////////// */
/* ---------------------------------------------------------------------- */
/* --- User Switches ---------------------------------------------------- */
/* ---------------------------------------------------------------------- */
#ifndef UMAC_OUTPUT_LEN
# define UMAC_OUTPUT_LEN 8 /* Alowable: 4, 8, 12, 16 */
#endif
#if UMAC_OUTPUT_LEN != 4 && UMAC_OUTPUT_LEN != 8 && \
UMAC_OUTPUT_LEN != 12 && UMAC_OUTPUT_LEN != 16
# error UMAC_OUTPUT_LEN must be defined to 4, 8, 12 or 16
#endif
/* #define FORCE_C_ONLY 1 ANSI C and 64-bit integers req'd */
/* #define AES_IMPLEMENTAION 1 1 = OpenSSL, 2 = Barreto, 3 = Gladman */
/* #define SSE2 0 Is SSE2 is available? */
/* #define RUN_TESTS 0 Run basic correctness/speed tests */
/* #define UMAC_AE_SUPPORT 0 Enable auhthenticated encryption */
/* ---------------------------------------------------------------------- */
/* -- Global Includes --------------------------------------------------- */
/* ---------------------------------------------------------------------- */
#include "mod_proxy.h"
#include "proxy/ssh/umac.h"
#if defined(PR_USE_OPENSSL)
# include
# include
#endif /* PR_USE_OPENSSL */
#include
#include
#include
#if OPENSSL_VERSION_NUMBER > 0x000907000L
/* ---------------------------------------------------------------------- */
/* --- Primitive Data Types --- */
/* ---------------------------------------------------------------------- */
/* The following assumptions may need change on your system */
typedef uint8_t UINT8; /* 1 byte */
typedef uint16_t UINT16; /* 2 byte */
typedef uint32_t UINT32; /* 4 byte */
typedef uint64_t UINT64; /* 8 bytes */
typedef unsigned int UWORD; /* Register */
/* ---------------------------------------------------------------------- */
/* --- Constants -------------------------------------------------------- */
/* ---------------------------------------------------------------------- */
#define UMAC_KEY_LEN 16 /* UMAC takes 16 bytes of external key */
/* Message "words" are read from memory in an endian-specific manner. */
/* For this implementation to behave correctly, __LITTLE_ENDIAN__ must */
/* be set true if the host computer is little-endian. */
#if BYTE_ORDER == LITTLE_ENDIAN
#define __LITTLE_ENDIAN__ 1
#else
#define __LITTLE_ENDIAN__ 0
#endif
/* ---------------------------------------------------------------------- */
/* ---------------------------------------------------------------------- */
/* ----- Architecture Specific ------------------------------------------ */
/* ---------------------------------------------------------------------- */
/* ---------------------------------------------------------------------- */
/* ---------------------------------------------------------------------- */
/* ---------------------------------------------------------------------- */
/* ----- Primitive Routines --------------------------------------------- */
/* ---------------------------------------------------------------------- */
/* ---------------------------------------------------------------------- */
/* ---------------------------------------------------------------------- */
/* --- 32-bit by 32-bit to 64-bit Multiplication ------------------------ */
/* ---------------------------------------------------------------------- */
#define MUL64(a,b) ((UINT64)((UINT64)(UINT32)(a) * (UINT64)(UINT32)(b)))
/* ---------------------------------------------------------------------- */
/* --- Endian Conversion --- Forcing assembly on some platforms */
/* ---------------------------------------------------------------------- */
#if defined(HAVE_SWAP32)
#define LOAD_UINT32_REVERSED(p) (swap32(*(UINT32 *)(p)))
#define STORE_UINT32_REVERSED(p,v) (*(UINT32 *)(p) = swap32(v))
#else /* HAVE_SWAP32 */
static UINT32 LOAD_UINT32_REVERSED(void *ptr)
{
UINT32 temp = *(UINT32 *)ptr;
temp = (temp >> 24) | ((temp & 0x00FF0000) >> 8 )
| ((temp & 0x0000FF00) << 8 ) | (temp << 24);
return (UINT32)temp;
}
# if (__LITTLE_ENDIAN__)
static void STORE_UINT32_REVERSED(void *ptr, UINT32 x)
{
UINT32 i = (UINT32)x;
*(UINT32 *)ptr = (i >> 24) | ((i & 0x00FF0000) >> 8 )
| ((i & 0x0000FF00) << 8 ) | (i << 24);
}
# endif /* __LITTLE_ENDIAN */
#endif /* HAVE_SWAP32 */
/* The following definitions use the above reversal-primitives to do the right
* thing on endian specific load and stores.
*/
#if (__LITTLE_ENDIAN__)
#define LOAD_UINT32_LITTLE(ptr) (*(UINT32 *)(ptr))
#define STORE_UINT32_BIG(ptr,x) STORE_UINT32_REVERSED(ptr,x)
#else
#define LOAD_UINT32_LITTLE(ptr) LOAD_UINT32_REVERSED(ptr)
#define STORE_UINT32_BIG(ptr,x) (*(UINT32 *)(ptr) = (UINT32)(x))
#endif
/* ---------------------------------------------------------------------- */
/* ---------------------------------------------------------------------- */
/* ----- Begin KDF & PDF Section ---------------------------------------- */
/* ---------------------------------------------------------------------- */
/* ---------------------------------------------------------------------- */
/* UMAC uses AES with 16 byte block and key lengths */
#define AES_BLOCK_LEN 16
/* OpenSSL's AES */
typedef AES_KEY aes_int_key[1];
#define aes_encryption(in,out,int_key) \
AES_encrypt((unsigned char *)(in),(unsigned char *)(out),(AES_KEY *)int_key)
#define aes_key_setup(key,int_key) \
AES_set_encrypt_key((unsigned char *)(key),UMAC_KEY_LEN*8,int_key)
/* The user-supplied UMAC key is stretched using AES in a counter
* mode to supply all random bits needed by UMAC. The kdf function takes
* an AES internal key representation 'key' and writes a stream of
* 'nbytes' bytes to the memory pointed at by 'bufp'. Each distinct
* 'ndx' causes a distinct byte stream.
*/
static void kdf(void *bufp, aes_int_key key, UINT8 ndx, int nbytes)
{
UINT8 in_buf[AES_BLOCK_LEN] = {0};
UINT8 out_buf[AES_BLOCK_LEN];
UINT8 *dst_buf = (UINT8 *)bufp;
int i;
/* Setup the initial value */
in_buf[AES_BLOCK_LEN-9] = ndx;
in_buf[AES_BLOCK_LEN-1] = i = 1;
while (nbytes >= AES_BLOCK_LEN) {
aes_encryption(in_buf, out_buf, key);
memcpy(dst_buf,out_buf,AES_BLOCK_LEN);
in_buf[AES_BLOCK_LEN-1] = ++i;
nbytes -= AES_BLOCK_LEN;
dst_buf += AES_BLOCK_LEN;
}
if (nbytes) {
aes_encryption(in_buf, out_buf, key);
memcpy(dst_buf,out_buf,nbytes);
}
#if defined(HAVE_MEMSET_S)
memset_s(in_buf, sizeof(in_buf), 0, sizeof(in_buf));
memset_s(out_buf, sizeof(out_buf), 0, sizeof(out_buf));
#endif /* HAVE_MEMSET_S */
}
/* The final UHASH result is XOR'd with the output of a pseudorandom
* function. Here, we use AES to generate random output and
* xor the appropriate bytes depending on the last bits of nonce.
* This scheme is optimized for sequential, increasing big-endian nonces.
*/
typedef struct {
UINT8 cache[AES_BLOCK_LEN]; /* Previous AES output is saved */
UINT8 nonce[AES_BLOCK_LEN]; /* The AES input making above cache */
aes_int_key prf_key; /* Expanded AES key for PDF */
} pdf_ctx;
static void pdf_init(pdf_ctx *pc, aes_int_key prf_key)
{
UINT8 buf[UMAC_KEY_LEN];
kdf(buf, prf_key, 0, UMAC_KEY_LEN);
aes_key_setup(buf, pc->prf_key);
/* Initialize pdf and cache */
memset(pc->nonce, 0, sizeof(pc->nonce));
aes_encryption(pc->nonce, pc->cache, pc->prf_key);
#if defined(HAVE_MEMSET_S)
memset_s(buf, sizeof(buf), 0, sizeof(buf));
#endif /* HAVE_MEMSET_S */
}
static void pdf_gen_xor(pdf_ctx *pc, const UINT8 nonce[8], UINT8 buf[8])
{
/* 'ndx' indicates that we'll be using the 0th or 1st eight bytes
* of the AES output. If last time around we returned the ndx-1st
* element, then we may have the result in the cache already.
*/
#if (UMAC_OUTPUT_LEN == 4)
#define LOW_BIT_MASK 3
#elif (UMAC_OUTPUT_LEN == 8)
#define LOW_BIT_MASK 1
#elif (UMAC_OUTPUT_LEN > 8)
#define LOW_BIT_MASK 0
#endif
union {
UINT8 tmp_nonce_lo[4];
UINT32 align;
} t;
#if LOW_BIT_MASK != 0
int ndx = nonce[7] & LOW_BIT_MASK;
#endif
/* ProFTPD Note: We've changed the original code where, which used explicit
* typecasting to treat the nonce[8] as a UINT32, to memcpy(3)/memcmp(3),
* to avoid strict aliasing gcc warnings when -O2 or higher is used.
*/
memcpy(t.tmp_nonce_lo, &(nonce[4]), sizeof(UINT32));
t.tmp_nonce_lo[3] &= ~LOW_BIT_MASK; /* zero last bit */
if ( (memcmp(t.tmp_nonce_lo, &(pc->nonce[4]), sizeof(UINT32)) != 0) ||
(memcmp(nonce, pc->nonce, sizeof(UINT32)) != 0) )
{
memmove(pc->nonce, nonce, sizeof(UINT32));
memmove(&(pc->nonce[4]), t.tmp_nonce_lo, sizeof(UINT32));
aes_encryption(pc->nonce, pc->cache, pc->prf_key);
}
#if (UMAC_OUTPUT_LEN == 4)
*((UINT32 *)buf) ^= ((UINT32 *)pc->cache)[ndx];
#elif (UMAC_OUTPUT_LEN == 8)
*((UINT64 *)buf) ^= ((UINT64 *)pc->cache)[ndx];
#elif (UMAC_OUTPUT_LEN == 12)
((UINT64 *)buf)[0] ^= ((UINT64 *)pc->cache)[0];
((UINT32 *)buf)[2] ^= ((UINT32 *)pc->cache)[2];
#elif (UMAC_OUTPUT_LEN == 16)
((UINT64 *)buf)[0] ^= ((UINT64 *)pc->cache)[0];
((UINT64 *)buf)[1] ^= ((UINT64 *)pc->cache)[1];
#endif
}
/* ---------------------------------------------------------------------- */
/* ---------------------------------------------------------------------- */
/* ----- Begin NH Hash Section ------------------------------------------ */
/* ---------------------------------------------------------------------- */
/* ---------------------------------------------------------------------- */
/* The NH-based hash functions used in UMAC are described in the UMAC paper
* and specification, both of which can be found at the UMAC website.
* The interface to this implementation has two
* versions, one expects the entire message being hashed to be passed
* in a single buffer and returns the hash result immediately. The second
* allows the message to be passed in a sequence of buffers. In the
* muliple-buffer interface, the client calls the routine nh_update() as
* many times as necessary. When there is no more data to be fed to the
* hash, the client calls nh_final() which calculates the hash output.
* Before beginning another hash calculation the nh_reset() routine
* must be called. The single-buffer routine, nh(), is equivalent to
* the sequence of calls nh_update() and nh_final(); however it is
* optimized and should be preferred whenever the multiple-buffer interface
* is not necessary. When using either interface, it is the client's
* responsibility to pass no more than L1_KEY_LEN bytes per hash result.
*
* The routine nh_init() initializes the nh_ctx data structure and
* must be called once, before any other PDF routine.
*/
/* The "nh_aux" routines do the actual NH hashing work. They
* expect buffers to be multiples of L1_PAD_BOUNDARY. These routines
* produce output for all STREAMS NH iterations in one call,
* allowing the parallel implementation of the streams.
*/
#define STREAMS (UMAC_OUTPUT_LEN / 4) /* Number of times hash is applied */
#define L1_KEY_LEN 1024 /* Internal key bytes */
#define L1_KEY_SHIFT 16 /* Toeplitz key shift between streams */
#define L1_PAD_BOUNDARY 32 /* pad message to boundary multiple */
#define ALLOC_BOUNDARY 16 /* Keep buffers aligned to this */
#define HASH_BUF_BYTES 64 /* nh_aux_hb buffer multiple */
typedef struct {
UINT8 nh_key [L1_KEY_LEN + L1_KEY_SHIFT * (STREAMS - 1)]; /* NH Key */
UINT8 data [HASH_BUF_BYTES]; /* Incoming data buffer */
int next_data_empty; /* Bookkeeping variable for data buffer. */
int bytes_hashed; /* Bytes (out of L1_KEY_LEN) incorporated. */
UINT64 state[STREAMS]; /* on-line state */
} nh_ctx;
#if (UMAC_OUTPUT_LEN == 4)
static void nh_aux(void *kp, const void *dp, void *hp, UINT32 dlen)
/* NH hashing primitive. Previous (partial) hash result is loaded and
* then stored via hp pointer. The length of the data pointed at by "dp",
* "dlen", is guaranteed to be divisible by L1_PAD_BOUNDARY (32). Key
* is expected to be endian compensated in memory at key setup.
*/
{
UINT64 h;
UWORD c = dlen / 32;
UINT32 *k = (UINT32 *)kp;
const UINT32 *d = (const UINT32 *)dp;
UINT32 d0,d1,d2,d3,d4,d5,d6,d7;
UINT32 k0,k1,k2,k3,k4,k5,k6,k7;
h = *((UINT64 *)hp);
do {
d0 = LOAD_UINT32_LITTLE(d+0); d1 = LOAD_UINT32_LITTLE(d+1);
d2 = LOAD_UINT32_LITTLE(d+2); d3 = LOAD_UINT32_LITTLE(d+3);
d4 = LOAD_UINT32_LITTLE(d+4); d5 = LOAD_UINT32_LITTLE(d+5);
d6 = LOAD_UINT32_LITTLE(d+6); d7 = LOAD_UINT32_LITTLE(d+7);
k0 = *(k+0); k1 = *(k+1); k2 = *(k+2); k3 = *(k+3);
k4 = *(k+4); k5 = *(k+5); k6 = *(k+6); k7 = *(k+7);
h += MUL64((k0 + d0), (k4 + d4));
h += MUL64((k1 + d1), (k5 + d5));
h += MUL64((k2 + d2), (k6 + d6));
h += MUL64((k3 + d3), (k7 + d7));
d += 8;
k += 8;
} while (--c);
*((UINT64 *)hp) = h;
}
#elif (UMAC_OUTPUT_LEN == 8)
static void nh_aux(void *kp, const void *dp, void *hp, UINT32 dlen)
/* Same as previous nh_aux, but two streams are handled in one pass,
* reading and writing 16 bytes of hash-state per call.
*/
{
UINT64 h1,h2;
UWORD c = dlen / 32;
UINT32 *k = (UINT32 *)kp;
const UINT32 *d = (const UINT32 *)dp;
UINT32 d0,d1,d2,d3,d4,d5,d6,d7;
UINT32 k0,k1,k2,k3,k4,k5,k6,k7,
k8,k9,k10,k11;
h1 = *((UINT64 *)hp);
h2 = *((UINT64 *)hp + 1);
k0 = *(k+0); k1 = *(k+1); k2 = *(k+2); k3 = *(k+3);
do {
d0 = LOAD_UINT32_LITTLE(d+0); d1 = LOAD_UINT32_LITTLE(d+1);
d2 = LOAD_UINT32_LITTLE(d+2); d3 = LOAD_UINT32_LITTLE(d+3);
d4 = LOAD_UINT32_LITTLE(d+4); d5 = LOAD_UINT32_LITTLE(d+5);
d6 = LOAD_UINT32_LITTLE(d+6); d7 = LOAD_UINT32_LITTLE(d+7);
k4 = *(k+4); k5 = *(k+5); k6 = *(k+6); k7 = *(k+7);
k8 = *(k+8); k9 = *(k+9); k10 = *(k+10); k11 = *(k+11);
h1 += MUL64((k0 + d0), (k4 + d4));
h2 += MUL64((k4 + d0), (k8 + d4));
h1 += MUL64((k1 + d1), (k5 + d5));
h2 += MUL64((k5 + d1), (k9 + d5));
h1 += MUL64((k2 + d2), (k6 + d6));
h2 += MUL64((k6 + d2), (k10 + d6));
h1 += MUL64((k3 + d3), (k7 + d7));
h2 += MUL64((k7 + d3), (k11 + d7));
k0 = k8; k1 = k9; k2 = k10; k3 = k11;
d += 8;
k += 8;
} while (--c);
((UINT64 *)hp)[0] = h1;
((UINT64 *)hp)[1] = h2;
}
#elif (UMAC_OUTPUT_LEN == 12)
static void nh_aux(void *kp, const void *dp, void *hp, UINT32 dlen)
/* Same as previous nh_aux, but two streams are handled in one pass,
* reading and writing 24 bytes of hash-state per call.
*/
{
UINT64 h1,h2,h3;
UWORD c = dlen / 32;
UINT32 *k = (UINT32 *)kp;
const UINT32 *d = (const UINT32 *)dp;
UINT32 d0,d1,d2,d3,d4,d5,d6,d7;
UINT32 k0,k1,k2,k3,k4,k5,k6,k7,
k8,k9,k10,k11,k12,k13,k14,k15;
h1 = *((UINT64 *)hp);
h2 = *((UINT64 *)hp + 1);
h3 = *((UINT64 *)hp + 2);
k0 = *(k+0); k1 = *(k+1); k2 = *(k+2); k3 = *(k+3);
k4 = *(k+4); k5 = *(k+5); k6 = *(k+6); k7 = *(k+7);
do {
d0 = LOAD_UINT32_LITTLE(d+0); d1 = LOAD_UINT32_LITTLE(d+1);
d2 = LOAD_UINT32_LITTLE(d+2); d3 = LOAD_UINT32_LITTLE(d+3);
d4 = LOAD_UINT32_LITTLE(d+4); d5 = LOAD_UINT32_LITTLE(d+5);
d6 = LOAD_UINT32_LITTLE(d+6); d7 = LOAD_UINT32_LITTLE(d+7);
k8 = *(k+8); k9 = *(k+9); k10 = *(k+10); k11 = *(k+11);
k12 = *(k+12); k13 = *(k+13); k14 = *(k+14); k15 = *(k+15);
h1 += MUL64((k0 + d0), (k4 + d4));
h2 += MUL64((k4 + d0), (k8 + d4));
h3 += MUL64((k8 + d0), (k12 + d4));
h1 += MUL64((k1 + d1), (k5 + d5));
h2 += MUL64((k5 + d1), (k9 + d5));
h3 += MUL64((k9 + d1), (k13 + d5));
h1 += MUL64((k2 + d2), (k6 + d6));
h2 += MUL64((k6 + d2), (k10 + d6));
h3 += MUL64((k10 + d2), (k14 + d6));
h1 += MUL64((k3 + d3), (k7 + d7));
h2 += MUL64((k7 + d3), (k11 + d7));
h3 += MUL64((k11 + d3), (k15 + d7));
k0 = k8; k1 = k9; k2 = k10; k3 = k11;
k4 = k12; k5 = k13; k6 = k14; k7 = k15;
d += 8;
k += 8;
} while (--c);
((UINT64 *)hp)[0] = h1;
((UINT64 *)hp)[1] = h2;
((UINT64 *)hp)[2] = h3;
}
#elif (UMAC_OUTPUT_LEN == 16)
static void nh_aux(void *kp, const void *dp, void *hp, UINT32 dlen)
/* Same as previous nh_aux, but two streams are handled in one pass,
* reading and writing 24 bytes of hash-state per call.
*/
{
UINT64 h1,h2,h3,h4;
UWORD c = dlen / 32;
UINT32 *k = (UINT32 *)kp;
const UINT32 *d = (const UINT32 *)dp;
UINT32 d0,d1,d2,d3,d4,d5,d6,d7;
UINT32 k0,k1,k2,k3,k4,k5,k6,k7,
k8,k9,k10,k11,k12,k13,k14,k15,
k16,k17,k18,k19;
h1 = *((UINT64 *)hp);
h2 = *((UINT64 *)hp + 1);
h3 = *((UINT64 *)hp + 2);
h4 = *((UINT64 *)hp + 3);
k0 = *(k+0); k1 = *(k+1); k2 = *(k+2); k3 = *(k+3);
k4 = *(k+4); k5 = *(k+5); k6 = *(k+6); k7 = *(k+7);
do {
d0 = LOAD_UINT32_LITTLE(d+0); d1 = LOAD_UINT32_LITTLE(d+1);
d2 = LOAD_UINT32_LITTLE(d+2); d3 = LOAD_UINT32_LITTLE(d+3);
d4 = LOAD_UINT32_LITTLE(d+4); d5 = LOAD_UINT32_LITTLE(d+5);
d6 = LOAD_UINT32_LITTLE(d+6); d7 = LOAD_UINT32_LITTLE(d+7);
k8 = *(k+8); k9 = *(k+9); k10 = *(k+10); k11 = *(k+11);
k12 = *(k+12); k13 = *(k+13); k14 = *(k+14); k15 = *(k+15);
k16 = *(k+16); k17 = *(k+17); k18 = *(k+18); k19 = *(k+19);
h1 += MUL64((k0 + d0), (k4 + d4));
h2 += MUL64((k4 + d0), (k8 + d4));
h3 += MUL64((k8 + d0), (k12 + d4));
h4 += MUL64((k12 + d0), (k16 + d4));
h1 += MUL64((k1 + d1), (k5 + d5));
h2 += MUL64((k5 + d1), (k9 + d5));
h3 += MUL64((k9 + d1), (k13 + d5));
h4 += MUL64((k13 + d1), (k17 + d5));
h1 += MUL64((k2 + d2), (k6 + d6));
h2 += MUL64((k6 + d2), (k10 + d6));
h3 += MUL64((k10 + d2), (k14 + d6));
h4 += MUL64((k14 + d2), (k18 + d6));
h1 += MUL64((k3 + d3), (k7 + d7));
h2 += MUL64((k7 + d3), (k11 + d7));
h3 += MUL64((k11 + d3), (k15 + d7));
h4 += MUL64((k15 + d3), (k19 + d7));
k0 = k8; k1 = k9; k2 = k10; k3 = k11;
k4 = k12; k5 = k13; k6 = k14; k7 = k15;
k8 = k16; k9 = k17; k10 = k18; k11 = k19;
d += 8;
k += 8;
} while (--c);
((UINT64 *)hp)[0] = h1;
((UINT64 *)hp)[1] = h2;
((UINT64 *)hp)[2] = h3;
((UINT64 *)hp)[3] = h4;
}
/* ---------------------------------------------------------------------- */
#endif /* UMAC_OUTPUT_LENGTH */
/* ---------------------------------------------------------------------- */
/* ---------------------------------------------------------------------- */
static void nh_transform(nh_ctx *hc, const UINT8 *buf, UINT32 nbytes)
/* This function is a wrapper for the primitive NH hash functions. It takes
* as argument "hc" the current hash context and a buffer which must be a
* multiple of L1_PAD_BOUNDARY. The key passed to nh_aux is offset
* appropriately according to how much message has been hashed already.
*/
{
UINT8 *key;
key = hc->nh_key + hc->bytes_hashed;
nh_aux(key, buf, hc->state, nbytes);
}
/* ---------------------------------------------------------------------- */
#if (__LITTLE_ENDIAN__)
static void endian_convert(void *buf, UWORD bpw, UINT32 num_bytes)
/* We endian convert the keys on little-endian computers to */
/* compensate for the lack of big-endian memory reads during hashing. */
{
UWORD iters = num_bytes / bpw;
if (bpw == 4) {
UINT32 *p = (UINT32 *)buf;
do {
*p = LOAD_UINT32_REVERSED(p);
p++;
} while (--iters);
} else if (bpw == 8) {
UINT32 *p = (UINT32 *)buf;
UINT32 t;
do {
t = LOAD_UINT32_REVERSED(p+1);
p[1] = LOAD_UINT32_REVERSED(p);
p[0] = t;
p += 2;
} while (--iters);
}
}
#define endian_convert_if_le(x,y,z) endian_convert((x),(y),(z))
#else
#define endian_convert_if_le(x,y,z) do{}while(0) /* Do nothing */
#endif
/* ---------------------------------------------------------------------- */
static void nh_reset(nh_ctx *hc)
/* Reset nh_ctx to ready for hashing of new data */
{
hc->bytes_hashed = 0;
hc->next_data_empty = 0;
hc->state[0] = 0;
#if (UMAC_OUTPUT_LEN >= 8)
hc->state[1] = 0;
#endif
#if (UMAC_OUTPUT_LEN >= 12)
hc->state[2] = 0;
#endif
#if (UMAC_OUTPUT_LEN == 16)
hc->state[3] = 0;
#endif
}
/* ---------------------------------------------------------------------- */
static void nh_init(nh_ctx *hc, aes_int_key prf_key)
/* Generate nh_key, endian convert and reset to be ready for hashing. */
{
kdf(hc->nh_key, prf_key, 1, sizeof(hc->nh_key));
endian_convert_if_le(hc->nh_key, 4, sizeof(hc->nh_key));
nh_reset(hc);
}
/* ---------------------------------------------------------------------- */
static void nh_update(nh_ctx *hc, const UINT8 *buf, UINT32 nbytes)
/* Incorporate nbytes of data into a nh_ctx, buffer whatever is not an */
/* even multiple of HASH_BUF_BYTES. */
{
UINT32 i,j;
j = hc->next_data_empty;
if ((j + nbytes) >= HASH_BUF_BYTES) {
if (j) {
i = HASH_BUF_BYTES - j;
memcpy(hc->data+j, buf, i);
nh_transform(hc,hc->data,HASH_BUF_BYTES);
nbytes -= i;
buf += i;
hc->bytes_hashed += HASH_BUF_BYTES;
}
if (nbytes >= HASH_BUF_BYTES) {
i = nbytes & ~(HASH_BUF_BYTES - 1);
nh_transform(hc, buf, i);
nbytes -= i;
buf += i;
hc->bytes_hashed += i;
}
j = 0;
}
memcpy(hc->data + j, buf, nbytes);
hc->next_data_empty = j + nbytes;
}
/* ---------------------------------------------------------------------- */
static void zero_pad(UINT8 *p, int nbytes)
{
/* Write "nbytes" of zeroes, beginning at "p" */
if (nbytes >= (int)sizeof(UWORD)) {
while ((ptrdiff_t)p % sizeof(UWORD)) {
*p = 0;
nbytes--;
p++;
}
while (nbytes >= (int)sizeof(UWORD)) {
*(UWORD *)p = 0;
nbytes -= sizeof(UWORD);
p += sizeof(UWORD);
}
}
while (nbytes) {
*p = 0;
nbytes--;
p++;
}
}
/* ---------------------------------------------------------------------- */
static void nh_final(nh_ctx *hc, UINT8 *result)
/* After passing some number of data buffers to nh_update() for integration
* into an NH context, nh_final is called to produce a hash result. If any
* bytes are in the buffer hc->data, incorporate them into the
* NH context. Finally, add into the NH accumulation "state" the total number
* of bits hashed. The resulting numbers are written to the buffer "result".
* If nh_update was never called, L1_PAD_BOUNDARY zeroes are incorporated.
*/
{
int nh_len, nbits;
if (hc->next_data_empty != 0) {
nh_len = ((hc->next_data_empty + (L1_PAD_BOUNDARY - 1)) &
~(L1_PAD_BOUNDARY - 1));
zero_pad(hc->data + hc->next_data_empty,
nh_len - hc->next_data_empty);
nh_transform(hc, hc->data, nh_len);
hc->bytes_hashed += hc->next_data_empty;
} else if (hc->bytes_hashed == 0) {
nh_len = L1_PAD_BOUNDARY;
zero_pad(hc->data, L1_PAD_BOUNDARY);
nh_transform(hc, hc->data, nh_len);
}
nbits = (hc->bytes_hashed << 3);
((UINT64 *)result)[0] = ((UINT64 *)hc->state)[0] + nbits;
#if (UMAC_OUTPUT_LEN >= 8)
((UINT64 *)result)[1] = ((UINT64 *)hc->state)[1] + nbits;
#endif
#if (UMAC_OUTPUT_LEN >= 12)
((UINT64 *)result)[2] = ((UINT64 *)hc->state)[2] + nbits;
#endif
#if (UMAC_OUTPUT_LEN == 16)
((UINT64 *)result)[3] = ((UINT64 *)hc->state)[3] + nbits;
#endif
nh_reset(hc);
}
/* ---------------------------------------------------------------------- */
static void nh(nh_ctx *hc, const UINT8 *buf, UINT32 padded_len,
UINT32 unpadded_len, UINT8 *result)
/* All-in-one nh_update() and nh_final() equivalent.
* Assumes that padded_len is divisible by L1_PAD_BOUNDARY and result is
* well aligned
*/
{
UINT32 nbits;
/* Initialize the hash state */
nbits = (unpadded_len << 3);
((UINT64 *)result)[0] = nbits;
#if (UMAC_OUTPUT_LEN >= 8)
((UINT64 *)result)[1] = nbits;
#endif
#if (UMAC_OUTPUT_LEN >= 12)
((UINT64 *)result)[2] = nbits;
#endif
#if (UMAC_OUTPUT_LEN == 16)
((UINT64 *)result)[3] = nbits;
#endif
nh_aux(hc->nh_key, buf, result, padded_len);
}
/* ---------------------------------------------------------------------- */
/* ---------------------------------------------------------------------- */
/* ----- Begin UHASH Section -------------------------------------------- */
/* ---------------------------------------------------------------------- */
/* ---------------------------------------------------------------------- */
/* UHASH is a multi-layered algorithm. Data presented to UHASH is first
* hashed by NH. The NH output is then hashed by a polynomial-hash layer
* unless the initial data to be hashed is short. After the polynomial-
* layer, an inner-product hash is used to produce the final UHASH output.
*
* UHASH provides two interfaces, one all-at-once and another where data
* buffers are presented sequentially. In the sequential interface, the
* UHASH client calls the routine uhash_update() as many times as necessary.
* When there is no more data to be fed to UHASH, the client calls
* uhash_final() which
* calculates the UHASH output. Before beginning another UHASH calculation
* the uhash_reset() routine must be called. The all-at-once UHASH routine,
* uhash(), is equivalent to the sequence of calls uhash_update() and
* uhash_final(); however it is optimized and should be
* used whenever the sequential interface is not necessary.
*
* The routine uhash_init() initializes the uhash_ctx data structure and
* must be called once, before any other UHASH routine.
*/
/* ---------------------------------------------------------------------- */
/* ----- Constants and uhash_ctx ---------------------------------------- */
/* ---------------------------------------------------------------------- */
/* ---------------------------------------------------------------------- */
/* ----- Poly hash and Inner-Product hash Constants --------------------- */
/* ---------------------------------------------------------------------- */
/* Primes and masks */
#define p36 ((UINT64)0x0000000FFFFFFFFBull) /* 2^36 - 5 */
#define p64 ((UINT64)0xFFFFFFFFFFFFFFC5ull) /* 2^64 - 59 */
#define m36 ((UINT64)0x0000000FFFFFFFFFull) /* The low 36 of 64 bits */
/* ---------------------------------------------------------------------- */
typedef struct uhash_ctx {
nh_ctx hash; /* Hash context for L1 NH hash */
UINT64 poly_key_8[STREAMS]; /* p64 poly keys */
UINT64 poly_accum[STREAMS]; /* poly hash result */
UINT64 ip_keys[STREAMS*4]; /* Inner-product keys */
UINT32 ip_trans[STREAMS]; /* Inner-product translation */
UINT32 msg_len; /* Total length of data passed */
/* to uhash */
} uhash_ctx;
typedef struct uhash_ctx *uhash_ctx_t;
/* ---------------------------------------------------------------------- */
/* The polynomial hashes use Horner's rule to evaluate a polynomial one
* word at a time. As described in the specification, poly32 and poly64
* require keys from special domains. The following implementations exploit
* the special domains to avoid overflow. The results are not guaranteed to
* be within Z_p32 and Z_p64, but the Inner-Product hash implementation
* patches any errant values.
*/
static UINT64 poly64(UINT64 cur, UINT64 key, UINT64 data)
{
UINT32 key_hi = (UINT32)(key >> 32),
key_lo = (UINT32)key,
cur_hi = (UINT32)(cur >> 32),
cur_lo = (UINT32)cur,
x_lo,
x_hi;
UINT64 X,T,res;
X = MUL64(key_hi, cur_lo) + MUL64(cur_hi, key_lo);
x_lo = (UINT32)X;
x_hi = (UINT32)(X >> 32);
res = (MUL64(key_hi, cur_hi) + x_hi) * 59 + MUL64(key_lo, cur_lo);
T = ((UINT64)x_lo << 32);
res += T;
if (res < T)
res += 59;
res += data;
if (res < data)
res += 59;
return res;
}
/* Although UMAC is specified to use a ramped polynomial hash scheme, this
* implementation does not handle all ramp levels. Because we don't handle
* the ramp up to p128 modulus in this implementation, we are limited to
* 2^14 poly_hash() invocations per stream (for a total capacity of 2^24
* bytes input to UMAC per tag, ie. 16MB).
*/
static void poly_hash(uhash_ctx_t hc, UINT32 data_in[])
{
int i;
UINT64 *data=(UINT64*)data_in;
for (i = 0; i < STREAMS; i++) {
if ((UINT32)(data[i] >> 32) == 0xfffffffful) {
hc->poly_accum[i] = poly64(hc->poly_accum[i],
hc->poly_key_8[i], p64 - 1);
hc->poly_accum[i] = poly64(hc->poly_accum[i],
hc->poly_key_8[i], (data[i] - 59));
} else {
hc->poly_accum[i] = poly64(hc->poly_accum[i],
hc->poly_key_8[i], data[i]);
}
}
}
/* ---------------------------------------------------------------------- */
/* The final step in UHASH is an inner-product hash. The poly hash
* produces a result not necessarily WORD_LEN bytes long. The inner-
* product hash breaks the polyhash output into 16-bit chunks and
* multiplies each with a 36 bit key.
*/
static UINT64 ip_aux(UINT64 t, UINT64 *ipkp, UINT64 data)
{
t = t + ipkp[0] * (UINT64)(UINT16)(data >> 48);
t = t + ipkp[1] * (UINT64)(UINT16)(data >> 32);
t = t + ipkp[2] * (UINT64)(UINT16)(data >> 16);
t = t + ipkp[3] * (UINT64)(UINT16)(data);
return t;
}
static UINT32 ip_reduce_p36(UINT64 t)
{
/* Divisionless modular reduction */
UINT64 ret;
ret = (t & m36) + 5 * (t >> 36);
if (ret >= p36)
ret -= p36;
/* return least significant 32 bits */
return (UINT32)(ret);
}
/* If the data being hashed by UHASH is no longer than L1_KEY_LEN, then
* the polyhash stage is skipped and ip_short is applied directly to the
* NH output.
*/
static void ip_short(uhash_ctx_t ahc, UINT8 *nh_res, unsigned char *res)
{
UINT64 t;
UINT64 *nhp = (UINT64 *)nh_res;
t = ip_aux(0,ahc->ip_keys, nhp[0]);
STORE_UINT32_BIG((UINT32 *)res+0, ip_reduce_p36(t) ^ ahc->ip_trans[0]);
#if (UMAC_OUTPUT_LEN >= 8)
t = ip_aux(0,ahc->ip_keys+4, nhp[1]);
STORE_UINT32_BIG((UINT32 *)res+1, ip_reduce_p36(t) ^ ahc->ip_trans[1]);
#endif
#if (UMAC_OUTPUT_LEN >= 12)
t = ip_aux(0,ahc->ip_keys+8, nhp[2]);
STORE_UINT32_BIG((UINT32 *)res+2, ip_reduce_p36(t) ^ ahc->ip_trans[2]);
#endif
#if (UMAC_OUTPUT_LEN == 16)
t = ip_aux(0,ahc->ip_keys+12, nhp[3]);
STORE_UINT32_BIG((UINT32 *)res+3, ip_reduce_p36(t) ^ ahc->ip_trans[3]);
#endif
}
/* If the data being hashed by UHASH is longer than L1_KEY_LEN, then
* the polyhash stage is not skipped and ip_long is applied to the
* polyhash output.
*/
static void ip_long(uhash_ctx_t ahc, unsigned char *res)
{
int i;
UINT64 t;
for (i = 0; i < STREAMS; i++) {
/* fix polyhash output not in Z_p64 */
if (ahc->poly_accum[i] >= p64)
ahc->poly_accum[i] -= p64;
t = ip_aux(0,ahc->ip_keys+(i*4), ahc->poly_accum[i]);
STORE_UINT32_BIG((UINT32 *)res+i,
ip_reduce_p36(t) ^ ahc->ip_trans[i]);
}
}
/* ---------------------------------------------------------------------- */
/* ---------------------------------------------------------------------- */
/* Reset uhash context for next hash session */
static int uhash_reset(uhash_ctx_t pc)
{
nh_reset(&pc->hash);
pc->msg_len = 0;
pc->poly_accum[0] = 1;
#if (UMAC_OUTPUT_LEN >= 8)
pc->poly_accum[1] = 1;
#endif
#if (UMAC_OUTPUT_LEN >= 12)
pc->poly_accum[2] = 1;
#endif
#if (UMAC_OUTPUT_LEN == 16)
pc->poly_accum[3] = 1;
#endif
return 1;
}
/* ---------------------------------------------------------------------- */
/* Given a pointer to the internal key needed by kdf() and a uhash context,
* initialize the NH context and generate keys needed for poly and inner-
* product hashing. All keys are endian adjusted in memory so that native
* loads cause correct keys to be in registers during calculation.
*/
static void uhash_init(uhash_ctx_t ahc, aes_int_key prf_key)
{
int i;
UINT8 buf[(8*STREAMS+4)*sizeof(UINT64)];
/* Zero the entire uhash context */
memset(ahc, 0, sizeof(uhash_ctx));
/* Initialize the L1 hash */
nh_init(&ahc->hash, prf_key);
/* Setup L2 hash variables */
kdf(buf, prf_key, 2, sizeof(buf)); /* Fill buffer with index 1 key */
for (i = 0; i < STREAMS; i++) {
/* Fill keys from the buffer, skipping bytes in the buffer not
* used by this implementation. Endian reverse the keys if on a
* little-endian computer.
*/
memcpy(ahc->poly_key_8+i, buf+24*i, 8);
endian_convert_if_le(ahc->poly_key_8+i, 8, 8);
/* Mask the 64-bit keys to their special domain */
ahc->poly_key_8[i] &= ((UINT64)0x01ffffffu << 32) + 0x01ffffffu;
ahc->poly_accum[i] = 1; /* Our polyhash prepends a non-zero word */
}
/* Setup L3-1 hash variables */
kdf(buf, prf_key, 3, sizeof(buf)); /* Fill buffer with index 2 key */
for (i = 0; i < STREAMS; i++)
memcpy(ahc->ip_keys+4*i, buf+(8*i+4)*sizeof(UINT64),
4*sizeof(UINT64));
endian_convert_if_le(ahc->ip_keys, sizeof(UINT64),
sizeof(ahc->ip_keys));
for (i = 0; i < STREAMS*4; i++)
ahc->ip_keys[i] %= p36; /* Bring into Z_p36 */
/* Setup L3-2 hash variables */
/* Fill buffer with index 4 key */
kdf(ahc->ip_trans, prf_key, 4, STREAMS * sizeof(UINT32));
endian_convert_if_le(ahc->ip_trans, sizeof(UINT32),
STREAMS * sizeof(UINT32));
#if defined(HAVE_MEMSET_S)
memset_s(buf, sizeof(buf), 0, sizeof(buf));
#endif /* HAVE_MEMSET_S */
}
/* ---------------------------------------------------------------------- */
static int uhash_update(uhash_ctx_t ctx, const unsigned char *input, long len)
/* Given len bytes of data, we parse it into L1_KEY_LEN chunks and
* hash each one with NH, calling the polyhash on each NH output.
*/
{
UWORD bytes_hashed, bytes_remaining;
UINT64 result_buf[STREAMS];
UINT8 *nh_result = (UINT8 *)&result_buf;
if (ctx->msg_len + len <= L1_KEY_LEN) {
nh_update(&ctx->hash, (const UINT8 *)input, len);
ctx->msg_len += len;
} else {
bytes_hashed = ctx->msg_len % L1_KEY_LEN;
if (ctx->msg_len == L1_KEY_LEN)
bytes_hashed = L1_KEY_LEN;
if (bytes_hashed + len >= L1_KEY_LEN) {
/* If some bytes have been passed to the hash function */
/* then we want to pass at most (L1_KEY_LEN - bytes_hashed) */
/* bytes to complete the current nh_block. */
if (bytes_hashed) {
bytes_remaining = (L1_KEY_LEN - bytes_hashed);
nh_update(&ctx->hash, (const UINT8 *)input, bytes_remaining);
nh_final(&ctx->hash, nh_result);
ctx->msg_len += bytes_remaining;
poly_hash(ctx,(UINT32 *)nh_result);
len -= bytes_remaining;
input += bytes_remaining;
}
/* Hash directly from input stream if enough bytes */
while (len >= L1_KEY_LEN) {
nh(&ctx->hash, (const UINT8 *)input, L1_KEY_LEN,
L1_KEY_LEN, nh_result);
ctx->msg_len += L1_KEY_LEN;
len -= L1_KEY_LEN;
input += L1_KEY_LEN;
poly_hash(ctx,(UINT32 *)nh_result);
}
}
/* pass remaining < L1_KEY_LEN bytes of input data to NH */
if (len) {
nh_update(&ctx->hash, (const UINT8 *)input, len);
ctx->msg_len += len;
}
}
return (1);
}
/* ---------------------------------------------------------------------- */
static int uhash_final(uhash_ctx_t ctx, unsigned char *res)
/* Incorporate any pending data, pad, and generate tag */
{
UINT64 result_buf[STREAMS];
UINT8 *nh_result = (UINT8 *)&result_buf;
if (ctx->msg_len > L1_KEY_LEN) {
if (ctx->msg_len % L1_KEY_LEN) {
nh_final(&ctx->hash, nh_result);
poly_hash(ctx,(UINT32 *)nh_result);
}
ip_long(ctx, res);
} else {
nh_final(&ctx->hash, nh_result);
ip_short(ctx,nh_result, res);
}
uhash_reset(ctx);
return (1);
}
/* ---------------------------------------------------------------------- */
/* ---------------------------------------------------------------------- */
/* ----- Begin UMAC Section --------------------------------------------- */
/* ---------------------------------------------------------------------- */
/* ---------------------------------------------------------------------- */
/* The UMAC interface has two interfaces, an all-at-once interface where
* the entire message to be authenticated is passed to UMAC in one buffer,
* and a sequential interface where the message is presented a little at a
* time. The all-at-once is more optimaized than the sequential version and
* should be preferred when the sequential interface is not required.
*/
struct umac_ctx {
uhash_ctx hash; /* Hash function for message compression */
pdf_ctx pdf; /* PDF for hashed output */
void *free_ptr; /* Address to free this struct via */
};
/* ---------------------------------------------------------------------- */
int proxy_ssh_umac_reset(struct umac_ctx *ctx)
/* Reset the hash function to begin a new authentication. */
{
uhash_reset(&ctx->hash);
return (1);
}
/* ---------------------------------------------------------------------- */
int proxy_ssh_umac_delete(struct umac_ctx *ctx)
/* Deallocate the ctx structure */
{
if (ctx) {
if (ALLOC_BOUNDARY)
ctx = (struct umac_ctx *)ctx->free_ptr;
free(ctx);
}
return (1);
}
/* ---------------------------------------------------------------------- */
struct umac_ctx *proxy_ssh_umac_alloc(void) {
struct umac_ctx *ctx, *octx;
size_t bytes_to_add;
octx = ctx = malloc(sizeof(struct umac_ctx) + ALLOC_BOUNDARY);
if (ctx) {
if (ALLOC_BOUNDARY) {
bytes_to_add = ALLOC_BOUNDARY -
((ptrdiff_t)ctx & (ALLOC_BOUNDARY - 1));
ctx = (struct umac_ctx *)((unsigned char *)ctx + bytes_to_add);
}
ctx->free_ptr = octx;
}
return (ctx);
}
void proxy_ssh_umac_init(struct umac_ctx *ctx, const unsigned char key[]) {
aes_int_key prf_key;
aes_key_setup(key, prf_key);
pdf_init(&ctx->pdf, prf_key);
uhash_init(&ctx->hash, prf_key);
}
struct umac_ctx *proxy_ssh_umac_new(const unsigned char key[])
/* Dynamically allocate a umac_ctx struct, initialize variables,
* generate subkeys from key. Align to 16-byte boundary.
*/
{
struct umac_ctx *ctx;
ctx = proxy_ssh_umac_alloc();
if (ctx) {
proxy_ssh_umac_init(ctx, key);
}
return (ctx);
}
/* ---------------------------------------------------------------------- */
int proxy_ssh_umac_final(struct umac_ctx *ctx, unsigned char tag[], const unsigned char nonce[8])
/* Incorporate any pending data, pad, and generate tag */
{
uhash_final(&ctx->hash, (unsigned char *)tag);
pdf_gen_xor(&ctx->pdf, (const UINT8 *)nonce, (UINT8 *)tag);
return (1);
}
/* ---------------------------------------------------------------------- */
int proxy_ssh_umac_update(struct umac_ctx *ctx, const unsigned char *input, long len)
/* Given len bytes of data, we parse it into L1_KEY_LEN chunks and */
/* hash each one, calling the PDF on the hashed output whenever the hash- */
/* output buffer is full. */
{
uhash_update(&ctx->hash, input, len);
return (1);
}
#else
struct umac_ctx {
void *free_ptr; /* Address to free this struct via */
};
int proxy_ssh_umac_reset(struct umac_ctx *ctx)
/* Reset the hash function to begin a new authentication. */
{
return (0);
}
/* ---------------------------------------------------------------------- */
int proxy_ssh_umac_delete(struct umac_ctx *ctx)
/* Deallocate the ctx structure */
{
return (0);
}
/* ---------------------------------------------------------------------- */
struct umac_ctx *proxy_ssh_umac_alloc(void) {
return (NULL);
}
void proxy_ssh_umac_init(struct umac_ctx *ctx, const unsigned char key[]) {
}
struct umac_ctx *proxy_ssh_umac_new(const unsigned char key[])
/* Dynamically allocate a umac_ctx struct, initialize variables,
* generate subkeys from key. Align to 16-byte boundary.
*/
{
return (NULL);
}
/* ---------------------------------------------------------------------- */
int proxy_ssh_umac_final(struct umac_ctx *ctx, unsigned char tag[], const unsigned char nonce[8])
{
return (0);
}
/* ---------------------------------------------------------------------- */
int proxy_ssh_umac_update(struct umac_ctx *ctx, const unsigned char *input, long len)
/* Given len bytes of data, we parse it into L1_KEY_LEN chunks and */
/* hash each one, calling the PDF on the hashed output whenever the hash- */
/* output buffer is full. */
{
return (0);
}
#endif /* OpenSSL-0.9.7 or later */
/* ---------------------------------------------------------------------- */
/* ---------------------------------------------------------------------- */
/* ----- End UMAC Section ----------------------------------------------- */
/* ---------------------------------------------------------------------- */
/* ---------------------------------------------------------------------- */
proftpd-mod_proxy-0.9.5/lib/proxy/ssh/umac128.c 0000664 0000000 0000000 00000003036 14757370167 0021314 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH UMAC
* Copyright (c) 2022 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "proxy/ssh/umac.h"
#define UMAC_OUTPUT_LEN 16
#define proxy_ssh_umac_alloc proxy_ssh_umac128_alloc
#define proxy_ssh_umac_init proxy_ssh_umac128_init
#define proxy_ssh_umac_new proxy_ssh_umac128_new
#define proxy_ssh_umac_update proxy_ssh_umac128_update
#define proxy_ssh_umac_reset proxy_ssh_umac128_reset
#define proxy_ssh_umac_final proxy_ssh_umac128_final
#define proxy_ssh_umac_delete proxy_ssh_umac128_delete
#define proxy_ssh_umac_ctx proxy_ssh_umac128_ctx
#include "umac.c"
proftpd-mod_proxy-0.9.5/lib/proxy/ssh/utf8.c 0000664 0000000 0000000 00000021601 14757370167 0021020 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH UTF8 encoding
* Copyright (c) 2021 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "proxy/ssh/utf8.h"
#if defined(HAVE_ICONV_H)
# include
#endif
#if defined(HAVE_LANGINFO_H)
# include
#endif
static const char *local_charset = NULL;
static const char *trace_channel = "proxy.ssh.utf8";
#if defined(PR_USE_NLS) && defined(HAVE_ICONV_H)
static iconv_t decode_conv = (iconv_t) -1;
static iconv_t encode_conv = (iconv_t) -1;
static int utf8_convert(iconv_t conv, const char *inbuf, size_t *inbuflen,
char *outbuf, size_t *outbuflen) {
# ifdef HAVE_ICONV
/* Reset the state machine before each conversion. */
(void) iconv(conv, NULL, NULL, NULL, NULL);
while (*inbuflen > 0) {
size_t nconv;
pr_signals_handle();
/* Solaris/FreeBSD's iconv(3) takes a const char ** for the input buffer,
* whereas Linux/Mac OSX iconv(3) use char ** for the input buffer.
*/
#if defined(LINUX) || defined(DARWIN6) || defined(DARWIN7) || \
defined(DARWIN8) || defined(DARWIN9) || defined(DARWIN10) || \
defined(DARWIN11) || defined(DARWIN12)
nconv = iconv(conv, (char **) &inbuf, inbuflen, &outbuf, outbuflen);
#else
nconv = iconv(conv, &inbuf, inbuflen, &outbuf, outbuflen);
#endif
if (nconv == (size_t) -1) {
/* Note: an errno of EILSEQ here can indicate badly encoded strings OR
* (more likely) that the source character set used in the iconv_open(3)
* call for this iconv_t descriptor does not accurately describe the
* character encoding of the given string. E.g. a filename may use
* the ISO8859-1 character set, but iconv_open(3) was called using
* US-ASCII.
*/
return -1;
}
/* XXX We should let the loop condition work, rather than breaking out
* of the loop here.
*/
break;
}
return 0;
# else
errno = ENOSYS;
return -1;
# endif /* HAVE_ICONV */
}
#endif /* !PR_USE_NLS && !HAVE_ICONV_H */
#if defined(PR_USE_OPENSSL)
int proxy_ssh_utf8_set_charset(const char *charset) {
int res;
if (charset == NULL) {
errno = EINVAL;
return -1;
}
if (local_charset) {
pr_trace_msg(trace_channel, 5,
"attempting to switch local charset from %s to %s", local_charset,
charset);
} else {
pr_trace_msg(trace_channel, 5, "attempting to use %s as local charset",
charset);
}
(void) proxy_ssh_utf8_free();
local_charset = pstrdup(permanent_pool, charset);
res = proxy_ssh_utf8_init();
if (res < 0) {
pr_trace_msg(trace_channel, 1,
"failed to initialize encoding for local charset %s", charset);
local_charset = NULL;
return -1;
}
return res;
}
int proxy_ssh_utf8_free(void) {
# if defined(PR_USE_NLS) && defined(HAVE_ICONV)
int res = 0;
/* Close the iconv handles. */
if (encode_conv != (iconv_t) -1) {
res = iconv_close(encode_conv);
if (res < 0) {
pr_trace_msg(trace_channel, 1,
"error closing encoding conversion handle from '%s' to '%s': %s",
local_charset, "UTF-8", strerror(errno));
res = -1;
}
encode_conv = (iconv_t) -1;
}
if (decode_conv != (iconv_t) -1) {
res = iconv_close(decode_conv);
if (res < 0) {
pr_trace_msg(trace_channel, 1,
"error closing decoding conversion handle from '%s' to '%s': %s",
"UTF-8", local_charset, strerror(errno));
res = -1;
}
decode_conv = (iconv_t) -1;
}
return res;
# else
errno = ENOSYS;
return -1;
# endif
}
int proxy_ssh_utf8_init(void) {
#if defined(PR_USE_NLS) && defined(HAVE_ICONV)
if (local_charset == NULL) {
local_charset = pr_encode_get_local_charset();
} else {
pr_trace_msg(trace_channel, 3,
"using '%s' as local charset for UTF8 conversion", local_charset);
}
/* Get the iconv handles. */
encode_conv = iconv_open("UTF-8", local_charset);
if (encode_conv == (iconv_t) -1) {
pr_trace_msg(trace_channel, 1, "error opening conversion handle from '%s' "
"to '%s': %s", local_charset, "UTF-8", strerror(errno));
return -1;
}
decode_conv = iconv_open(local_charset, "UTF-8");
if (decode_conv == (iconv_t) -1) {
int xerrno = errno;
pr_trace_msg(trace_channel, 1, "error opening conversion handle from '%s' "
"to '%s': %s", "UTF-8", local_charset, strerror(errno));
(void) iconv_close(encode_conv);
encode_conv = (iconv_t) -1;
errno = xerrno;
return -1;
}
return 0;
# else
errno = ENOSYS;
return -1;
#endif /* HAVE_ICONV */
}
char *proxy_ssh_utf8_decode_text(pool *p, const char *text) {
#if defined(PR_USE_NLS) && defined(HAVE_ICONV_H)
size_t inlen, inbuflen, outlen, outbuflen;
char *inbuf, outbuf[PR_TUNABLE_PATH_MAX*2], *res = NULL;
if (p == NULL ||
text == NULL) {
errno = EINVAL;
return NULL;
}
if (decode_conv == (iconv_t) -1) {
pr_trace_msg(trace_channel, 1,
"decoding conversion handle is invalid, unable to decode UTF8 text");
return (char *) text;
}
/* If the local charset matches the remote charset (i.e. local_charset is
* "UTF-8"), then there's no point in converting; the charsets are the
* same. Indeed, on some libiconv implementations, attempting to
* convert between the same charsets results in a tightly spinning CPU
* (see Bug#3272).
*/
if (strcasecmp(local_charset, "UTF-8") == 0) {
return (char *) text;
}
inlen = strlen(text) + 1;
inbuf = pcalloc(p, inlen);
memcpy(inbuf, text, inlen);
inbuflen = inlen;
outbuflen = sizeof(outbuf);
if (utf8_convert(decode_conv, inbuf, &inbuflen, outbuf, &outbuflen) < 0) {
pr_trace_msg(trace_channel, 1, "error decoding text: %s", strerror(errno));
if (pr_trace_get_level(trace_channel) >= 14) {
/* Write out the text we tried (and failed) to decode, in hex. */
register unsigned int i;
unsigned char *raw_text;
size_t len, raw_len;
len = strlen(text);
raw_len = (len * 5) + 1;
raw_text = pcalloc(p, raw_len + 1);
for (i = 0; i < len; i++) {
pr_snprintf((char *) (raw_text + (i * 5)), (raw_len - 1) - (i * 5),
"0x%02x ", (unsigned char) text[i]);
}
pr_trace_msg(trace_channel, 14, "unable to decode text (raw bytes): %s",
raw_text);
}
return (char *) text;
}
outlen = sizeof(outbuf) - outbuflen;
res = pcalloc(p, outlen);
memcpy(res, outbuf, outlen);
return res;
#else
return pstrdup(p, text);
#endif /* !PR_USE_NLS && !HAVE_ICONV_H */
}
char *proxy_ssh_utf8_encode_text(pool *p, const char *text) {
#if defined(PR_USE_NLS) && defined(HAVE_ICONV_H)
size_t inlen, inbuflen, outlen, outbuflen;
char *inbuf, outbuf[PR_TUNABLE_PATH_MAX*2], *res;
if (p == NULL ||
text == NULL) {
errno = EINVAL;
return NULL;
}
if (encode_conv == (iconv_t) -1) {
pr_trace_msg(trace_channel, 1,
"encoding conversion handle is invalid, unable to encode UTF8 text");
return (char *) text;
}
inlen = strlen(text) + 1;
inbuf = pcalloc(p, inlen);
memcpy(inbuf, text, inlen);
inbuflen = inlen;
outbuflen = sizeof(outbuf);
if (utf8_convert(encode_conv, inbuf, &inbuflen, outbuf, &outbuflen) < 0) {
pr_trace_msg(trace_channel, 1, "error encoding text: %s", strerror(errno));
if (pr_trace_get_level(trace_channel) >= 14) {
/* Write out the text we tried (and failed) to encode, in hex. */
register unsigned int i;
unsigned char *raw_text;
size_t len, raw_len;
len = strlen(text);
raw_len = (len * 5) + 1;
raw_text = pcalloc(p, raw_len + 1);
for (i = 0; i < len; i++) {
pr_snprintf((char *) (raw_text + (i * 5)), (raw_len - 1) - (i * 5),
"0x%02x ", (unsigned char) text[i]);
}
pr_trace_msg(trace_channel, 14, "unable to encode text (raw bytes): %s",
raw_text);
}
return (char *) text;
}
outlen = sizeof(outbuf) - outbuflen;
res = pcalloc(p, outlen);
memcpy(res, outbuf, outlen);
return res;
#else
return pstrdup(p, text);
#endif /* !PR_USE_NLS && !HAVE_ICONV_H */
}
#endif /* PR_USE_OPENSSL */
proftpd-mod_proxy-0.9.5/lib/proxy/str.c 0000664 0000000 0000000 00000003466 14757370167 0020156 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy String implementation
* Copyright (c) 2020 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "proxy/str.h"
char *proxy_strnstr(const char *s1, const char *s2, size_t len) {
#if defined(HAVE_STRNSTR)
if (s1 == NULL ||
s2 == NULL ||
len == 0) {
return NULL;
}
/* strnstr(3) does not check this, but it should. */
if (s2[0] == '\0') {
return NULL;
}
return strnstr(s1, s2, len);
#else
register unsigned int i;
size_t s2_len;
if (s1 == NULL ||
s2 == NULL ||
len == 0) {
return NULL;
}
s2_len = strlen(s2);
if (s2_len == 0 ||
s2_len > len) {
return NULL;
}
for (i = 0; i <= (unsigned int) (len - s2_len); i++) {
if (s1[0] == s2[0] &&
strncmp(s1, s2, s2_len) == 0) {
return (char *) s1;
}
s1++;
}
return NULL;
#endif /* HAVE_STRNSTR */
}
proftpd-mod_proxy-0.9.5/lib/proxy/tls.c 0000664 0000000 0000000 00000425737 14757370167 0020161 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy TLS implementation
* Copyright (c) 2015-2024 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "proxy/conn.h"
#include "proxy/netio.h"
#include "proxy/session.h"
#include "proxy/tls.h"
#include "proxy/tls/db.h"
#include "proxy/tls/redis.h"
/* Define if you have the LibreSSL library. */
#if defined(LIBRESSL_VERSION_NUMBER)
# define HAVE_LIBRESSL 1
#endif
static const char *trace_channel = "proxy.tls";
#if defined(PR_USE_OPENSSL)
extern xaset_t *server_list;
static unsigned long tls_opts = 0UL;
static const char *tls_tables_path = NULL;
static struct proxy_tls_datastore tls_ds;
static int tls_engine = PROXY_TLS_ENGINE_AUTO;
static int tls_need_data_prot = TRUE;
static int tls_verify_server = TRUE;
#if defined(PSK_MAX_PSK_LEN)
static const char *tls_psk_name = NULL;
static BIGNUM *tls_psk_bn = NULL;
static int tls_psk_used = FALSE;
# define PROXY_TLS_MIN_PSK_LEN 20
#endif /* PSK support */
/* The SSL_set_session_ticket_ext() API was not fixed until OpenSSL-1.0.2e.
* Thus mod_proxy's caching/reuse of session tickets will not work properly
* before that version.
*/
#if defined(SSL_CTRL_SET_TLSEXT_TICKET_KEYS)
# define PROXY_TLS_USE_SESSION_TICKETS 1
#endif
static const char *timing_channel = "timing";
#define PROXY_TLS_DEFAULT_CIPHER_SUITE "DEFAULT:!ADH:!EXPORT:!DES"
#define PROXY_TLS_NEXT_PROTO "ftp"
/* SSL record/buffer sizes */
#define PROXY_TLS_HANDSHAKE_WRITE_BUFFER_SIZE 1400
/* SSL adaptive buffer sizes/values */
#define PROXY_TLS_DATA_ADAPTIVE_WRITE_MIN_BUFFER_SIZE (4 * 1024)
#define PROXY_TLS_DATA_ADAPTIVE_WRITE_MAX_BUFFER_SIZE (16 * 1024)
#define PROXY_TLS_DATA_ADAPTIVE_WRITE_BOOST_THRESHOLD (1024 * 1024)
#define PROXY_TLS_DATA_ADAPTIVE_WRITE_BOOST_INTERVAL_MS 1000
#if defined(SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS)
static int tls_ssl_opts = (SSL_OP_ALL|SSL_OP_NO_SSLv2|SSL_OP_SINGLE_DH_USE)^SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS;
#else
/* OpenSSL-0.9.6 and earlier (yes, it appears people still have these versions
* installed) does not define the DONT_INSERT_EMPTY_FRAGMENTS option.
*/
static int tls_ssl_opts = SSL_OP_ALL|SSL_OP_NO_SSLv2|SSL_OP_SINGLE_DH_USE;
#endif
static const char *tls_cipher_suite = NULL;
#if OPENSSL_VERSION_NUMBER >= 0x10101000L && \
defined(TLS1_3_VERSION)
static const char *tlsv13_cipher_suite = NULL;
#endif /* TLS1_3_VERSION */
#define PROXY_TLS_VERIFY_DEPTH 9
/* ProxyTLSTimeoutHandshake */
static unsigned int handshake_timeout = 30;
static int handshake_timer_id = -1;
static int handshake_timed_out = FALSE;
#define PROXY_TLS_SHUTDOWN_BIDIRECTIONAL 0x001
/* Stream notes */
#define PROXY_TLS_NETIO_NOTE "mod_proxy.SSL"
#define PROXY_TLS_ADAPTIVE_BYTES_COUNT_KEY "mod_proxy.SSL.adaptive.bytes"
#define PROXY_TLS_ADAPTIVE_BYTES_MS_KEY "mod_proxy.SSL.adaptive.ms"
/* Session caching */
#define PROXY_TLS_MAX_SESSION_AGE 86400
#define PROXY_TLS_MAX_SESSION_COUNT 1000
static SSL_CTX *ssl_ctx = NULL;
static pr_netio_t *tls_ctrl_netio = NULL;
static pr_netio_t *tls_data_netio = NULL;
static SSL *tls_ctrl_ssl = NULL;
static int netio_install_ctrl(void);
static int netio_install_data(void);
/* Indices for data stashed in SSL objects */
#define PROXY_TLS_IDX_TICKET_KEY 2
#define PROXY_TLS_IDX_HAD_TICKET 3
#if !defined(OPENSSL_NO_TLSEXT)
static void tls_tlsext_cb(SSL *, int, int, unsigned char *, int, void *);
#endif /* !OPENSSL_NO_TLSEXT */
static int handshake_timeout_cb(CALLBACK_FRAME) {
handshake_timed_out = TRUE;
return 0;
}
const char *proxy_tls_get_errors(void) {
unsigned int count = 0;
unsigned long error_code;
BIO *bio = NULL;
char *data = NULL;
long datalen;
const char *error_data = NULL, *str = "(unknown)";
int error_flags = 0;
/* Use ERR_print_errors() and a memory BIO to build up a string with
* all of the error messages from the error queue.
*/
error_code = ERR_get_error_line_data(NULL, NULL, &error_data, &error_flags);
if (error_code) {
bio = BIO_new(BIO_s_mem());
}
while (error_code) {
pr_signals_handle();
if (error_flags & ERR_TXT_STRING) {
BIO_printf(bio, "\n (%u) %s [%s]", ++count,
ERR_error_string(error_code, NULL), error_data);
} else {
BIO_printf(bio, "\n (%u) %s", ++count,
ERR_error_string(error_code, NULL));
}
error_data = NULL;
error_flags = 0;
error_code = ERR_get_error_line_data(NULL, NULL, &error_data, &error_flags);
}
datalen = BIO_get_mem_data(bio, &data);
if (data) {
data[datalen] = '\0';
str = pstrdup(session.pool, data);
}
if (bio != NULL) {
BIO_free(bio);
}
return str;
}
static char *tls_x509_name_oneline(X509_NAME *x509_name) {
static char buf[1024] = {'\0'};
/* If we are using OpenSSL 0.9.6 or newer, we want to use
* X509_NAME_print_ex() instead of X509_NAME_oneline().
*/
#if OPENSSL_VERSION_NUMBER < 0x000906000L
memset(&buf, '\0', sizeof(buf));
return X509_NAME_oneline(x509_name, buf, sizeof(buf)-1);
#else
/* Sigh...do it the hard way. */
BIO *mem = BIO_new(BIO_s_mem());
char *data = NULL;
long datalen = 0;
int ok;
ok = X509_NAME_print_ex(mem, x509_name, 0, XN_FLAG_ONELINE);
if (ok) {
datalen = BIO_get_mem_data(mem, &data);
if (data != NULL) {
memset(&buf, '\0', sizeof(buf));
if ((size_t) datalen >= sizeof(buf)) {
datalen = sizeof(buf)-1;
}
memcpy(buf, data, datalen);
buf[datalen] = '\0';
buf[sizeof(buf)-1] = '\0';
BIO_free(mem);
return buf;
}
}
BIO_free(mem);
return NULL;
#endif /* OPENSSL_VERSION_NUMBER >= 0x000906000 */
}
static char *tls_get_subj_name(SSL *ssl) {
X509 *cert;
cert = SSL_get_peer_certificate(ssl);
if (cert != NULL) {
char *subj_name;
subj_name = tls_x509_name_oneline(X509_get_subject_name(cert));
X509_free(cert);
return subj_name;
}
errno = ENOENT;
return NULL;
}
static int tls_get_block(conn_t *conn) {
int flags;
flags = fcntl(conn->rfd, F_GETFL);
if (flags & O_NONBLOCK) {
return FALSE;
}
return TRUE;
}
static void tls_fatal(long error, int lineno) {
switch (error) {
case SSL_ERROR_NONE:
return;
case SSL_ERROR_SSL:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"panic: SSL_ERROR_SSL, line %d: %s", lineno, proxy_tls_get_errors());
break;
case SSL_ERROR_WANT_READ:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"panic: SSL_ERROR_WANT_READ, line %d", lineno);
break;
case SSL_ERROR_WANT_WRITE:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"panic: SSL_ERROR_WANT_WRITE, line %d", lineno);
break;
case SSL_ERROR_WANT_X509_LOOKUP:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"panic: SSL_ERROR_WANT_X509_LOOKUP, line %d", lineno);
break;
case SSL_ERROR_SYSCALL: {
long xerrcode = ERR_get_error();
if (errno == ECONNRESET) {
pr_trace_msg(trace_channel, 17,
"SSL_ERROR_SYSCALL error (errcode %ld) occurred on line %d; ignoring "
"ECONNRESET (%s)", xerrcode, lineno, strerror(errno));
return;
}
/* Check to see if the OpenSSL error queue has info about this. */
if (xerrcode == 0) {
/* The OpenSSL error queue doesn't have any more info, so we'll
* examine the error value itself.
*/
if (errno == EOF) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"panic: SSL_ERROR_SYSCALL, line %d: EOF that violates protocol",
lineno);
} else {
/* Check errno */
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"panic: SSL_ERROR_SYSCALL, line %d: system error: %s", lineno,
strerror(errno));
}
} else {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"panic: SSL_ERROR_SYSCALL, line %d: %s", lineno,
proxy_tls_get_errors());
}
break;
}
case SSL_ERROR_ZERO_RETURN:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"panic: SSL_ERROR_ZERO_RETURN, line %d", lineno);
break;
case SSL_ERROR_WANT_CONNECT:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"panic: SSL_ERROR_WANT_CONNECT, line %d", lineno);
break;
default:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"panic: SSL_ERROR %ld, line %d", error, lineno);
break;
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unexpected OpenSSL error, disconnecting");
pr_log_pri(PR_LOG_WARNING, "%s", MOD_PROXY_VERSION
": unexpected OpenSSL error, disconnecting");
pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION, NULL);
}
static void tls_end_sess(SSL *ssl, int strms, int flags) {
int lineno = 0, res = 0;
int shutdown_state;
BIO *rbio, *wbio;
int bread, bwritten;
unsigned long rbio_rbytes, rbio_wbytes, wbio_rbytes, wbio_wbytes;
if (ssl == NULL) {
return;
}
rbio = SSL_get_rbio(ssl);
rbio_rbytes = BIO_number_read(rbio);
rbio_wbytes = BIO_number_written(rbio);
wbio = SSL_get_wbio(ssl);
wbio_rbytes = BIO_number_read(wbio);
wbio_wbytes = BIO_number_written(wbio);
/* A 'close_notify' alert (SSL shutdown message) may have been previously
* sent to the server via netio_shutdown_cb().
*/
shutdown_state = SSL_get_shutdown(ssl);
if (!(shutdown_state & SSL_SENT_SHUTDOWN)) {
errno = 0;
/* 'close_notify' not already sent; send it now. */
pr_trace_msg(trace_channel, 17,
"shutting down TLS session, 'close_notify' not already sent; "
"sending now");
lineno = __LINE__ + 1;
res = SSL_shutdown(ssl);
}
if (res == 0) {
/* Now call SSL_shutdown() again, but only if necessary. */
if (flags & PROXY_TLS_SHUTDOWN_BIDIRECTIONAL) {
shutdown_state = SSL_get_shutdown(ssl);
res = 1;
if (!(shutdown_state & SSL_RECEIVED_SHUTDOWN)) {
errno = 0;
pr_trace_msg(trace_channel, 17,
"shutting down TLS session, 'close_notify' not received; try again");
lineno = __LINE__ + 1;
res = SSL_shutdown(ssl);
}
}
/* If SSL_shutdown() returned -1 here, an error occurred during the
* shutdown.
*/
if (res < 0) {
long err_code;
err_code = SSL_get_error(ssl, res);
switch (err_code) {
case SSL_ERROR_WANT_READ:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"SSL_shutdown error: WANT_READ");
pr_log_debug(DEBUG0, MOD_PROXY_VERSION
": SSL_shutdown error: WANT_READ");
break;
case SSL_ERROR_WANT_WRITE:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"SSL_shutdown error: WANT_WRITE");
pr_log_debug(DEBUG0, MOD_PROXY_VERSION
": SSL_shutdown error: WANT_WRITE");
break;
case SSL_ERROR_ZERO_RETURN:
/* Clean shutdown, nothing we need to do. */
break;
case SSL_ERROR_SYSCALL:
if (errno != 0 &&
errno != EOF &&
errno != EBADF &&
errno != EPIPE &&
errno != EPERM &&
errno != ENOSYS) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"SSL_shutdown syscall error: %s", strerror(errno));
}
break;
default: {
const char *errors;
errors = proxy_tls_get_errors();
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"SSL_shutdown error [%ld]: %s", err_code, errors);
pr_log_debug(DEBUG0, MOD_PROXY_VERSION
": SSL_shutdown error [%ld]: %s", err_code, errors);
break;
}
}
}
} else if (res < 0) {
long err_code;
err_code = SSL_get_error(ssl, res);
switch (err_code) {
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
case SSL_ERROR_ZERO_RETURN:
/* Clean shutdown, nothing we need to do. The WANT_READ/WANT_WRITE
* error codes crept into OpenSSL 0.9.8m, with changes to make
* SSL_shutdown() work properly for non-blocking sockets. And
* handling these error codes for older OpenSSL versions won't break
* things.
*/
break;
case SSL_ERROR_SYSCALL:
if (errno != 0 &&
errno != EOF &&
errno != EBADF &&
errno != EPIPE &&
errno != EPERM &&
errno != ENOSYS) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"SSL_shutdown syscall error: %s", strerror(errno));
}
break;
default:
if (lineno == 0) {
lineno = __LINE__;
}
tls_fatal(err_code, lineno);
break;
}
}
bread = (BIO_number_read(rbio) - rbio_rbytes) +
(BIO_number_read(wbio) - wbio_rbytes);
bwritten = (BIO_number_written(rbio) - rbio_wbytes) +
(BIO_number_written(wbio) - wbio_wbytes);
/* Manually update session.total_raw_in/out, in order to have %I/%O be
* accurately represented for the raw traffic.
*/
if (bread > 0) {
session.total_raw_in += bread;
}
if (bwritten > 0) {
session.total_raw_out += bwritten;
}
SSL_free(ssl);
if (res >= 0) {
pr_trace_msg(trace_channel, 17, "TLS session cleanly shut down");
}
}
static int tls_readmore(int rfd) {
fd_set rfds;
struct timeval tv;
FD_ZERO(&rfds);
FD_SET(rfd, &rfds);
/* Use a timeout of 15 seconds */
tv.tv_sec = 15;
tv.tv_usec = 0;
return select(rfd + 1, &rfds, NULL, NULL, &tv);
}
static int tls_writemore(int wfd) {
fd_set wfds;
struct timeval tv;
FD_ZERO(&wfds);
FD_SET(wfd, &wfds);
/* Use a timeout of 15 seconds */
tv.tv_sec = 15;
tv.tv_usec = 0;
return select(wfd + 1, NULL, &wfds, NULL, &tv);
}
static ssize_t tls_read(SSL *ssl, void *buf, size_t len,
int nstrm_type, pr_table_t *notes) {
int lineno;
ssize_t count;
read_retry:
pr_signals_handle();
lineno = __LINE__ + 1;
count = SSL_read(ssl, buf, len);
if (count < 0) {
long err;
int fd;
err = SSL_get_error(ssl, count);
fd = SSL_get_fd(ssl);
/* read(2) returns only the generic error number -1 */
count = -1;
switch (err) {
case SSL_ERROR_WANT_READ:
/* OpenSSL needs more data from the wire to finish the current block,
* so we wait a little while for it.
*/
pr_trace_msg(trace_channel, 17,
"WANT_READ encountered while reading SSL data on fd %d, "
"waiting to read data", fd);
err = tls_readmore(fd);
if (err > 0) {
goto read_retry;
} else if (err == 0) {
/* Still missing data after timeout. Simulate an EINTR and return.
*/
errno = EINTR;
/* If err < 0, i.e. some error from the select(), everything is
* already in place; errno is properly set and this function
* returns -1.
*/
break;
}
case SSL_ERROR_WANT_WRITE:
/* OpenSSL needs to write more data to the wire to finish the current
* block, so we wait a little while for it.
*/
pr_trace_msg(trace_channel, 17,
"WANT_WRITE encountered while writing SSL data on fd %d, "
"waiting to send data", fd);
err = tls_writemore(fd);
if (err > 0) {
goto read_retry;
} else if (err == 0) {
/* Still missing data after timeout. Simulate an EINTR and return.
*/
errno = EINTR;
/* If err < 0, i.e. some error from the select(), everything is
* already in place; errno is properly set and this function
* returns -1.
*/
break;
}
case SSL_ERROR_ZERO_RETURN:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"read EOF from client during TLS");
break;
default:
tls_fatal(err, lineno);
break;
}
}
return count;
}
static ssize_t tls_write(SSL *ssl, const void *buf, size_t len,
int nstrm_type, pr_table_t *notes) {
ssize_t count;
int lineno, xerrno = 0;
/* Help ensure we have a clean/trustable errno value. */
errno = 0;
lineno = __LINE__ + 1;
count = SSL_write(ssl, buf, len);
xerrno = errno;
if (count < 0) {
long err = SSL_get_error(ssl, count);
/* write(2) returns only the generic error number -1 */
count = -1;
switch (err) {
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
/* Simulate an EINTR in case OpenSSL wants to write more. */
xerrno = EINTR;
break;
default:
tls_fatal(err, lineno);
break;
}
}
if (nstrm_type == PR_NETIO_STRM_DATA) {
BIO *wbio;
uint64_t *adaptive_bytes_written_ms = NULL, now;
off_t *adaptive_bytes_written_count = NULL;
const void *v;
v = pr_table_get(notes, PROXY_TLS_ADAPTIVE_BYTES_COUNT_KEY, NULL);
if (v != NULL) {
adaptive_bytes_written_count = (off_t *) v;
}
v = pr_table_get(notes, PROXY_TLS_ADAPTIVE_BYTES_MS_KEY, NULL);
if (v != NULL) {
adaptive_bytes_written_ms = (uint64_t *) v;
}
(void) pr_gettimeofday_millis(&now);
wbio = SSL_get_wbio(ssl);
if (adaptive_bytes_written_count != NULL) {
(*adaptive_bytes_written_count) += count;
if (*adaptive_bytes_written_count >= PROXY_TLS_DATA_ADAPTIVE_WRITE_BOOST_THRESHOLD) {
/* Boost the buffer size if we've written more than the "boost"
* threshold.
*/
(void) BIO_set_write_buf_size(wbio,
PROXY_TLS_DATA_ADAPTIVE_WRITE_MAX_BUFFER_SIZE);
}
if (now > (*adaptive_bytes_written_ms + PROXY_TLS_DATA_ADAPTIVE_WRITE_BOOST_INTERVAL_MS)) {
/* If it's been longer than the boost interval since our last write,
* then reset the buffer size to the smaller version, assuming
* congestion (and thus closing of the TCP congestion window).
*/
(void) BIO_set_write_buf_size(wbio,
PROXY_TLS_DATA_ADAPTIVE_WRITE_MIN_BUFFER_SIZE);
*adaptive_bytes_written_count = 0;
}
*adaptive_bytes_written_ms = now;
}
}
errno = xerrno;
return count;
}
static const char *get_printable_san(pool *p, const char *data,
size_t datalen) {
register unsigned int i;
char *ptr, *res;
size_t reslen = 0;
/* First, calculate the length of the resulting printable string we'll
* be generating.
*/
for (i = 0; i < datalen; i++) {
if (PR_ISPRINT(data[i])) {
reslen++;
} else {
reslen += 4;
}
}
/* Leave one space in the allocated string for the terminating NUL. */
ptr = res = pcalloc(p, reslen + 1);
for (i = 0; i < datalen; i++) {
if (PR_ISPRINT(data[i])) {
*(ptr++) = data[i];
} else {
snprintf(ptr, reslen - (ptr - res), "\\x%02x", data[i]);
ptr += 4;
}
}
return res;
}
static int cert_match_wildcard(pool *p, const char *host_name,
const char *cert_name, size_t cert_namelen) {
register unsigned int i;
char *ptr, *ptr2;
unsigned int host_label_count = 1, cert_label_count = 1;
int res;
size_t host_namelen;
/* To be a valid wildcard pattern, we need a '*', a '.', and a top-level
* domain. Thus if the length is less than 4 characters, it CANNOT be
* a wildcard match.
*/
if (cert_namelen < 4) {
return FALSE;
}
/* If no '.', FALSE. */
ptr = strchr(cert_name, '.');
if (ptr == NULL) {
return FALSE;
}
/* If no '*', FALSE. */
ptr2 = strchr(cert_name, '*');
if (ptr2 == NULL) {
return FALSE;
}
/* If more than one '*', FALSE. */
if (strchr(ptr2 + 1, '*') != NULL) {
pr_trace_msg(trace_channel, 17,
"multiple '*' characters found in '%s', unable to use for wildcard "
"matching", cert_name);
return FALSE;
}
/* If not in leftmost label, FALSE. */
if (ptr2 > ptr) {
pr_trace_msg(trace_channel, 17,
"wildcard character in '%s' is NOT in the leftmost label", cert_name);
return FALSE;
}
/* If not the same number of labels, FALSE */
host_namelen = strlen(host_name);
for (i = 0; i < host_namelen; i++) {
if (host_name[i] == '.') {
host_label_count++;
}
}
for (i = 0; i < cert_namelen; i++) {
if (cert_name[i] == '.') {
cert_label_count++;
}
}
if (host_label_count != cert_label_count) {
pr_trace_msg(trace_channel, 17,
"cert name '%s' label count (%d) does not match host name '%s' "
"label count (%d)", cert_name,
cert_label_count, host_name, host_label_count);
return FALSE;
}
res = pr_fnmatch(cert_name, host_name, PR_FNM_NOESCAPE);
if (res == 0) {
return TRUE;
}
pr_trace_msg(trace_channel, 17,
"certificate name with wildcard '%s' did not match host name '%s'",
cert_name, host_name);
return FALSE;
}
static int cert_match_dns_san(pool *p, X509 *cert, const char *dns_name,
int allow_wildcards) {
int matched = FALSE;
#if OPENSSL_VERSION_NUMBER > 0x000907000L
STACK_OF(GENERAL_NAME) *sans;
sans = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
if (sans != NULL) {
register int i;
int nsans = sk_GENERAL_NAME_num(sans);
for (i = 0; i < nsans; i++) {
GENERAL_NAME *alt_name = sk_GENERAL_NAME_value(sans, i);
if (alt_name->type == GEN_DNS) {
char *dns_san;
size_t dns_sanlen;
dns_san = (char *) ASN1_STRING_data(alt_name->d.ia5);
dns_sanlen = strlen(dns_san);
/* Check for subjectAltName values which contain embedded NULs.
* This can cause verification problems (spoofing), e.g. if the
* string is "www.goodguy.com\0www.badguy.com"; the use of strcmp()
* only checks "www.goodguy.com".
*/
if ((size_t) ASN1_STRING_length(alt_name->d.ia5) != dns_sanlen) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"cert dNSName SAN contains embedded NULs, "
"rejecting as possible spoof attempt");
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"suspicious dNSName SAN value: '%s'",
get_printable_san(p, dns_san,
ASN1_STRING_length(alt_name->d.dNSName)));
GENERAL_NAME_free(alt_name);
sk_GENERAL_NAME_free(sans);
return 0;
}
if (strncasecmp(dns_name, dns_san, dns_sanlen + 1) == 0) {
pr_trace_msg(trace_channel, 8,
"found cert dNSName SAN matching '%s'", dns_name);
matched = TRUE;
} else {
if (allow_wildcards) {
matched = cert_match_wildcard(p, dns_name, dns_san, dns_sanlen);
}
}
if (matched == FALSE) {
pr_trace_msg(trace_channel, 9,
"cert dNSName SAN '%s' did not match '%s'", dns_san, dns_name);
}
}
GENERAL_NAME_free(alt_name);
if (matched == TRUE) {
break;
}
}
sk_GENERAL_NAME_free(sans);
}
#endif /* OpenSSL-0.9.7 or later */
return matched;
}
static int cert_match_ip_san(pool *p, X509 *cert, const char *ipstr) {
int matched = FALSE;
STACK_OF(GENERAL_NAME) *sans;
sans = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
if (sans != NULL) {
register int i;
int nsans = sk_GENERAL_NAME_num(sans);
for (i = 0; i < nsans; i++) {
GENERAL_NAME *alt_name = sk_GENERAL_NAME_value(sans, i);
if (alt_name->type == GEN_IPADD) {
unsigned char *san_data = NULL;
int have_ipstr = FALSE, san_datalen;
#if defined(PR_USE_IPV6)
char san_ipstr[INET6_ADDRSTRLEN + 1] = {'\0'};
#else
char san_ipstr[INET_ADDRSTRLEN + 1] = {'\0'};
#endif /* PR_USE_IPV6 */
san_data = ASN1_STRING_data(alt_name->d.ip);
memset(san_ipstr, '\0', sizeof(san_ipstr));
san_datalen = ASN1_STRING_length(alt_name->d.ip);
if (san_datalen == 4) {
/* IPv4 address */
snprintf(san_ipstr, sizeof(san_ipstr)-1, "%u.%u.%u.%u",
san_data[0], san_data[1], san_data[2], san_data[3]);
have_ipstr = TRUE;
#if defined(PR_USE_IPV6)
} else if (san_datalen == 16) {
/* IPv6 address */
if (pr_inet_ntop(AF_INET6, san_data, san_ipstr,
sizeof(san_ipstr)-1) == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to convert cert iPAddress SAN value (length %d) "
"to IPv6 representation: %s", san_datalen, strerror(errno));
} else {
have_ipstr = TRUE;
}
#endif /* PR_USE_IPV6 */
} else {
pr_trace_msg(trace_channel, 3,
"unsupported cert SAN ipAddress length (%d), ignoring",
san_datalen);
continue;
}
if (have_ipstr) {
size_t san_ipstrlen;
san_ipstrlen = strlen(san_ipstr);
if (strncmp(ipstr, san_ipstr, san_ipstrlen + 1) == 0) {
pr_trace_msg(trace_channel, 8,
"found cert iPAddress SAN matching '%s'", ipstr);
matched = TRUE;
} else {
if (san_datalen == 16) {
/* We need to handle the case where the iPAddress SAN might
* have contained an IPv4-mapped IPv6 adress, and we're
* comparing against an IPv4 address.
*/
if (san_ipstrlen > 7 &&
strncasecmp(san_ipstr, "::ffff:", 7) == 0) {
if (strncmp(ipstr, san_ipstr + 7, san_ipstrlen - 6) == 0) {
pr_trace_msg(trace_channel, 8,
"found cert iPAddress SAN '%s' matching '%s'",
san_ipstr, ipstr);
matched = TRUE;
}
}
} else {
pr_trace_msg(trace_channel, 9,
"cert iPAddress SAN '%s' did not match '%s'", san_ipstr, ipstr);
}
}
}
}
GENERAL_NAME_free(alt_name);
if (matched == TRUE) {
break;
}
}
sk_GENERAL_NAME_free(sans);
}
return matched;
}
static int cert_match_cn(pool *p, X509 *cert, const char *name,
int allow_wildcards) {
int matched = FALSE, idx = -1;
X509_NAME *subj_name = NULL;
X509_NAME_ENTRY *cn_entry = NULL;
ASN1_STRING *cn_asn1 = NULL;
char *cn_str = NULL;
size_t cn_len = 0;
/* Find the position of the CommonName (CN) field within the Subject of
* the cert.
*/
subj_name = X509_get_subject_name(cert);
if (subj_name == NULL) {
pr_trace_msg(trace_channel, 12,
"unable to check certificate CommonName against '%s': "
"unable to get Subject", name);
return 0;
}
idx = X509_NAME_get_index_by_NID(subj_name, NID_commonName, -1);
if (idx < 0) {
pr_trace_msg(trace_channel, 12,
"unable to check certificate CommonName against '%s': "
"no CommonName attribute found", name);
return 0;
}
cn_entry = X509_NAME_get_entry(subj_name, idx);
if (cn_entry == NULL) {
pr_trace_msg(trace_channel, 12,
"unable to check certificate CommonName against '%s': "
"error obtaining CommonName attribute found: %s", name,
proxy_tls_get_errors());
return 0;
}
/* Convert the CN field to a string, by way of an ASN1 object. */
cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry);
if (cn_asn1 == NULL) {
pr_trace_msg(trace_channel, 12,
"unable to check certificate CommonName against '%s': "
"error converting CommonName attribute to ASN.1: %s", name,
proxy_tls_get_errors());
return 0;
}
cn_str = (char *) ASN1_STRING_data(cn_asn1);
/* Check for CommonName values which contain embedded NULs. This can cause
* verification problems (spoofing), e.g. if the string is
* "www.goodguy.com\0www.badguy.com"; the use of strcmp() only checks
* "www.goodguy.com".
*/
cn_len = strlen(cn_str);
if ((size_t) ASN1_STRING_length(cn_asn1) != cn_len) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"cert CommonName contains embedded NULs, rejecting as possible spoof "
"attempt");
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"suspicious CommonName value: '%s'",
get_printable_san(p, (const char *) cn_str, ASN1_STRING_length(cn_asn1)));
return 0;
}
/* Yes, this is deliberately a case-insensitive comparison. Most CNs
* contain a hostname (case-insensitive); if they contain an IP address,
* the case-insensitivity won't hurt anything. In fact, it's needed for
* e.g. IPv6 addresses.
*/
if (strncasecmp(name, cn_str, cn_len + 1) == 0) {
pr_trace_msg(trace_channel, 12, "cert CommonName '%s' matches '%s'", cn_str,
name);
matched = TRUE;
} else {
if (allow_wildcards) {
matched = cert_match_wildcard(p, name, cn_str, cn_len);
}
}
if (matched == FALSE) {
pr_trace_msg(trace_channel, 12, "cert CommonName '%s' does NOT match '%s'",
cn_str, name);
}
return matched;
}
static int check_server_cert(SSL *ssl, conn_t *conn, const char *host_name) {
X509 *cert = NULL;
int ok = -1;
long verify_result;
/* Only perform these more stringent checks if asked to verify servers. */
if (tls_verify_server == FALSE) {
return 0;
}
/* Check SSL_get_verify_result. */
verify_result = SSL_get_verify_result(ssl);
if (verify_result != X509_V_OK) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to verify '%s' server certificate: %s",
conn->remote_name, X509_verify_cert_error_string(verify_result));
return -1;
}
cert = SSL_get_peer_certificate(ssl);
if (cert == NULL) {
/* This can be null in the case where some anonymous (insecure)
* cipher suite was used.
*/
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to verify '%s': server did not provide certificate",
conn->remote_name);
# if defined(PSK_MAX_PSK_LEN)
if (tls_psk_used) {
return 0;
}
# endif /* PSK support */
return -1;
}
/* XXX If using OpenSSL-1.0.2/1.1.0, we might be able to use:
* X509_match_host() and X509_match_ip()/X509_match_ip_asc().
*/
ok = cert_match_ip_san(conn->pool, cert,
pr_netaddr_get_ipstr(conn->remote_addr));
if (ok == 0) {
ok = cert_match_cn(conn->pool, cert,
pr_netaddr_get_ipstr(conn->remote_addr), FALSE);
}
if (ok == 0) {
ok = cert_match_dns_san(conn->pool, cert, host_name, TRUE);
if (ok == 0) {
ok = cert_match_cn(conn->pool, cert, host_name, TRUE);
}
}
X509_free(cert);
return ok;
}
static void stash_stream_ssl(pr_netio_stream_t *nstrm, SSL *ssl) {
if (pr_table_add(nstrm->notes,
pstrdup(nstrm->strm_pool, PROXY_TLS_NETIO_NOTE), ssl,
sizeof(SSL *)) < 0) {
if (errno != EEXIST) {
pr_trace_msg(trace_channel, 4,
"error stashing '%s' note on %s %s stream: %s",
PROXY_TLS_NETIO_NOTE,
nstrm->strm_type == PR_NETIO_STRM_CTRL ? "control" : "data",
nstrm->strm_mode == PR_NETIO_IO_RD ? "read" : "write",
strerror(errno));
}
}
}
static int tls_verify_cb(int ok, X509_STORE_CTX *ctx) {
X509 *cert;
cert = X509_STORE_CTX_get_current_cert(ctx);
if (!ok) {
int verify_depth, verify_error;
verify_depth = X509_STORE_CTX_get_error_depth(ctx);
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error: unable to verify server certificate at depth %d",
verify_depth);
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error: cert subject: %s",
tls_x509_name_oneline(X509_get_subject_name(cert)));
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error: cert issuer: %s",
tls_x509_name_oneline(X509_get_issuer_name(cert)));
/* Catch a too long certificate chain here. */
if (verify_depth > PROXY_TLS_VERIFY_DEPTH) {
X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_CHAIN_TOO_LONG);
}
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
verify_error = X509_STORE_CTX_get_error(ctx);
#else
verify_error = ctx->error;
#endif /* OpenSSL-1.1.x and later */
switch (verify_error) {
case X509_V_ERR_CERT_CHAIN_TOO_LONG:
case X509_V_ERR_CERT_NOT_YET_VALID:
case X509_V_ERR_CERT_HAS_EXPIRED:
case X509_V_ERR_CERT_REVOKED:
case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
case X509_V_ERR_APPLICATION_VERIFICATION:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"server certificate failed verification: %s",
X509_verify_cert_error_string(verify_error));
ok = 0;
break;
case X509_V_ERR_INVALID_PURPOSE: {
register int i;
int count = X509_PURPOSE_get_count();
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"server certificate failed verification: %s",
X509_verify_cert_error_string(verify_error));
for (i = 0; i < count; i++) {
X509_PURPOSE *purp = X509_PURPOSE_get0(i);
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
" purpose #%d: %s", i+1, X509_PURPOSE_get0_name(purp));
}
ok = 0;
break;
}
default:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error verifying server certificate: [%d] %s",
verify_error, X509_verify_cert_error_string(verify_error));
ok = 0;
break;
}
if (tls_verify_server == FALSE) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"ProxyTLSVerifyServer off, ignoring failed certificate verification");
ok = 1;
}
} else {
if (tls_opts & PROXY_TLS_OPT_ENABLE_DIAGS) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.verify]: cert subject: %s",
tls_x509_name_oneline(X509_get_subject_name(cert)));
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.verify]: cert issuer: %s",
tls_x509_name_oneline(X509_get_issuer_name(cert)));
}
}
return ok;
}
static int tls_get_cached_sess(pool *p, SSL *ssl, const char *host, int port) {
char port_str[32], *sess_key = NULL;
SSL_SESSION *sess = NULL;
if (tls_opts & PROXY_TLS_OPT_NO_SESSION_CACHE) {
if (tls_opts & PROXY_TLS_OPT_NO_SESSION_TICKETS) {
pr_trace_msg(trace_channel, 19,
"NoSessionCache and NoSessionTickets ProxyTLSOptions in effect, "
"not using cached SSL sessions");
SSL_set_options(ssl, SSL_OP_NO_TICKET);
return 0;
}
}
memset(port_str, '\0', sizeof(port_str));
snprintf(port_str, sizeof(port_str)-1, "%d", port);
sess_key = pstrcat(p, "ftp://", host, ":", port_str, NULL);
pr_trace_msg(trace_channel, 19,
"looking for cached SSL session using key '%s'", sess_key);
sess = (tls_ds.get_sess)(p, tls_ds.dsh, sess_key);
if (sess != NULL) {
long sess_age;
time_t now;
now = time(NULL);
sess_age = now - SSL_SESSION_get_time(sess);
if (sess_age >= PROXY_TLS_MAX_SESSION_AGE) {
pr_trace_msg(trace_channel, 9, "cached SSL session expired, removing");
(void) (tls_ds.remove_sess)(p, tls_ds.dsh, sess_key);
SSL_SESSION_free(sess);
errno = ENOENT;
sess = NULL;
}
}
if (sess == NULL) {
if (errno == ENOENT) {
pr_trace_msg(trace_channel, 19,
"no cached sessions found for key '%s'", sess_key);
} else {
pr_trace_msg(trace_channel, 9,
"error getting cached session using key '%s': %s", sess_key,
strerror(errno));
}
return 0;
}
pr_trace_msg(trace_channel, 12,
"found cached SSL session using key '%s'", sess_key);
SSL_set_session(ssl, sess);
SSL_SESSION_free(sess);
return 0;
}
static int tls_add_cached_sess(pool *p, SSL *ssl, const char *host, int port) {
char port_str[32], *sess_key = NULL;
SSL_SESSION *sess = NULL;
int res, sess_count, xerrno = 0;
time_t now, sess_age;
if (tls_opts & PROXY_TLS_OPT_NO_SESSION_CACHE) {
if (tls_opts & PROXY_TLS_OPT_NO_SESSION_TICKETS) {
pr_trace_msg(trace_channel, 19,
"NoSessionCache and NoSessionTickets ProxyTLSOptions in effect, "
"not caching SSL sessions");
return 0;
}
}
sess_count = (tls_ds.count_sess)(p, tls_ds.dsh);
if (sess_count < 0) {
return -1;
}
if (sess_count >= PROXY_TLS_MAX_SESSION_COUNT) {
pr_trace_msg(trace_channel, 14,
"Maximum number of cached sessions (%d) reached, not caching SSL session",
PROXY_TLS_MAX_SESSION_COUNT);
return 0;
}
sess = SSL_get1_session(ssl);
/* If this session is already past our expiration policy, ignore it. */
now = time(NULL);
sess_age = now - SSL_SESSION_get_time(sess);
if (sess_age >= PROXY_TLS_MAX_SESSION_AGE) {
pr_trace_msg(trace_channel, 9,
"SSL session has already expired, not caching");
SSL_SESSION_free(sess);
return 0;
}
memset(port_str, '\0', sizeof(port_str));
snprintf(port_str, sizeof(port_str)-1, "%d", port);
sess_key = pstrcat(p, "ftp://", host, ":", port_str, NULL);
pr_trace_msg(trace_channel, 19,
"caching SSL session using key '%s'", sess_key);
res = (tls_ds.add_sess)(p, tls_ds.dsh, sess_key, sess);
xerrno = errno;
SSL_SESSION_free(sess);
if (res < 0) {
pr_trace_msg(trace_channel, 9,
"error storing cached SSL session using key '%s': %s", sess_key,
strerror(xerrno));
} else {
pr_trace_msg(trace_channel, 19,
"successfully cached SSL session using key '%s'", sess_key);
}
return 0;
}
static int tls_connect(conn_t *conn, const char *host_name,
pr_netio_stream_t *nstrm) {
int blocking, res = 0, xerrno = 0;
char *subj = NULL;
SSL *ssl = NULL;
BIO *rbio = NULL, *wbio = NULL;
if (ssl_ctx == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to start session: null SSL_CTX");
errno = EPERM;
return -1;
}
ssl = SSL_new(ssl_ctx);
if (ssl == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error: unable to allocate SSL session: %s", proxy_tls_get_errors());
return -2;
}
SSL_set_verify(ssl, SSL_VERIFY_PEER, tls_verify_cb);
/* This works with either rfd or wfd (I hope). */
rbio = BIO_new_socket(conn->rfd, FALSE);
wbio = BIO_new_socket(conn->rfd, FALSE);
SSL_set_bio(ssl, rbio, wbio);
#if !defined(OPENSSL_NO_TLSEXT)
SSL_set_tlsext_debug_callback(ssl, tls_tlsext_cb);
/* We should be a well-behaved TLS client, and NOT send an SNI value
* that is an IP address. Per RFC 6066, literal IPv4/IPv6 addresses are NOT
* permitted in the SNI.
*/
if (pr_netaddr_is_v4(host_name) != TRUE &&
pr_netaddr_is_v6(host_name) != TRUE) {
pr_trace_msg(trace_channel, 9, "sending SNI '%s'", host_name);
SSL_set_tlsext_host_name(ssl, host_name);
} else {
pr_trace_msg(trace_channel, 9, "skipping sending of IP address SNI '%s'",
host_name);
}
# if defined(TLSEXT_STATUSTYPE_ocsp)
pr_trace_msg(trace_channel, 9, "requesting stapled OCSP response");
SSL_set_tlsext_status_type(ssl, TLSEXT_STATUSTYPE_ocsp);
# endif /* OCSP support */
#endif /* OPENSSL_NO_TLSEXT */
if (nstrm->strm_type == PR_NETIO_STRM_DATA) {
/* If we're opening a data connection, reuse the SSL data from the
* session on the control connection (including any session IDs and/or
* tickets).
*/
SSL_copy_session_id(ssl, tls_ctrl_ssl);
} else if (nstrm->strm_type == PR_NETIO_STRM_CTRL) {
tls_get_cached_sess(nstrm->strm_pool, ssl, host_name, conn->remote_port);
# if !defined(PROXY_TLS_USE_SESSION_TICKETS)
SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_TICKET);
# endif /* Session ticket support */
}
/* If configured, set a timer for the handshake. */
if (handshake_timeout) {
handshake_timer_id = pr_timer_add(handshake_timeout, -1,
&proxy_module, handshake_timeout_cb, "SSL/TLS handshake");
}
/* Make sure that TCP_NODELAY is enabled for the handshake. */
(void) pr_inet_set_proto_nodelay(conn->pool, conn, 1);
/* Make sure that TCP_CORK (aka TCP_NOPUSH) is DISABLED for the handshake. */
if (pr_inet_set_proto_cork(conn->wfd, 0) < 0) {
pr_trace_msg(trace_channel, 9,
"error disabling TCP_CORK on %s conn: %s",
nstrm->strm_type == PR_NETIO_STRM_CTRL ? "control" : "data",
strerror(errno));
}
connect_retry:
blocking = tls_get_block(conn);
if (blocking) {
/* Put the connection in non-blocking mode for the duration of the
* SSL handshake. This lets us handle EAGAIN/retries better (i.e.
* without spinning in a tight loop and consuming the CPU).
*/
if (pr_inet_set_nonblock(conn->pool, conn) < 0) {
pr_trace_msg(trace_channel, 3,
"error making connection nonblocking: %s", strerror(errno));
}
}
pr_signals_handle();
res = SSL_connect(ssl);
if (res == -1) {
xerrno = errno;
}
if (blocking) {
/* Return the connection to blocking mode. */
if (pr_inet_set_block(conn->pool, conn) < 0) {
pr_trace_msg(trace_channel, 3,
"error making connection blocking: %s", strerror(errno));
}
}
if (res < 1) {
const char *msg = "unable to connect using TLS";
int errcode, shutdown_ssl;
errcode = SSL_get_error(ssl, res);
shutdown_ssl = TRUE;
pr_signals_handle();
if (handshake_timed_out) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"TLS negotiation timed out (%u seconds)", handshake_timeout);
tls_end_sess(ssl, nstrm->strm_type, 0);
return -4;
}
switch (errcode) {
case SSL_ERROR_WANT_READ:
pr_trace_msg(trace_channel, 17,
"WANT_READ encountered while connecting on fd %d, "
"waiting to read data", conn->rfd);
tls_readmore(conn->rfd);
goto connect_retry;
case SSL_ERROR_WANT_WRITE:
pr_trace_msg(trace_channel, 17,
"WANT_WRITE encountered while connecting on fd %d, "
"waiting to read data", conn->rfd);
tls_writemore(conn->rfd);
goto connect_retry;
case SSL_ERROR_ZERO_RETURN:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"%s: TLS connection closed", msg);
break;
case SSL_ERROR_WANT_X509_LOOKUP:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"%s: needs X509 lookup", msg);
break;
case SSL_ERROR_SYSCALL: {
/* Check to see if the OpenSSL error queue has info about this. */
int xerrcode = ERR_get_error();
if (xerrcode == 0) {
/* The OpenSSL error queue doesn't have any more info, so we'll
* examine the SSL_connect() return value itself.
*/
if (res == 0) {
/* EOF */
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"%s: received EOF that violates protocol", msg);
} else if (res == -1) {
/* Check errno */
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"%s: system call error: [%d] %s", msg, xerrno,
strerror(xerrno));
}
} else {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"%s: system call error: %s", msg, proxy_tls_get_errors());
}
shutdown_ssl = FALSE;
break;
}
case SSL_ERROR_SSL:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"%s: protocol error: %s", msg, proxy_tls_get_errors());
shutdown_ssl = FALSE;
break;
}
if (nstrm->strm_type == PR_NETIO_STRM_CTRL) {
pr_event_generate("mod_proxy.tls-ctrl-handshake-failed", &errcode);
} else if (nstrm->strm_type == PR_NETIO_STRM_DATA) {
pr_event_generate("mod_proxy.tls-data-handshake-failed", &errcode);
}
if (shutdown_ssl == TRUE) {
tls_end_sess(ssl, nstrm->strm_type, 0);
}
return -3;
}
/* Disable the handshake timer. */
pr_timer_remove(handshake_timer_id, &proxy_module);
/* Disable TCP_NODELAY, now that the handshake is done. */
(void) pr_inet_set_proto_nodelay(conn->pool, conn, 0);
if (nstrm->strm_type == PR_NETIO_STRM_DATA) {
/* Reenable TCP_CORK (aka TCP_NOPUSH), now that the handshake is done. */
if (pr_inet_set_proto_cork(conn->wfd, 1) < 0) {
pr_trace_msg(trace_channel, 9,
"error re-enabling TCP_CORK on data conn: %s", strerror(errno));
}
} else if (nstrm->strm_type == PR_NETIO_STRM_CTRL) {
int reused;
/* Only try to cache the new SSL session if we actually did create a
* new session. Otherwise, leave the previously cached session as is.
*/
reused = SSL_session_reused(ssl);
if (reused == 0) {
tls_add_cached_sess(nstrm->strm_pool, ssl, host_name, conn->remote_port);
}
}
/* Manually update the raw bytes counters with the network IO from the
* SSL handshake.
*/
session.total_raw_in += (BIO_number_read(rbio) +
BIO_number_read(wbio));
session.total_raw_out += (BIO_number_written(rbio) +
BIO_number_written(wbio));
/* Stash the SSL pointer in BOTH input and output streams for this
* connection.
*/
stash_stream_ssl(conn->instrm, ssl);
stash_stream_ssl(conn->outstrm, ssl);
if (nstrm->strm_type == PR_NETIO_STRM_DATA) {
pr_buffer_t *strm_buf;
/* Clear any data from the NetIO stream buffers which may have been read
* in before the SSL/TLS handshake occurred (Bug#3624).
*/
strm_buf = nstrm->strm_buf;
if (strm_buf != NULL) {
strm_buf->current = NULL;
strm_buf->remaining = strm_buf->buflen;
}
} else {
/* Stash a pointer to the control connection SSL object. */
tls_ctrl_ssl = ssl;
}
subj = tls_get_subj_name(ssl);
if (subj != NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"Server: %s", subj);
}
if (check_server_cert(ssl, conn, host_name) < 0) {
tls_end_sess(ssl, nstrm->strm_type, 0);
return -1;
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"%s connection created, using cipher %s (%d bits)",
SSL_get_version(ssl), SSL_get_cipher_name(ssl),
SSL_get_cipher_bits(ssl, NULL));
return 0;
}
static int tls_seed_prng(void) {
char *heapdata, stackdata[1024];
FILE *fp = NULL;
pid_t pid;
struct timeval tv;
#if OPENSSL_VERSION_NUMBER >= 0x00905100L
if (RAND_status() == 1) {
/* PRNG already well-seeded. */
return 0;
}
#endif
pr_log_debug(DEBUG9, MOD_PROXY_VERSION
": PRNG not seeded with enough data, looking for entropy sources");
/* If the device '/dev/urandom' is present, OpenSSL uses it by default.
* Check if it's present, else we have to make random data ourselves.
*/
fp = fopen("/dev/urandom", "r");
if (fp != NULL) {
fclose(fp);
pr_log_debug(DEBUG9, MOD_PROXY_VERSION
": device /dev/urandom is present, assuming OpenSSL will use that "
"for PRNG data");
return 0;
}
/* Not enough entropy; trying providing some. */
gettimeofday(&tv, NULL);
RAND_seed(&(tv.tv_sec), sizeof(tv.tv_sec));
RAND_seed(&(tv.tv_usec), sizeof(tv.tv_usec));
pid = getpid();
RAND_seed(&pid, sizeof(pid_t));
RAND_seed(stackdata, sizeof(stackdata));
heapdata = malloc(sizeof(stackdata));
if (heapdata != NULL) {
RAND_seed(heapdata, sizeof(stackdata));
free(heapdata);
}
#if OPENSSL_VERSION_NUMBER >= 0x00905100L
if (RAND_status() == 0) {
/* PRNG still badly seeded. */
errno = EPERM;
return -1;
}
#endif
return 0;
}
/* NetIO callbacks */
static void netio_abort_cb(pr_netio_stream_t *nstrm) {
nstrm->strm_flags |= PR_NETIO_SESS_ABORT;
}
static int netio_close_cb(pr_netio_stream_t *nstrm) {
int res = 0;
SSL *ssl = NULL;
ssl = (SSL *) pr_table_get(nstrm->notes, PROXY_TLS_NETIO_NOTE, NULL);
if (ssl != NULL) {
if (nstrm->strm_type == PR_NETIO_STRM_CTRL &&
nstrm->strm_mode == PR_NETIO_IO_WR) {
const struct proxy_session *proxy_sess;
const char *host_name;
int remote_port;
proxy_sess = pr_table_get(session.notes, "mod_proxy.proxy-session", NULL);
if (proxy_sess == NULL) {
/* Unlikely to occur. */
pr_trace_msg(trace_channel, 1, "missing proxy session unexpectedly");
errno = EINVAL;
return -1;
}
host_name = proxy_conn_get_host(proxy_sess->dst_pconn);
remote_port = proxy_conn_get_port(proxy_sess->dst_pconn);
/* Cache the SSL session here, as it may have changed (e.g. due to
* renegotiations) during the lifetime of the control connection.
*/
tls_add_cached_sess(nstrm->strm_pool, ssl, host_name, remote_port);
pr_table_remove(nstrm->notes, PROXY_TLS_NETIO_NOTE, NULL);
tls_end_sess(ssl, nstrm->strm_type, 0);
proxy_sess_state &= ~PROXY_SESS_STATE_BACKEND_HAS_CTRL_TLS;
}
if (nstrm->strm_type == PR_NETIO_STRM_DATA &&
nstrm->strm_mode == PR_NETIO_IO_WR) {
pr_table_remove(nstrm->notes, PROXY_TLS_NETIO_NOTE, NULL);
}
}
res = close(nstrm->strm_fd);
nstrm->strm_fd = -1;
return res;
}
static pr_netio_stream_t *netio_open_cb(pr_netio_stream_t *nstrm, int fd,
int mode) {
nstrm->strm_fd = fd;
nstrm->strm_mode = mode;
return nstrm;
}
static int netio_poll_cb(pr_netio_stream_t *nstrm) {
fd_set rfds, wfds;
struct timeval tval;
FD_ZERO(&rfds);
FD_ZERO(&wfds);
if (nstrm->strm_mode == PR_NETIO_IO_RD) {
FD_SET(nstrm->strm_fd, &rfds);
} else {
FD_SET(nstrm->strm_fd, &wfds);
}
tval.tv_sec = (nstrm->strm_flags & PR_NETIO_SESS_INTR) ?
nstrm->strm_interval : 10;
tval.tv_usec = 0;
return select(nstrm->strm_fd + 1, &rfds, &wfds, NULL, &tval);
}
static int netio_postopen_cb(pr_netio_stream_t *nstrm) {
/* If this stream is for writing, and TLS is wanted/required, then perform
* a TLS handshake.
*/
if (tls_engine == PROXY_TLS_ENGINE_OFF) {
return 0;
}
if (nstrm->strm_mode == PR_NETIO_IO_WR) {
const struct proxy_session *proxy_sess;
uint64_t *adaptive_ms = NULL, start_ms;
off_t *adaptive_bytes = NULL;
conn_t *conn = NULL;
const char *host_name;
if (nstrm->strm_type == PR_NETIO_STRM_DATA) {
if (tls_need_data_prot == FALSE) {
pr_trace_msg(trace_channel, 17,
"data connection does not require SSL/TLS, skipping handshake");
return 0;
}
/* This callback may be invoked by `pr_netio_postopen` for a frontend
* data conn, as when we already have a backend data conn using TLS
* already established.
*
* This happens when the frontend is NOT using TLS, and is using passive
* data transfers, but the backed IS using TLS, and is using active
* data transfers.
*
* NOTE: This is still a bug; mod_proxy is calling pr_netio_postopen(),
* which should not be invoking this callback.
*/
if (proxy_sess_state & PROXY_SESS_STATE_BACKEND_HAS_DATA_TLS) {
pr_trace_msg(trace_channel, 27,
"data connection already using SSL/TLS, skipping handshake");
return 0;
}
}
proxy_sess = pr_table_get(session.notes, "mod_proxy.proxy-session", NULL);
if (proxy_sess == NULL) {
/* Unlikely to occur. */
pr_trace_msg(trace_channel, 1, "missing proxy session unexpectedly");
errno = EINVAL;
return -1;
}
/* TODO: Handle SSCN_MODE CLIENT (connect), SERVER (accept) */
pr_gettimeofday_millis(&start_ms);
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"starting TLS negotiation on %s connection",
nstrm->strm_type == PR_NETIO_STRM_CTRL ? "control" : "data");
if (nstrm->strm_type == PR_NETIO_STRM_CTRL) {
conn = proxy_sess->backend_ctrl_conn;
} else {
conn = proxy_sess->backend_data_conn;
}
host_name = proxy_conn_get_host(proxy_sess->dst_pconn);
if (tls_connect(conn, host_name, nstrm) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to open %s connection to %s: TLS negotiation failed",
nstrm->strm_type == PR_NETIO_STRM_CTRL ? "control" : "data", host_name);
errno = EPERM;
return -1;
}
if (pr_trace_get_level(timing_channel) >= 4) {
unsigned long elapsed_ms;
uint64_t finish_ms;
pr_gettimeofday_millis(&finish_ms);
elapsed_ms = (unsigned long) (finish_ms - start_ms);
pr_trace_msg(timing_channel, 4,
"TLS %s handshake duration: %lu ms",
nstrm->strm_type == PR_NETIO_STRM_CTRL ? "control" : "data",
elapsed_ms);
}
adaptive_ms = pcalloc(nstrm->strm_pool, sizeof(uint64_t));
if (pr_table_add(nstrm->notes, PROXY_TLS_ADAPTIVE_BYTES_MS_KEY,
adaptive_ms, sizeof(uint64_t)) < 0) {
pr_trace_msg(trace_channel, 3,
"error stashing '%s' stream note: %s", PROXY_TLS_ADAPTIVE_BYTES_MS_KEY,
strerror(errno));
}
adaptive_bytes = pcalloc(nstrm->strm_pool, sizeof(off_t));
if (pr_table_add(nstrm->notes, PROXY_TLS_ADAPTIVE_BYTES_COUNT_KEY,
adaptive_bytes, sizeof(off_t)) < 0) {
pr_trace_msg(trace_channel, 3,
"error stashing '%s' stream note: %s",
PROXY_TLS_ADAPTIVE_BYTES_COUNT_KEY, strerror(errno));
}
if (netio_install_data() < 0) {
pr_trace_msg(trace_channel, 1,
"error installing data connection NetIO: %s", strerror(errno));
}
if (nstrm->strm_type == PR_NETIO_STRM_CTRL) {
proxy_sess_state |= PROXY_SESS_STATE_BACKEND_HAS_CTRL_TLS;
} else if (nstrm->strm_type == PR_NETIO_STRM_DATA) {
proxy_sess_state |= PROXY_SESS_STATE_BACKEND_HAS_DATA_TLS;
}
}
return 0;
}
static int netio_read_cb(pr_netio_stream_t *nstrm, char *buf, size_t buflen) {
SSL *ssl = NULL;
ssl = (SSL *) pr_table_get(nstrm->notes, PROXY_TLS_NETIO_NOTE, NULL);
if (ssl != NULL) {
BIO *rbio, *wbio;
int bread = 0, bwritten = 0;
ssize_t res = 0;
unsigned long rbio_rbytes, rbio_wbytes, wbio_rbytes, wbio_wbytes;
rbio = SSL_get_rbio(ssl);
rbio_rbytes = BIO_number_read(rbio);
rbio_wbytes = BIO_number_written(rbio);
wbio = SSL_get_wbio(ssl);
wbio_rbytes = BIO_number_read(wbio);
wbio_wbytes = BIO_number_written(wbio);
res = tls_read(ssl, buf, buflen, nstrm->strm_type, nstrm->notes);
bread = (BIO_number_read(rbio) - rbio_rbytes) +
(BIO_number_read(wbio) - wbio_rbytes);
bwritten = (BIO_number_written(rbio) - rbio_wbytes) +
(BIO_number_written(wbio) - wbio_wbytes);
/* Manually update session.total_raw_in with the difference between
* the raw bytes read in versus the non-SSL bytes read in, in order to
* have %I be accurately represented for the raw traffic.
*/
if (res > 0) {
session.total_raw_in += (bread - res);
}
/* Manually update session.total_raw_out, in order to have %O be
* accurately represented for the raw traffic.
*/
if (bwritten > 0) {
session.total_raw_out += bwritten;
}
return res;
}
return read(nstrm->strm_fd, buf, buflen);
}
static pr_netio_stream_t *netio_reopen_cb(pr_netio_stream_t *nstrm, int fd,
int mode) {
if (nstrm->strm_fd != -1) {
(void) close(nstrm->strm_fd);
}
nstrm->strm_fd = fd;
nstrm->strm_mode = mode;
return nstrm;
}
static int netio_shutdown_cb(pr_netio_stream_t *nstrm, int how) {
if (how == 1 ||
how == 2) {
/* Closing this stream for writing; we need to send the 'close_notify'
* alert first, so that the client knows, at the application layer,
* that the SSL/TLS session is shutting down.
*/
if (nstrm->strm_mode == PR_NETIO_IO_WR &&
(nstrm->strm_type == PR_NETIO_STRM_CTRL ||
nstrm->strm_type == PR_NETIO_STRM_DATA)) {
SSL *ssl;
/* If we do not have TLS on this connection, then don't try to do
* a TLS shutdown.
*/
if (nstrm->strm_type == PR_NETIO_STRM_CTRL) {
if (!(proxy_sess_state & PROXY_SESS_STATE_BACKEND_HAS_CTRL_TLS)) {
return shutdown(nstrm->strm_fd, how);
}
} else if (nstrm->strm_type == PR_NETIO_STRM_DATA) {
if (!(proxy_sess_state & PROXY_SESS_STATE_BACKEND_HAS_DATA_TLS)) {
return shutdown(nstrm->strm_fd, how);
}
}
ssl = (SSL *) pr_table_get(nstrm->notes, PROXY_TLS_NETIO_NOTE, NULL);
if (ssl != NULL) {
BIO *rbio, *wbio;
int bread = 0, bwritten = 0;
unsigned long rbio_rbytes, rbio_wbytes, wbio_rbytes, wbio_wbytes;
rbio = SSL_get_rbio(ssl);
rbio_rbytes = BIO_number_read(rbio);
rbio_wbytes = BIO_number_written(rbio);
wbio = SSL_get_wbio(ssl);
wbio_rbytes = BIO_number_read(wbio);
wbio_wbytes = BIO_number_written(wbio);
if (!(SSL_get_shutdown(ssl) & SSL_SENT_SHUTDOWN)) {
/* We haven't sent a 'close_notify' alert yet; do so now. */
SSL_shutdown(ssl);
}
if (nstrm->strm_type == PR_NETIO_STRM_DATA) {
proxy_sess_state &= ~PROXY_SESS_STATE_BACKEND_HAS_DATA_TLS;
}
bread = (BIO_number_read(rbio) - rbio_rbytes) +
(BIO_number_read(wbio) - wbio_rbytes);
bwritten = (BIO_number_written(rbio) - rbio_wbytes) +
(BIO_number_written(wbio) - wbio_wbytes);
/* Manually update session.total_raw_in/out, in order to have %I/%O be
* accurately represented for the raw traffic.
*/
if (bread > 0) {
session.total_raw_in += bread;
}
if (bwritten > 0) {
session.total_raw_out += bwritten;
}
}
}
}
return shutdown(nstrm->strm_fd, how);
}
static int netio_write_cb(pr_netio_stream_t *nstrm, char *buf, size_t buflen) {
SSL *ssl;
ssl = (SSL *) pr_table_get(nstrm->notes, PROXY_TLS_NETIO_NOTE, NULL);
if (ssl != NULL) {
BIO *rbio, *wbio;
int bread = 0, bwritten = 0;
ssize_t res = 0;
unsigned long rbio_rbytes, rbio_wbytes, wbio_rbytes, wbio_wbytes;
rbio = SSL_get_rbio(ssl);
rbio_rbytes = BIO_number_read(rbio);
rbio_wbytes = BIO_number_written(rbio);
wbio = SSL_get_wbio(ssl);
wbio_rbytes = BIO_number_read(wbio);
wbio_wbytes = BIO_number_written(wbio);
res = tls_write(ssl, buf, buflen, nstrm->strm_type, nstrm->notes);
bread = (BIO_number_read(rbio) - rbio_rbytes) +
(BIO_number_read(wbio) - wbio_rbytes);
bwritten = (BIO_number_written(rbio) - rbio_wbytes) +
(BIO_number_written(wbio) - wbio_wbytes);
/* Manually update session.total_raw_in, in order to have %I be
* accurately represented for the raw traffic.
*/
if (bread > 0) {
session.total_raw_in += bread;
}
/* Manually update session.total_raw_out with the difference between
* the raw bytes written out versus the non-SSL bytes written out,
* in order to have %O be accurately represented for the raw traffic.
*/
if (res > 0) {
session.total_raw_out += (bwritten - res);
}
return res;
}
return write(nstrm->strm_fd, buf, buflen);
}
static int netio_install_ctrl(void) {
pr_netio_t *netio;
if (tls_ctrl_netio != NULL) {
/* If we already have our ctrl netio, then it's been registered, and
* we don't need to do anything more.
*/
return 0;
}
netio = pr_alloc_netio2(permanent_pool, &proxy_module, "proxy.tls");
netio->abort = netio_abort_cb;
netio->close = netio_close_cb;
netio->open = netio_open_cb;
netio->poll = netio_poll_cb;
netio->postopen = netio_postopen_cb;
netio->read = netio_read_cb;
netio->reopen = netio_reopen_cb;
netio->shutdown = netio_shutdown_cb;
netio->write = netio_write_cb;
if (proxy_netio_use(PR_NETIO_STRM_CTRL, netio) < 0) {
return -1;
}
tls_ctrl_netio = netio;
return 0;
}
static int netio_install_data(void) {
pr_netio_t *netio;
if (tls_data_netio != NULL) {
pr_netio_t *using_netio = NULL;
if (proxy_netio_using(PR_NETIO_STRM_DATA, &using_netio) == 0) {
if (using_netio == NULL) {
if (proxy_netio_use(PR_NETIO_STRM_DATA, tls_data_netio) < 0) {
return -1;
}
}
}
return 0;
}
netio = pr_alloc_netio2(permanent_pool, &proxy_module, "proxy.tls");
netio->abort = netio_abort_cb;
netio->close = netio_close_cb;
netio->open = netio_open_cb;
netio->poll = netio_poll_cb;
netio->postopen = netio_postopen_cb;
netio->read = netio_read_cb;
netio->reopen = netio_reopen_cb;
netio->shutdown = netio_shutdown_cb;
netio->write = netio_write_cb;
if (proxy_netio_use(PR_NETIO_STRM_DATA, netio) < 0) {
return -1;
}
tls_data_netio = netio;
return 0;
}
/* Initialization routines */
#if defined(PSK_MAX_PSK_LEN)
static unsigned int tls_psk_cb(SSL *ssl, const char *psk_hint, char *identity,
unsigned int max_identity_len,
unsigned char *psk, unsigned int max_psklen) {
int res, bn_len;
unsigned int psklen;
if (psk_hint != NULL) {
pr_trace_msg(trace_channel, 7, "received PSK identity hint: '%s'",
psk_hint);
} else {
pr_trace_msg(trace_channel, 17, "received no PSK identity hint");
}
res = snprintf(identity, max_identity_len, "%s", tls_psk_name);
if (res < 0 ||
res > (int) max_identity_len) {
pr_trace_msg(trace_channel, 6,
"error setting PSK identity to '%s'", tls_psk_name);
return 0;
}
bn_len = BN_num_bytes(tls_psk_bn);
if (bn_len > (int) max_psklen) {
pr_trace_msg(trace_channel, 6,
"warning: unable to use '%s' PSK: max buffer size (%u bytes) "
"too small for key (%d bytes)", tls_psk_name, max_psklen,
bn_len);
return 0;
}
psklen = BN_bn2bin(tls_psk_bn, psk);
if (psklen == 0) {
pr_trace_msg(trace_channel, 6,
"error converting '%s' PSK to binary: %s", tls_psk_name,
proxy_tls_get_errors());
return 0;
}
if (tls_opts & PROXY_TLS_OPT_ENABLE_DIAGS) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.psk] used PSK identity '%s'", tls_psk_name);
}
tls_psk_used = TRUE;
return psklen;
}
#endif /* PSK support */
#if !defined(OPENSSL_NO_TLSEXT) && defined(TLSEXT_STATUSTYPE_ocsp)
/* TODO: Do more than just log the received stapled OCSP response. */
static int tls_ocsp_response_cb(SSL *ssl, void *user_data) {
BIO *bio = NULL;
char *data = NULL;
long datalen;
const unsigned char *ptr;
int len, res = 1;
bio = BIO_new(BIO_s_mem());
len = SSL_get_tlsext_status_ocsp_resp(ssl, &ptr);
BIO_puts(bio, "OCSP response: ");
if (ptr == NULL) {
BIO_puts(bio, "no response sent\n");
} else {
OCSP_RESPONSE *resp;
resp = d2i_OCSP_RESPONSE(NULL, &ptr, len);
if (resp == NULL) {
BIO_puts(bio, "response parse error\n");
BIO_dump_indent(bio, (char *) ptr, len, 4);
res = 0;
} else {
BIO_puts(bio, "\n======================================\n");
OCSP_RESPONSE_print(bio, resp, 0);
BIO_puts(bio, "======================================\n");
OCSP_RESPONSE_free(resp);
}
}
datalen = BIO_get_mem_data(bio, &data);
if (data) {
data[datalen] = '\0';
}
pr_trace_msg(trace_channel, 1, "%s", "stapled OCSP response:");
pr_trace_msg(trace_channel, 1, "%s", data);
BIO_free(bio);
return res;
}
#endif /* OCSP support */
#if !defined(OPENSSL_NO_TLSEXT)
struct proxy_tls_next_proto {
const char *proto;
unsigned char *encoded_proto;
unsigned int encoded_protolen;
};
#if defined(PR_USE_OPENSSL_NPN)
static int tls_npn_cb(SSL *ssl,
unsigned char **npn_out, unsigned char *npn_outlen,
const unsigned char *npn_in, unsigned int npn_inlen,
void *data) {
struct proxy_tls_next_proto *next_proto;
next_proto = data;
if (pr_trace_get_level(trace_channel) >= 12) {
register unsigned int i;
int res;
pr_trace_msg(trace_channel, 12,
"NPN protocols advertised by server:");
for (i = 0; i < npn_inlen; i++) {
pr_trace_msg(trace_channel, 12,
" %.*s", (int) npn_in[i], (char *) &(npn_in[i+1]));
i += npn_in[i];
}
res = SSL_select_next_proto(npn_out, npn_outlen, npn_in, npn_inlen,
next_proto->encoded_proto, next_proto->encoded_protolen);
if (res != OPENSSL_NPN_NEGOTIATED) {
pr_trace_msg(trace_channel, 12,
"failed to negotiate NPN protocol '%s': %s", PROXY_TLS_NEXT_PROTO,
res == OPENSSL_NPN_UNSUPPORTED ? "NPN unsupported by server" :
"No overlap with server protocols");
}
}
return SSL_TLSEXT_ERR_OK;
}
#endif /* PR_USE_OPENSSL_NPN */
static int set_next_protocol(SSL_CTX *ctx) {
register unsigned int i;
const char *proto = PROXY_TLS_NEXT_PROTO;
struct proxy_tls_next_proto *next_proto;
unsigned char *encoded_proto;
size_t encoded_protolen, proto_len;
proto_len = strlen(proto);
encoded_protolen = proto_len + 1;
encoded_proto = palloc(proxy_pool, encoded_protolen);
encoded_proto[0] = proto_len;
for (i = 0; i < proto_len; i++) {
encoded_proto[i+1] = proto[i];
}
next_proto = palloc(proxy_pool, sizeof(struct proxy_tls_next_proto));
next_proto->proto = pstrdup(proxy_pool, proto);
next_proto->encoded_proto = encoded_proto;
next_proto->encoded_protolen = encoded_protolen;
# if defined(PR_USE_OPENSSL_NPN)
SSL_CTX_set_next_proto_select_cb(ctx, tls_npn_cb, next_proto);
# endif /* NPN */
# if defined(PR_USE_OPENSSL_ALPN)
SSL_CTX_set_alpn_protos(ctx, next_proto->encoded_proto,
next_proto->encoded_protolen);
# endif /* ALPN */
return 0;
}
#endif /* OPENSSL_NO_TLSEXT */
static int init_ssl_ctx(void) {
long ssl_mode = 0;
int ssl_opts = tls_ssl_opts;
if (ssl_ctx != NULL) {
SSL_CTX_free(ssl_ctx);
ssl_ctx = NULL;
}
ssl_ctx = SSL_CTX_new(SSLv23_client_method());
if (ssl_ctx == NULL) {
pr_log_debug(DEBUG0, MOD_PROXY_VERSION
": error creating SSL_CTX: %s", proxy_tls_get_errors());
errno = EPERM;
return -1;
}
/* Note that we explicitly do NOT use OpenSSL's internal cache for
* client session caching; we'll use our own.
*/
SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_OFF);
#if OPENSSL_VERSION_NUMBER > 0x000906000L
/* The SSL_MODE_AUTO_RETRY mode was added in 0.9.6. */
ssl_mode |= SSL_MODE_AUTO_RETRY;
#endif
#if OPENSSL_VERSION_NUMBER >= 0x1000001fL
/* The SSL_MODE_RELEASE_BUFFERS mode was added in 1.0.0a. */
ssl_mode |= SSL_MODE_RELEASE_BUFFERS;
#endif
if (ssl_mode != 0) {
SSL_CTX_set_mode(ssl_ctx, ssl_mode);
}
/* If using OpenSSL-0.9.7 or greater, prevent session resumptions on
* renegotiations (more secure).
*/
#if OPENSSL_VERSION_NUMBER > 0x000907000L
ssl_opts |= SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION;
#endif
/* Disable SSL compression. */
#if defined(SSL_OP_NO_COMPRESSION)
ssl_opts |= SSL_OP_NO_COMPRESSION;
#endif /* SSL_OP_NO_COMPRESSION */
#if defined(PR_USE_OPENSSL_ECC)
# if defined(SSL_OP_SINGLE_ECDH_USE)
ssl_opts |= SSL_OP_SINGLE_ECDH_USE;
# endif
# if defined(SSL_OP_SAFARI_ECDHE_ECDSA_BUG)
ssl_opts |= SSL_OP_SAFARI_ECDHE_ECDSA_BUG;
# endif
#endif /* ECC support */
SSL_CTX_set_options(ssl_ctx, ssl_opts);
#if !defined(OPENSSL_NO_TLSEXT)
if (set_next_protocol(ssl_ctx) < 0) {
pr_trace_msg(trace_channel, 4,
"error setting TLS next protocol: %s", strerror(errno));
}
#endif /* OPENSSL_NO_TLSEXT */
if (tls_seed_prng() < 0) {
pr_log_debug(DEBUG1, MOD_PROXY_VERSION ": unable to properly seed PRNG");
}
return 0;
}
/* Event listeners */
static void proxy_tls_shutdown_ev(const void *event_data, void *user_data) {
RAND_cleanup();
}
#endif /* PR_USE_OPENSSL */
int proxy_tls_set_data_prot(int data_prot) {
int old_data_prot;
#if defined(PR_USE_OPENSSL)
old_data_prot = tls_need_data_prot;
tls_need_data_prot = data_prot;
#else
old_data_prot = FALSE;
#endif /* PR_USE_OPENSSL */
return old_data_prot;
}
int proxy_tls_set_tls(int engine) {
#if defined(PR_USE_OPENSSL)
if (engine != PROXY_TLS_ENGINE_ON &&
engine != PROXY_TLS_ENGINE_OFF &&
engine != PROXY_TLS_ENGINE_AUTO &&
engine != PROXY_TLS_ENGINE_IMPLICIT) {
errno = EINVAL;
return -1;
}
tls_engine = engine;
#endif /* PR_USE_OPENSSL */
return 0;
}
int proxy_tls_using_tls(void) {
#if defined(PR_USE_OPENSSL)
return tls_engine;
#else
return PROXY_TLS_ENGINE_OFF;
#endif /* PR_USE_OPENSSL */
}
int proxy_tls_match_client_tls(void) {
int client_using_tls = FALSE, res = 0;
/* Is the client using TLS right now? */
if (session.rfc2228_mech != NULL &&
strcasecmp(session.rfc2228_mech, "TLS") == 0) {
client_using_tls = TRUE;
}
if (client_using_tls == TRUE) {
int client_using_implicit_ftps = FALSE;
config_rec *c;
unsigned long mod_tls_opts = 0UL;
/* Is the client using implicit FTPS? */
c = find_config(main_server->conf, CONF_PARAM, "TLSOptions", FALSE);
while (c != NULL) {
unsigned long opts;
pr_signals_handle();
opts = *((unsigned long *) c->argv[0]);
mod_tls_opts |= opts;
c = find_config_next(c, c->next, CONF_PARAM, "TLSOptions", FALSE);
}
/* We cheat, and duplicate/check for the UseImplicitTLS TLSOption
* (TLS_OPT_USE_IMPLICIT_SSL) from the mod_tls.c implementation.
*/
if (mod_tls_opts & 0x0200) {
client_using_implicit_ftps = TRUE;
}
if (client_using_implicit_ftps == TRUE) {
pr_trace_msg(trace_channel, 17,
"setting implicit FTPS due to ProxyTLSEngine MatchClient");
res = proxy_tls_set_tls(PROXY_TLS_ENGINE_IMPLICIT);
} else {
/* Using explicit FTPS. */
pr_trace_msg(trace_channel, 17,
"setting explicit FTPS due to ProxyTLSEngine MatchClient");
res = proxy_tls_set_tls(PROXY_TLS_ENGINE_ON);
}
} else {
pr_trace_msg(trace_channel, 17,
"disabling FTPS due to ProxyTLSEngine MatchClient");
res = proxy_tls_set_tls(PROXY_TLS_ENGINE_OFF);
}
return res;
}
int proxy_tls_init(pool *p, const char *tables_path, int flags) {
#if defined(PR_USE_OPENSSL)
int res;
memset(&tls_ds, 0, sizeof(tls_ds));
switch (proxy_datastore) {
case PROXY_DATASTORE_SQLITE:
res = proxy_tls_db_as_datastore(&tls_ds, proxy_datastore_data,
proxy_datastore_datasz);
break;
case PROXY_DATASTORE_REDIS:
res = proxy_tls_redis_as_datastore(&tls_ds, proxy_datastore_data,
proxy_datastore_datasz);
break;
default:
res = -1;
errno = EINVAL;
break;
}
if (res < 0) {
return -1;
}
res = (tls_ds.init)(p, tables_path, flags);
if (res < 0) {
return -1;
}
if (pr_module_exists("mod_sftp.c") == FALSE &&
pr_module_exists("mod_tls.c") == FALSE) {
#if OPENSSL_VERSION_NUMBER < 0x10100000L
OPENSSL_config(NULL);
#endif /* prior to OpenSSL-1.1.x */
SSL_load_error_strings();
SSL_library_init();
ERR_load_crypto_strings();
OpenSSL_add_all_algorithms();
}
res = init_ssl_ctx();
if (res < 0) {
return -1;
}
tls_tables_path = pstrdup(proxy_pool, tables_path);
pr_event_register(&proxy_module, "core.shutdown", proxy_tls_shutdown_ev,
NULL);
#endif /* PR_USE_OPENSSL */
return 0;
}
int proxy_tls_free(pool *p) {
if (p == NULL) {
errno = EINVAL;
return -1;
}
#if defined(PR_USE_OPENSSL)
if (ssl_ctx != NULL) {
SSL_CTX_free(ssl_ctx);
ssl_ctx = NULL;
}
if (tls_ds.dsh != NULL) {
int res;
res = (tls_ds.close)(p, tls_ds.dsh);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error closing datastore: %s", strerror(errno));
}
tls_ds.dsh = NULL;
}
#endif /* PR_USE_OPENSSL */
return 0;
}
#if defined(PR_USE_OPENSSL)
/* Construct the options value that disables all unsupported protocols. */
static int get_disabled_protocols(unsigned int supported_protocols) {
int disabled_protocols;
/* First, create an options value where ALL protocols are disabled. */
disabled_protocols = (SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_TLSv1);
# if defined(SSL_OP_NO_TLSv1_1)
disabled_protocols |= SSL_OP_NO_TLSv1_1;
# endif
# if defined(SSL_OP_NO_TLSv1_2)
disabled_protocols |= SSL_OP_NO_TLSv1_2;
# endif
# if defined(SSL_OP_NO_TLSv1_3)
disabled_protocols |= SSL_OP_NO_TLSv1_3;
# endif
/* Now, based on the given bitset of supported protocols, clear the
* necessary bits.
*/
if (supported_protocols & PROXY_TLS_PROTO_SSL_V3) {
disabled_protocols &= ~SSL_OP_NO_SSLv3;
}
if (supported_protocols & PROXY_TLS_PROTO_TLS_V1) {
disabled_protocols &= ~SSL_OP_NO_TLSv1;
}
# if OPENSSL_VERSION_NUMBER >= 0x10001000L
if (supported_protocols & PROXY_TLS_PROTO_TLS_V1_1) {
disabled_protocols &= ~SSL_OP_NO_TLSv1_1;
}
if (supported_protocols & PROXY_TLS_PROTO_TLS_V1_2) {
disabled_protocols &= ~SSL_OP_NO_TLSv1_2;
}
# endif /* OpenSSL-1.0.1 or later */
#if defined(SSL_OP_NO_TLSv1_3)
if (supported_protocols & PROXY_TLS_PROTO_TLS_V1_3) {
disabled_protocols &= ~SSL_OP_NO_TLSv1_3;
}
#endif /* OpenSSL 1.1.1 or later */
return disabled_protocols;
}
static const char *get_enabled_protocols_str(pool *p, unsigned int protos,
unsigned int *count) {
char *proto_str = "";
unsigned int nproto = 0;
if (protos & PROXY_TLS_PROTO_SSL_V3) {
proto_str = pstrcat(p, proto_str, *proto_str ? ", " : "",
"SSLv3", NULL);
nproto++;
}
if (protos & PROXY_TLS_PROTO_TLS_V1) {
proto_str = pstrcat(p, proto_str, *proto_str ? ", " : "",
"TLSv1", NULL);
nproto++;
}
if (protos & PROXY_TLS_PROTO_TLS_V1_1) {
proto_str = pstrcat(p, proto_str, *proto_str ? ", " : "",
"TLSv1.1", NULL);
nproto++;
}
if (protos & PROXY_TLS_PROTO_TLS_V1_2) {
proto_str = pstrcat(p, proto_str, *proto_str ? ", " : "",
"TLSv1.2", NULL);
nproto++;
}
*count = nproto;
return proto_str;
}
# if defined(PSK_MAX_PSK_LEN)
static int tls_load_psk(const char *identity, const char *path) {
register int i;
char key_buf[PR_TUNABLE_BUFFER_SIZE];
int fd, key_len, valid_hex = TRUE, res, xerrno;
struct stat st;
BIGNUM *bn = NULL;
PRIVS_ROOT
fd = open(path, O_RDONLY);
xerrno = errno;
PRIVS_RELINQUISH
if (fd < 0) {
pr_trace_msg(trace_channel, 6,
"error opening ProxyTLSPreSharedKey file '%s': %s", path,
strerror(xerrno));
errno = xerrno;
return -1;
}
if (fstat(fd, &st) < 0) {
xerrno = errno;
pr_trace_msg(trace_channel, 6,
"error checking ProxyTLSPreSharedKey file '%s': %s", path,
strerror(xerrno));
(void) close(fd);
errno = xerrno;
return -1;
}
/* Check on the permissions of the file; skip it if the permissions
* are too permissive, e.g. file is world-read/writable.
*/
if (st.st_mode & S_IROTH) {
pr_trace_msg(trace_channel, 6,
"unable to use ProxyTLSPreSharedKey file '%s': "
"file is world-readable", path);
(void) close(fd);
errno = EPERM;
return -1;
}
if (st.st_mode & S_IWOTH) {
pr_trace_msg(trace_channel, 6,
"unable to use ProxyTLSPreSharedKey file '%s': "
"file is world-writable", path);
(void) close(fd);
errno = EPERM;
return -1;
}
if (st.st_size == 0) {
pr_trace_msg(trace_channel, 6,
"unable to use ProxyTLSPreSharedKey file '%s': "
"file is zero length", path);
(void) close(fd);
errno = ENOENT;
return -1;
}
/* Read the entire key into memory. */
memset(key_buf, '\0', sizeof(key_buf));
key_len = read(fd, key_buf, sizeof(key_buf)-1);
xerrno = errno;
(void) close(fd);
if (key_len < 0) {
pr_trace_msg(trace_channel, 6,
": error reading ProxyTLSPreSharedKey file '%s': %s", path,
strerror(xerrno));
errno = xerrno;
return -1;
}
if (key_len < PROXY_TLS_MIN_PSK_LEN) {
pr_trace_msg(trace_channel, 6,
"read %d bytes from ProxyTLSPreSharedKey file '%s', need at least %d "
"bytes of key data, ignoring", key_len, path, PROXY_TLS_MIN_PSK_LEN);
errno = ENOENT;
return -1;
}
key_buf[key_len] = '\0';
key_buf[sizeof(key_buf)-1] = '\0';
/* Ignore any trailing newlines. */
if (key_buf[key_len-1] == '\n') {
key_buf[key_len-1] = '\0';
key_len--;
}
if (key_buf[key_len-1] == '\r') {
key_buf[key_len-1] = '\0';
key_len--;
}
/* Ensure that it is all hex encoded data */
for (i = 0; i < key_len; i++) {
if (isxdigit((int) key_buf[i]) == 0) {
valid_hex = FALSE;
break;
}
}
if (valid_hex == FALSE) {
pr_trace_msg(trace_channel, 6,
"unable to use '%s' data from ProxyTLSPreSharedKey file '%s': "
"not a hex number", key_buf, path);
errno = EINVAL;
return -1;
}
res = BN_hex2bn(&bn, key_buf);
if (res == 0) {
pr_trace_msg(trace_channel, 6,
"failed to convert '%s' data from ProxyTLSPreSharedKey file '%s' "
"to BIGNUM: %s", key_buf, path, proxy_tls_get_errors());
if (bn != NULL) {
BN_free(bn);
}
errno = EINVAL;
return -1;
}
tls_psk_name = identity;
tls_psk_bn = bn;
return 0;
}
# endif /* PSK support */
static void tls_info_cb(const SSL *ssl, int where, int ret) {
const char *str = "(unknown)";
int w;
pr_signals_handle();
w = where & ~SSL_ST_MASK;
if (w & SSL_ST_CONNECT) {
str = "connecting";
} else if (w & SSL_ST_ACCEPT) {
str = "accepting";
} else {
int ssl_state;
ssl_state = SSL_get_state(ssl);
switch (ssl_state) {
# if defined(SSL_ST_BEFORE)
case SSL_ST_BEFORE:
str = "before";
break;
# endif
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(HAVE_LIBRESSL)
case TLS_ST_OK:
#else
case SSL_ST_OK:
#endif /* OpenSSL-1.1.x and later */
str = "ok";
break;
# if defined(SSL_ST_RENEGOTIATE)
case SSL_ST_RENEGOTIATE:
str = "renegotiating";
break;
# endif
default:
break;
}
}
if (where & SSL_CB_CONNECT_LOOP) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.info] %s: %s", str, SSL_state_string_long(ssl));
} else if (where & SSL_CB_HANDSHAKE_START) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.info] %s: %s", str, SSL_state_string_long(ssl));
} else if (where & SSL_CB_HANDSHAKE_DONE) {
if (pr_trace_get_level(trace_channel) >= 9) {
int reused;
reused = SSL_session_reused((SSL *) ssl);
if (reused > 0) {
pr_trace_msg(trace_channel, 9,
"RESUMED SSL/TLS session: %s using cipher %s (%d bits)",
SSL_get_version(ssl), SSL_get_cipher_name(ssl),
SSL_get_cipher_bits(ssl, NULL));
} else {
pr_trace_msg(trace_channel, 9,
"negotiated NEW SSL/TLS session");
}
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.info] %s: %s", str, SSL_state_string_long(ssl));
} else if (where & SSL_CB_LOOP) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.info] %s: %s", str, SSL_state_string_long(ssl));
} else if (where & SSL_CB_ALERT) {
str = (where & SSL_CB_READ) ? "reading" : "writing";
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.info] %s: SSL/TLS alert %s: %s", str,
SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret));
} else if (where & SSL_CB_EXIT) {
if (ret == 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.info] %s: failed in %s: %s", str, SSL_state_string_long(ssl),
proxy_tls_get_errors());
} else if (ret < 0 &&
errno != 0 &&
errno != EAGAIN) {
/* Ignore EAGAIN errors */
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.info] %s: error in %s (errno %d: %s)", str,
SSL_state_string_long(ssl), errno, strerror(errno));
}
}
}
# if OPENSSL_VERSION_NUMBER > 0x000907000L
/* Borrowed from mod_tls. Would be nice to share this code somehow. */
struct tls_label {
int labelno;
const char *label_name;
};
/* SSL record types */
static struct tls_label tls_record_type_labels[] = {
{ 20, "ChangeCipherSpec" },
{ 21, "Alert" },
{ 22, "Handshake" },
{ 23, "ApplicationData" },
{ 0, NULL }
};
/* SSL versions */
static struct tls_label tls_version_labels[] = {
{ 0x0002, "SSL 2.0" },
{ 0x0300, "SSL 3.0" },
{ 0x0301, "TLS 1.0" },
{ 0x0302, "TLS 1.1" },
{ 0x0303, "TLS 1.2" },
{ 0x0304, "TLS 1.3" },
{ 0, NULL }
};
/* Cipher suites. These values come from:
* http://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-4
*/
static struct tls_label tls_ciphersuite_labels[] = {
{ 0x0000, "SSL_NULL_WITH_NULL_NULL" },
{ 0x0001, "SSL_RSA_WITH_NULL_MD5" },
{ 0x0002, "SSL_RSA_WITH_NULL_SHA" },
{ 0x0003, "SSL_RSA_EXPORT_WITH_RC4_40_MD5" },
{ 0x0004, "SSL_RSA_WITH_RC4_128_MD5" },
{ 0x0005, "SSL_RSA_WITH_RC4_128_SHA" },
{ 0x0006, "SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5" },
{ 0x0007, "SSL_RSA_WITH_IDEA_CBC_SHA" },
{ 0x0008, "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA" },
{ 0x0009, "SSL_RSA_WITH_DES_CBC_SHA" },
{ 0x000A, "SSL_RSA_WITH_3DES_EDE_CBC_SHA" },
{ 0x000B, "SSL_DH_DSS_EXPORT_WITH_DES40_CBC_SHA" },
{ 0x000C, "SSL_DH_DSS_WITH_DES_CBC_SHA" },
{ 0x000D, "SSL_DH_DSS_WITH_3DES_EDE_CBC_SHA" },
{ 0x000E, "SSL_DH_RSA_EXPORT_WITH_DES40_CBC_SHA" },
{ 0x000F, "SSL_DH_RSA_WITH_DES_CBC_SHA" },
{ 0x0010, "SSL_DH_RSA_WITH_3DES_EDE_CBC_SHA" },
{ 0x0011, "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA" },
{ 0x0012, "SSL_DHE_DSS_WITH_DES_CBC_SHA" },
{ 0x0013, "SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA" },
{ 0x0014, "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA" },
{ 0x0015, "SSL_DHE_RSA_WITH_DES_CBC_SHA" },
{ 0x0016, "SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA" },
{ 0x0017, "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5" },
{ 0x0018, "SSL_DH_anon_WITH_RC4_128_MD5" },
{ 0x0019, "SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA" },
{ 0x001A, "SSL_DH_anon_WITH_DES_CBC_SHA" },
{ 0x001B, "SSL_DH_anon_WITH_3DES_EDE_CBC_SHA" },
{ 0x001D, "SSL_FORTEZZA_KEA_WITH_FORTEZZA_CBC_SHA" },
{ 0x001E, "SSL_FORTEZZA_KEA_WITH_RC4_128_SHA" },
{ 0x001F, "TLS_KRB5_WITH_3DES_EDE_CBC_SHA" },
{ 0x0020, "TLS_KRB5_WITH_RC4_128_SHA" },
{ 0x0021, "TLS_KRB5_WITH_IDEA_CBC_SHA" },
{ 0x0022, "TLS_KRB5_WITH_DES_CBC_MD5" },
{ 0x0023, "TLS_KRB5_WITH_3DES_EDE_CBC_MD5" },
{ 0x0024, "TLS_KRB5_WITH_RC4_128_MD5" },
{ 0x0025, "TLS_KRB5_WITH_IDEA_CBC_MD5" },
{ 0x0026, "TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA" },
{ 0x0027, "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA" },
{ 0x0028, "TLS_KRB5_EXPORT_WITH_RC4_40_SHA" },
{ 0x0029, "TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5" },
{ 0x002A, "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5" },
{ 0x002B, "TLS_KRB5_EXPORT_WITH_RC4_40_MD5" },
{ 0x002F, "TLS_RSA_WITH_AES_128_CBC_SHA" },
{ 0x0030, "TLS_DH_DSS_WITH_AES_128_CBC_SHA" },
{ 0x0031, "TLS_DH_RSA_WITH_AES_128_CBC_SHA" },
{ 0x0032, "TLS_DHE_DSS_WITH_AES_128_CBC_SHA" },
{ 0x0033, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA" },
{ 0x0034, "TLS_DH_anon_WITH_AES_128_CBC_SHA" },
{ 0x0035, "TLS_RSA_WITH_AES_256_CBC_SHA" },
{ 0x0036, "TLS_DH_DSS_WITH_AES_256_CBC_SHA" },
{ 0x0037, "TLS_DH_RSA_WITH_AES_256_CBC_SHA" },
{ 0x0038, "TLS_DHE_DSS_WITH_AES_256_CBC_SHA" },
{ 0x0039, "TLS_DHE_RSA_WITH_AES_256_CBC_SHA" },
{ 0x003A, "TLS_DH_anon_WITH_AES_256_CBC_SHA" },
{ 0x003B, "TLS_RSA_WITH_NULL_SHA256" },
{ 0x003C, "TLS_RSA_WITH_AES_128_CBC_SHA256" },
{ 0x003D, "TLS_RSA_WITH_AES_256_CBC_SHA256" },
{ 0x003E, "TLS_DH_DSS_WITH_AES_128_CBC_SHA256" },
{ 0x003F, "TLS_DH_RSA_WITH_AES_128_CBC_SHA256" },
{ 0x0040, "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256" },
{ 0x0041, "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA" },
{ 0x0042, "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA" },
{ 0x0043, "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA" },
{ 0x0044, "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA" },
{ 0x0045, "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA" },
{ 0x0046, "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA" },
{ 0x0067, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256" },
{ 0x0068, "TLS_DH_DSS_WITH_AES_256_CBC_SHA256" },
{ 0x0069, "TLS_DH_RSA_WITH_AES_256_CBC_SHA256" },
{ 0x006A, "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256" },
{ 0x006B, "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256" },
{ 0x006C, "TLS_DH_anon_WITH_AES_128_CBC_SHA256" },
{ 0x006D, "TLS_DH_anon_WITH_AES_256_CBC_SHA256" },
{ 0x0084, "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA" },
{ 0x0085, "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA" },
{ 0x0086, "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA" },
{ 0x0087, "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA" },
{ 0x0088, "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA" },
{ 0x0089, "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA" },
{ 0x008A, "TLS_PSK_WITH_RC4_128_SHA" },
{ 0x008B, "TLS_PSK_WITH_3DES_EDE_CBC_SHA" },
{ 0x008C, "TLS_PSK_WITH_AES_128_CBC_SHA" },
{ 0x008D, "TLS_PSK_WITH_AES_256_CBC_SHA" },
{ 0x008E, "TLS_DHE_PSK_WITH_RC4_128_SHA" },
{ 0x008F, "TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA" },
{ 0x0090, "TLS_DHE_PSK_WITH_AES_128_CBC_SHA" },
{ 0x0091, "TLS_DHE_PSK_WITH_AES_256_CBC_SHA" },
{ 0x0092, "TLS_RSA_PSK_WITH_RC4_128_SHA" },
{ 0x0093, "TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA" },
{ 0x0094, "TLS_RSA_PSK_WITH_AES_128_CBC_SHA" },
{ 0x0095, "TLS_RSA_PSK_WITH_AES_256_CBC_SHA" },
{ 0x0096, "TLS_RSA_WITH_SEED_CBC_SHA" },
{ 0x0097, "TLS_DH_DSS_WITH_SEED_CBC_SHA" },
{ 0x0098, "TLS_DH_RSA_WITH_SEED_CBC_SHA" },
{ 0x0099, "TLS_DHE_DSS_WITH_SEED_CBC_SHA" },
{ 0x009A, "TLS_DHE_RSA_WITH_SEED_CBC_SHA" },
{ 0x009B, "TLS_DH_anon_WITH_SEED_CBC_SHA" },
{ 0x009C, "TLS_RSA_WITH_AES_128_GCM_SHA256" },
{ 0x009D, "TLS_RSA_WITH_AES_256_GCM_SHA384" },
{ 0x009E, "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256" },
{ 0x009F, "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384" },
{ 0x00A0, "TLS_DH_RSA_WITH_AES_128_GCM_SHA256" },
{ 0x00A1, "TLS_DH_RSA_WITH_AES_256_GCM_SHA384" },
{ 0x00A2, "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256" },
{ 0x00A3, "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384" },
{ 0x00A4, "TLS_DH_DSS_WITH_AES_128_GCM_SHA256" },
{ 0x00A5, "TLS_DH_DSS_WITH_AES_256_GCM_SHA384" },
{ 0x00A6, "TLS_DH_anon_WITH_AES_128_GCM_SHA256" },
{ 0x00A7, "TLS_DH_anon_WITH_AES_256_GCM_SHA384" },
{ 0x00A8, "TLS_PSK_WITH_AES_128_GCM_SHA256" },
{ 0x00A9, "TLS_PSK_WITH_AES_256_GCM_SHA384" },
{ 0x00AA, "TLS_DHE_PSK_WITH_AES_128_GCM_SHA256" },
{ 0x00AB, "TLS_DHE_PSK_WITH_AES_256_GCM_SHA384" },
{ 0x00AC, "TLS_RSA_PSK_WITH_AES_128_GCM_SHA256" },
{ 0x00AD, "TLS_RSA_PSK_WITH_AES_256_GCM_SHA384" },
{ 0x00AE, "TLS_PSK_WITH_AES_128_CBC_SHA256" },
{ 0x00AF, "TLS_PSK_WITH_AES_256_CBC_SHA384" },
{ 0x00B0, "TLS_PSK_WITH_NULL_SHA256" },
{ 0x00B1, "TLS_PSK_WITH_NULL_SHA384" },
{ 0x00B2, "TLS_DHE_PSK_WITH_AES_128_CBC_SHA256" },
{ 0x00B3, "TLS_DHE_PSK_WITH_AES_256_CBC_SHA384"},
{ 0x00B4, "TLS_DHE_PSK_WITH_NULL_SHA256" },
{ 0x00B5, "TLS_DHE_PSK_WITH_NULL_SHA384" },
{ 0x00B6, "TLS_RSA_PSK_WITH_AES_128_CBC_SHA256" },
{ 0x00B7, "TLS_RSA_PSK_WITH_AES_256_CBC_SHA384" },
{ 0x00B8, "TLS_RSA_PSK_WITH_NULL_SHA256" },
{ 0x00B9, "TLS_RSA_PSK_WITH_NULL_SHA384" },
{ 0x00BA, "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0x00BB, "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0x00BC, "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0x00BD, "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0x00BE, "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0x00BF, "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0x00C0, "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256" },
{ 0x00C1, "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256" },
{ 0x00C2, "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256" },
{ 0x00C3, "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256" },
{ 0x00C4, "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256" },
{ 0x00C5, "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256" },
{ 0x00FF, "TLS_EMPTY_RENEGOTIATION_INFO_SCSV" },
{ 0x1301, "TLS_AES_128_GCM_SHA256" },
{ 0x1302, "TLS_AES_256_GCM_SHA384" },
{ 0x1303, "TLS_CHACHA20_POLY1305_SHA256" },
{ 0xC001, "TLS_ECDH_ECDSA_WITH_NULL_SHA" },
{ 0xC002, "TLS_ECDH_ECDSA_WITH_RC4_128_SHA" },
{ 0xC003, "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA" },
{ 0xC004, "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA" },
{ 0xC005, "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA" },
{ 0xC006, "TLS_ECDHE_ECDSA_WITH_NULL_SHA" },
{ 0xC007, "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA" },
{ 0xC008, "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA" },
{ 0xC009, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA" },
{ 0xC00A, "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA" },
{ 0xC00B, "TLS_ECDH_RSA_WITH_NULL_SHA" },
{ 0xC00C, "TLS_ECDH_RSA_WITH_RC4_128_SHA" },
{ 0xC00D, "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA" },
{ 0xC00E, "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA" },
{ 0xC00F, "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA" },
{ 0xC010, "TLS_ECDHE_RSA_WITH_NULL_SHA" },
{ 0xC011, "TLS_ECDHE_RSA_WITH_RC4_128_SHA" },
{ 0xC012, "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA" },
{ 0xC013, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA" },
{ 0xC014, "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA" },
{ 0xC015, "TLS_ECDH_anon_WITH_NULL_SHA" },
{ 0xC016, "TLS_ECDH_anon_WITH_RC4_128_SHA" },
{ 0xC017, "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA" },
{ 0xC018, "TLS_ECDH_anon_WITH_AES_128_CBC_SHA" },
{ 0xC019, "TLS_ECDH_anon_WITH_AES_256_CBC_SHA" },
{ 0xC01A, "TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA" },
{ 0xC01B, "TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA" },
{ 0xC01C, "TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA" },
{ 0xC01D, "TLS_SRP_SHA_WITH_AES_128_CBC_SHA" },
{ 0xC01E, "TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA" },
{ 0xC01F, "TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA" },
{ 0xC020, "TLS_SRP_SHA_WITH_AES_256_CBC_SHA" },
{ 0xC021, "TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA" },
{ 0xC022, "TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA" },
{ 0xC023, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256" },
{ 0xC024, "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384" },
{ 0xC025, "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256" },
{ 0xC026, "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384" },
{ 0xC027, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256" },
{ 0xC028, "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384" },
{ 0xC029, "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256" },
{ 0xC02A, "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384" },
{ 0xC02B, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" },
{ 0xC02C, "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384" },
{ 0xC02D, "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256" },
{ 0xC02E, "TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384" },
{ 0xC02F, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" },
{ 0xC030, "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384" },
{ 0xC031, "TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256" },
{ 0xC032, "TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384" },
{ 0xC07A, "TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC07B, "TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC086, "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC087, "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC08A, "TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC08B, "TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC09C, "TLS_RSA_WITH_AES_128_CCM" },
{ 0xC09D, "TLS_RSA_WITH_AES_256_CCM" },
{ 0xC0AC, "TLS_ECDHE_ECDSA_WITH_AES_128_CCM" },
{ 0xC0AD, "TLS_ECDHE_ECDSA_WITH_AES_256_CCM" },
{ 0xC0AE, "TLS_DHE_RSA_WITH_AES_128_CCM" },
{ 0xC0AF, "TLS_DHE_RSA_WITH_AES_256_CCM" },
{ 0xCCA8, "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256" },
{ 0xCCA9, "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256" },
{ 0xCCAA, "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256" },
{ 0xCCAB, "TLS_PSK_WITH_CHACHA20_POLY1305_SHA256" },
{ 0xCCAC, "TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256" },
{ 0xCCAD, "TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256" },
{ 0xCCAE, "TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256" },
{ 0xFEFE, "SSL_RSA_FIPS_WITH_DES_CBC_SHA" },
{ 0xFEFF, "SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA" },
{ 0, NULL }
};
/* Compressions */
static struct tls_label tls_compression_labels[] = {
{ 0x0000, "None" },
{ 0x0001, "Zlib" },
{ 0, NULL }
};
/* Extensions */
static struct tls_label tls_extension_labels[] = {
{ 0, "server_name" },
{ 1, "max_fragment_length" },
{ 2, "client_certificate_url" },
{ 3, "trusted_ca_keys" },
{ 4, "truncated_hmac" },
{ 5, "status_request" },
{ 6, "user_mapping" },
{ 7, "client_authz" },
{ 8, "server_authz" },
{ 9, "cert_type" },
{ 10, "elliptic_curves" },
{ 11, "ec_point_formats" },
{ 12, "srp" },
{ 13, "signature_algorithms" },
{ 14, "use_srtp" },
{ 15, "heartbeat" },
{ 16, "application_layer_protocol_negotiation" },
{ 18, "signed_certificate_timestamp" },
{ 21, "padding" },
{ 22, "encrypt_then_mac" },
{ 23, "extended_master_secret" },
{ 35, "session_ticket" },
{ 41, "psk" },
{ 42, "early_data" },
{ 43, "supported_versions" },
{ 44, "cookie" },
{ 45, "psk_kex_modes" },
{ 47, "certificate_authorities" },
{ 49, "post_handshake_auth" },
{ 50, "signature_algorithms_cert" },
{ 51, "key_share" },
{ 0xFF01, "renegotiate" },
{ 13172, "next_proto_neg" },
{ 0, NULL }
};
# if defined(TLSEXT_TYPE_signature_algorithms)
/* Signature Algorithms. These values come from:
* https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-16
*/
static struct tls_label tls_sigalgo_labels[] = {
{ 0x0201, "rsa_pkcs1_sha1" },
{ 0x0202, "dsa_sha1" },
{ 0x0203, "ecdsa_sha1" },
{ 0x0301, "rsa_pkcs1_sha224" },
{ 0x0302, "dsa_sha224" },
{ 0x0303, "ecdsa_sha224" },
{ 0x0401, "rsa_pkcs1_sha256" },
{ 0x0402, "dsa_sha256" },
{ 0x0403, "ecdsa_secp256r1_sha256" },
{ 0x0501, "rsa_pkcs1_sha384" },
{ 0x0502, "dsa_sha384" },
{ 0x0503, "ecdsa_secp384r1_sha384" },
{ 0x0601, "rsa_pkcs1_sha512" },
{ 0x0602, "dsa_sha512" },
{ 0x0603, "ecdsa_secp521r1_sha512" },
{ 0x0804, "rsa_pss_rsae_sha256" },
{ 0x0805, "rsa_pss_rsae_sha384" },
{ 0x0806, "rsa_pss_rsae_sha512" },
{ 0x0807, "ed25519" },
{ 0x0808, "ed448" },
{ 0x0809, "rsa_pss_pss_sha256" },
{ 0x080A, "rsa_pss_pss_sha384" },
{ 0x080B, "rsa_pss_pss_sha512" },
{ 0, NULL }
};
# endif /* TLSEXT_TYPE_signature_algorithms */
# if defined(TLSEXT_TYPE_psk_kex_modes)
/* PSK KEX modes. These values come from:
* https://tools.ietf.org/html/rfc8446#section-4.2.9
*/
static struct tls_label tls_psk_kex_labels[] = {
{ 0, "psk_ke" },
{ 1, "psk_dhe_key" },
{ 0, NULL }
};
# endif /* TLSEXT_TYPE_psk_kex_modes */
static const char *tls_get_label(int labelno, struct tls_label *labels) {
register unsigned int i;
for (i = 0; labels[i].label_name != NULL; i++) {
if (labels[i].labelno == labelno) {
return labels[i].label_name;
}
}
return "[unknown/unsupported]";
}
static void tls_print_ssl_version(BIO *bio, const char *name,
const unsigned char **msg, size_t *msglen, int *pversion) {
int version;
if (*msglen < 2) {
return;
}
version = ((*msg)[0] << 8) | (*msg)[1];
BIO_printf(bio, " %s = %s\n", name,
tls_get_label(version, tls_version_labels));
*msg += 2;
*msglen -= 2;
if (pversion != NULL) {
*pversion = version;
}
}
static void tls_print_hex(BIO *bio, const char *indent, const char *name,
const unsigned char *msg, size_t msglen) {
BIO_printf(bio, "%s (%lu %s)\n", name, (unsigned long) msglen,
msglen != 1 ? "bytes" : "byte");
if (msglen > 0) {
register unsigned int i;
BIO_puts(bio, indent);
for (i = 0; i < msglen; i++) {
BIO_printf(bio, "%02x", msg[i]);
}
BIO_puts(bio, "\n");
}
}
static void tls_print_hexbuf(BIO *bio, const char *indent, const char *name,
size_t namelen, const unsigned char **msg, size_t *msglen) {
size_t buflen;
const unsigned char *ptr;
if (*msglen < namelen) {
return;
}
ptr = *msg;
buflen = ptr[0];
if (namelen > 1) {
buflen = (buflen << 8) | ptr[1];
}
if (*msglen < namelen + buflen) {
return;
}
ptr += namelen;
tls_print_hex(bio, indent, name, ptr, buflen);
*msg += (buflen + namelen);
*msglen -= (buflen + namelen);
}
static void tls_print_random(BIO *bio, const unsigned char **msg,
size_t *msglen) {
time_t ts;
const unsigned char *ptr;
if (*msglen < 32) {
return;
}
ptr = *msg;
ts = ((ptr[0] << 24) | (ptr[1] << 16) | (ptr[2] << 8) | ptr[3]);
ptr += 4;
BIO_puts(bio, " random:\n");
BIO_printf(bio, " gmt_unix_time = %s (not guaranteed to be accurate)\n",
pr_strtime2(ts, TRUE));
tls_print_hex(bio, " ", " random_bytes", ptr, 28);
*msg += 32;
*msglen -= 32;
}
static void tls_print_session_id(BIO *bio, const unsigned char **msg,
size_t *msglen) {
tls_print_hexbuf(bio, " ", " session_id", 1, msg, msglen);
}
static void tls_print_ciphersuites(BIO *bio, const char *name,
const unsigned char **msg, size_t *msglen) {
size_t len;
len = ((*msg[0]) << 8) | (*msg)[1];
*msg += 2;
*msglen -= 2;
BIO_printf(bio, " %s (%lu %s)\n", name, (unsigned long) len,
len != 1 ? "bytes" : "byte");
if (*msglen < len ||
(len & 1)) {
return;
}
while (len > 0) {
unsigned int suiteno;
pr_signals_handle();
suiteno = ((*msg[0]) << 8) | (*msg)[1];
BIO_printf(bio, " %s (0x%x)\n",
tls_get_label(suiteno, tls_ciphersuite_labels), suiteno);
*msg += 2;
*msglen -= 2;
len -= 2;
}
}
static void tls_print_compressions(BIO *bio, const char *name,
const unsigned char **msg, size_t *msglen) {
size_t len;
len = (*msg)[0];
*msg += 1;
*msglen -= 1;
if (*msglen < len) {
return;
}
BIO_printf(bio, " %s (%lu %s)\n", name, (unsigned long) len,
len != 1 ? "bytes" : "byte");
while (len > 0) {
int comp_type;
pr_signals_handle();
comp_type = (*msg)[0];
BIO_printf(bio, " %s\n",
tls_get_label(comp_type, tls_compression_labels));
*msg += 1;
*msglen -= 1;
len -= 1;
}
}
static void tls_print_extension(BIO *bio, const char *indent, int server,
int ext_type, const unsigned char *ext, size_t extlen) {
BIO_printf(bio, "%sextension_type = %s (%lu %s)\n", indent,
tls_get_label(ext_type, tls_extension_labels), (unsigned long) extlen,
extlen != 1 ? "bytes" : "byte");
}
static void tls_print_extensions(BIO *bio, const char *name, int server,
const unsigned char **msg, size_t *msglen) {
size_t len;
if (*msglen == 0) {
BIO_printf(bio, "%s: None\n", name);
return;
}
len = ((*msg)[0] << 8) | (*msg)[1];
if (len != (*msglen - 2)) {
return;
}
*msg += 2;
*msglen -= 2;
BIO_printf(bio, " %s (%lu %s)\n", name, (unsigned long) len,
len != 1 ? "bytes" : "byte");
while (len > 0) {
int ext_type;
size_t ext_len;
pr_signals_handle();
if (*msglen < 4) {
break;
}
ext_type = ((*msg)[0] << 8) | (*msg)[1];
ext_len = ((*msg)[2] << 8) | (*msg)[3];
if (*msglen < (ext_len + 4)) {
break;
}
*msg += 4;
tls_print_extension(bio, " ", server, ext_type, *msg, ext_len);
*msg += ext_len;
*msglen -= (ext_len + 4);
}
}
static void tls_print_client_hello(int io_flag, int version, int content_type,
const unsigned char *buf, size_t buflen, SSL *ssl, void *arg) {
BIO *bio;
char *data = NULL;
long datalen;
bio = BIO_new(BIO_s_mem());
BIO_puts(bio, "\nClientHello:\n");
tls_print_ssl_version(bio, "client_version", &buf, &buflen, NULL);
tls_print_random(bio, &buf, &buflen);
tls_print_session_id(bio, &buf, &buflen);
if (buflen < 2) {
BIO_free(bio);
return;
}
tls_print_ciphersuites(bio, "cipher_suites", &buf, &buflen);
if (buflen < 1) {
BIO_free(bio);
return;
}
tls_print_compressions(bio, "compression_methods", &buf, &buflen);
tls_print_extensions(bio, "extensions", FALSE, &buf, &buflen);
datalen = BIO_get_mem_data(bio, &data);
if (data != NULL) {
data[datalen] = '\0';
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] %.*s",
(int) datalen, data);
}
BIO_free(bio);
}
static void tls_print_server_hello(int io_flag, int version, int content_type,
const unsigned char *buf, size_t buflen, SSL *ssl, void *arg) {
BIO *bio;
char *data = NULL;
long datalen;
int print_session_id = TRUE, print_compressions = TRUE, server_version;
unsigned int suiteno;
bio = BIO_new(BIO_s_mem());
BIO_puts(bio, "\nServerHello:\n");
tls_print_ssl_version(bio, "server_version", &buf, &buflen, &server_version);
#if defined(TLS1_3_VERSION)
if (server_version == TLS1_3_VERSION) {
print_session_id = FALSE;
print_compressions = FALSE;
}
#endif /* TLS1_3_VERSION */
tls_print_random(bio, &buf, &buflen);
if (print_session_id == TRUE) {
tls_print_session_id(bio, &buf, &buflen);
}
if (buflen < 2) {
BIO_free(bio);
return;
}
/* Print the selected ciphersuite. */
BIO_printf(bio, " cipher_suites (2 bytes)\n");
suiteno = (buf[0] << 8) | buf[1];
BIO_printf(bio, " %s (0x%x)\n",
tls_get_label(suiteno, tls_ciphersuite_labels), suiteno);
buf += 2;
buflen -= 2;
if (print_compressions == TRUE) {
int comp_type;
if (buflen < 1) {
BIO_free(bio);
return;
}
/* Print the selected compression. */
BIO_printf(bio, " compression_methods (1 byte)\n");
comp_type = *buf;
BIO_printf(bio, " %s\n",
tls_get_label(comp_type, tls_compression_labels));
buf += 1;
buflen -= 1;
}
tls_print_extensions(bio, "extensions", TRUE, &buf, &buflen);
datalen = BIO_get_mem_data(bio, &data);
if (data != NULL) {
data[datalen] = '\0';
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] %.*s",
(int) datalen, data);
}
BIO_free(bio);
}
# if defined(SSL3_MT_NEWSESSION_TICKET)
static void tls_print_ticket(int io_flag, int version, int content_type,
const unsigned char *buf, size_t buflen, SSL *ssl, void *arg) {
BIO *bio;
char *data = NULL;
long datalen;
bio = BIO_new(BIO_s_mem());
BIO_puts(bio, "\nNewSessionTicket:\n");
if (buflen != 0) {
unsigned int ticket_lifetime;
int print_ticket_age = FALSE, print_extensions = FALSE;
ticket_lifetime = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
buf += 4;
buflen -= 4;
BIO_printf(bio, " ticket_lifetime_hint\n %u (sec)\n", ticket_lifetime);
# if defined(TLS1_3_VERSION)
if (SSL_version(ssl) == TLS1_3_VERSION) {
print_ticket_age = TRUE;
print_extensions = TRUE;
}
# endif /* TLS1_3_VERSION */
if (print_ticket_age == TRUE) {
unsigned int ticket_age_add;
ticket_age_add = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
buf += 4;
buflen -= 4;
BIO_printf(bio, " ticket_age_add\n %u (sec)\n", ticket_age_add);
tls_print_hexbuf(bio, " ", " ticket_nonce", 1, &buf, &buflen);
}
tls_print_hexbuf(bio, " ", " ticket", 2, &buf, &buflen);
if (print_extensions == TRUE) {
tls_print_extensions(bio, "extensions", TRUE, &buf, &buflen);
}
} else {
BIO_puts(bio, " \n");
}
datalen = BIO_get_mem_data(bio, &data);
if (data != NULL) {
data[datalen] = '\0';
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "[tls.msg] %.*s",
(int) datalen, data);
}
BIO_free(bio);
}
# endif /* SSL3_MT_NEWSESSION_TICKET */
#if !defined(OPENSSL_NO_TLSEXT)
static void tls_tlsext_cb(SSL *ssl, int server, int type,
unsigned char *tlsext_data, int tlsext_datalen, void *user_data) {
char *extension_name = "(unknown)";
int print_basic_info = TRUE;
/* Note: OpenSSL does not implement all possible extensions. For the
* "(unknown)" extensions, see:
*
* http://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#tls-extensiontype-values-1
*/
switch (type) {
# if defined(TLSEXT_TYPE_server_name)
case TLSEXT_TYPE_server_name: {
BIO *bio = NULL;
char *ext_info = "";
long ext_infolen = 0;
extension_name = "server name";
if (pr_trace_get_level(trace_channel) >= 21 &&
tlsext_datalen >= 2) {
/* We read 2 bytes for the extension length, 1 byte for the
* name type, and 2 bytes for the name length. For the SNI
* format specification, see:
* https://tools.ietf.org/html/rfc6066#section-3
*/
int ext_len;
ext_len = (tlsext_data[0] << 8) | tlsext_data[1];
if (tlsext_datalen == ext_len + 2 &&
(ext_len & 1) == 0) {
int name_type;
tlsext_data += 2;
name_type = tlsext_data[0];
tlsext_data += 1;
if (name_type == TLSEXT_NAMETYPE_host_name) {
size_t name_len;
name_len = (tlsext_data[0] << 8) | tlsext_data[1];
tlsext_data += 2;
bio = BIO_new(BIO_s_mem());
BIO_printf(bio, "\n %.*s (%lu)", (int) name_len, tlsext_data,
(unsigned long) name_len);
ext_infolen = BIO_get_mem_data(bio, &ext_info);
if (ext_info != NULL) {
ext_info[ext_infolen] = '\0';
}
}
}
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.tlsext] TLS %s extension \"%s\" (ID %d, %d %s)%.*s",
server ? "server" : "client", extension_name, type, tlsext_datalen,
tlsext_datalen != 1 ? "bytes" : "byte", (int) ext_infolen,
ext_info != NULL ? ext_info : "");
if (bio != NULL) {
BIO_free(bio);
}
print_basic_info = FALSE;
break;
}
# endif /* TLSEXT_TYPE_server_name */
# if defined(TLSEXT_TYPE_max_fragment_length)
case TLSEXT_TYPE_max_fragment_length:
extension_name = "max fragment length";
break;
# endif /* TLSEXT_TYPE_max_fragment_length */
# if defined(TLSEXT_TYPE_client_certificate_url)
case TLSEXT_TYPE_client_certificate_url:
extension_name = "client certificate URL";
break;
# endif /* TLSEXT_TYPE_client_certificate_url */
# if defined(TLSEXT_TYPE_trusted_ca_keys)
case TLSEXT_TYPE_trusted_ca_keys:
extension_name = "trusted CA keys";
break;
# endif /* TLSEXT_TYPE_trusted_ca_keys */
# if defined(TLSEXT_TYPE_truncated_hmac)
case TLSEXT_TYPE_truncated_hmac:
extension_name = "truncated HMAC";
break;
# endif /* TLSEXT_TYPE_truncated_hmac */
# if defined(TLSEXT_TYPE_status_request)
case TLSEXT_TYPE_status_request:
extension_name = "status request";
break;
# endif /* TLSEXT_TYPE_status_request */
# if defined(TLSEXT_TYPE_user_mapping)
case TLSEXT_TYPE_user_mapping:
extension_name = "user mapping";
break;
# endif /* TLSEXT_TYPE_user_mapping */
# if defined(TLSEXT_TYPE_client_authz)
case TLSEXT_TYPE_client_authz:
extension_name = "client authz";
break;
# endif /* TLSEXT_TYPE_client_authz */
# if defined(TLSEXT_TYPE_server_authz)
case TLSEXT_TYPE_server_authz:
extension_name = "server authz";
break;
# endif /* TLSEXT_TYPE_server_authz */
# if defined(TLSEXT_TYPE_cert_type)
case TLSEXT_TYPE_cert_type:
extension_name = "cert type";
break;
# endif /* TLSEXT_TYPE_cert_type */
# if defined(TLSEXT_TYPE_elliptic_curves)
case TLSEXT_TYPE_elliptic_curves:
extension_name = "elliptic curves";
break;
# endif /* TLSEXT_TYPE_elliptic_curves */
# if defined(TLSEXT_TYPE_ec_point_formats)
case TLSEXT_TYPE_ec_point_formats:
extension_name = "EC point formats";
break;
# endif /* TLSEXT_TYPE_ec_point_formats */
# if defined(TLSEXT_TYPE_srp)
case TLSEXT_TYPE_srp:
extension_name = "SRP";
break;
# endif /* TLSEXT_TYPE_srp */
# if defined(TLSEXT_TYPE_signature_algorithms)
case TLSEXT_TYPE_signature_algorithms: {
BIO *bio = NULL;
char *ext_info = "";
long ext_infolen = 0;
extension_name = "signature algorithms";
if (pr_trace_get_level(trace_channel) >= 21 &&
tlsext_datalen >= 2) {
int len;
len = (tlsext_data[0] << 8) | tlsext_data[1];
if (tlsext_datalen == len + 2 &&
(len & 1) == 0) {
tlsext_data += 2;
bio = BIO_new(BIO_s_mem());
BIO_puts(bio, "\n");
while (len > 0) {
unsigned int sig_algo;
pr_signals_handle();
sig_algo = (tlsext_data[0] << 8) | tlsext_data[1];
BIO_printf(bio, " %s (0x%x)\n",
tls_get_label(sig_algo, tls_sigalgo_labels), sig_algo);
len -= 2;
tlsext_data += 2;
}
ext_infolen = BIO_get_mem_data(bio, &ext_info);
if (ext_info != NULL) {
ext_info[ext_infolen] = '\0';
}
}
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.tlsext] TLS %s extension \"%s\" (ID %d, %d %s)%.*s",
server ? "server" : "client", extension_name, type, tlsext_datalen,
tlsext_datalen != 1 ? "bytes" : "byte", (int) ext_infolen,
ext_info != NULL ? ext_info : "");
if (bio != NULL) {
BIO_free(bio);
}
print_basic_info = FALSE;
break;
}
# endif /* TLSEXT_TYPE_signature_algorithms */
# if defined(TLSEXT_TYPE_use_srtp)
case TLSEXT_TYPE_use_srtp:
extension_name = "use SRTP";
break;
# endif /* TLSEXT_TYPE_use_srtp */
# if defined(TLSEXT_TYPE_heartbeat)
case TLSEXT_TYPE_heartbeat:
extension_name = "heartbeat";
break;
# endif /* TLSEXT_TYPE_heartbeat */
# if defined(TLSEXT_TYPE_signed_certificate_timestamp)
case TLSEXT_TYPE_signed_certificate_timestamp:
extension_name = "signed certificate timestamp";
break;
# endif /* TLSEXT_TYPE_signed_certificate_timestamp */
# if defined(TLSEXT_TYPE_encrypt_then_mac)
case TLSEXT_TYPE_encrypt_then_mac:
extension_name = "encrypt then mac";
break;
# endif /* TLSEXT_TYPE_encrypt_then_mac */
# if defined(TLSEXT_TYPE_extended_master_secret)
case TLSEXT_TYPE_extended_master_secret:
extension_name = "extended master secret";
break;
# endif /* TLSEXT_TYPE_extended_master_secret */
# if defined(TLSEXT_TYPE_session_ticket)
case TLSEXT_TYPE_session_ticket:
extension_name = "session ticket";
break;
# endif /* TLSEXT_TYPE_session_ticket */
# if defined(TLSEXT_TYPE_psk)
case TLSEXT_TYPE_psk:
extension_name = "PSK";
break;
# endif /* TLSEXT_TYPE_psk */
# if defined(TLSEXT_TYPE_supported_versions)
case TLSEXT_TYPE_supported_versions: {
BIO *bio = NULL;
char *ext_info = NULL;
long ext_infolen = 0;
/* If we are the server responding, we only indicate the selected
* protocol version. Otherwise, we are a client indicating the range
* of versions supported.
*/
extension_name = "supported versions";
if (pr_trace_get_level(trace_channel) >= 21) {
bio = BIO_new(BIO_s_mem());
if (server) {
if (tlsext_datalen == 2) {
int version;
version = (tlsext_data[0] << 8) | tlsext_data[1];
BIO_printf(bio, "\n %s (0x%x)\n",
tls_get_label(version, tls_version_labels), version);
}
} else {
if (tlsext_datalen >= 1) {
int len;
len = tlsext_data[0];
if (tlsext_datalen == len + 1) {
tlsext_data += 1;
BIO_puts(bio, "\n");
while (len > 0) {
int version;
pr_signals_handle();
version = (tlsext_data[0] << 8) | tlsext_data[1];
BIO_printf(bio, " %s (0x%x)\n",
tls_get_label(version, tls_version_labels), version);
len -= 2;
tlsext_data += 2;
}
}
}
}
ext_infolen = BIO_get_mem_data(bio, &ext_info);
if (ext_info != NULL) {
ext_info[ext_infolen] = '\0';
}
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.tlsext] TLS %s extension \"%s\" (ID %d, %d %s)%.*s",
server ? "server" : "client", extension_name, type, tlsext_datalen,
tlsext_datalen != 1 ? "bytes" : "byte", (int) ext_infolen,
ext_info != NULL ? ext_info : "");
if (bio != NULL) {
BIO_free(bio);
}
print_basic_info = FALSE;
break;
}
# endif /* TLSEXT_TYPE_supported_versions */
# if defined(TLSEXT_TYPE_psk_kex_modes)
case TLSEXT_TYPE_psk_kex_modes: {
BIO *bio = NULL;
char *ext_info = NULL;
long ext_infolen = 0;
extension_name = "PSK KEX modes";
if (pr_trace_get_level(trace_channel) >= 19) {
if (tlsext_datalen >= 1) {
int len;
bio = BIO_new(BIO_s_mem());
len = tlsext_data[0];
if (tlsext_datalen == len + 1) {
tlsext_data += 1;
BIO_puts(bio, "\n");
while (len > 0) {
int kex_mode;
pr_signals_handle();
kex_mode = tlsext_data[0];
BIO_printf(bio, " %s (%d)\n",
tls_get_label(kex_mode, tls_psk_kex_labels), kex_mode);
len -= 1;
tlsext_data += 1;
}
}
}
ext_infolen = BIO_get_mem_data(bio, &ext_info);
if (ext_info != NULL) {
ext_info[ext_infolen] = '\0';
}
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.tlsext] TLS %s extension \"%s\" (ID %d, %d %s)%.*s",
server ? "server" : "client", extension_name, type, tlsext_datalen,
tlsext_datalen != 1 ? "bytes" : "byte", (int) ext_infolen,
ext_info != NULL ? ext_info : "");
if (bio != NULL) {
BIO_free(bio);
}
print_basic_info = FALSE;
break;
}
# endif /* TLSEXT_TYPE_psk_kex_modes */
# if defined(TLSEXT_TYPE_renegotiate)
case TLSEXT_TYPE_renegotiate:
extension_name = "renegotiation info";
break;
# endif /* TLSEXT_TYPE_renegotiate */
# if defined(TLSEXT_TYPE_opaque_prf_input)
case TLSEXT_TYPE_opaque_prf_input:
extension_name = "opaque PRF input";
break;
# endif /* TLSEXT_TYPE_opaque_prf_input */
# if defined(TLSEXT_TYPE_next_proto_neg)
case TLSEXT_TYPE_next_proto_neg:
extension_name = "next protocol";
break;
# endif /* TLSEXT_TYPE_next_proto_neg */
# if defined(TLSEXT_TYPE_application_layer_protocol_negotiation)
case TLSEXT_TYPE_application_layer_protocol_negotiation:
extension_name = "application layer protocol";
break;
# endif /* TLSEXT_TYPE_application_layer_protocol_negotiation */
# if defined(TLSEXT_TYPE_padding)
case TLSEXT_TYPE_padding:
extension_name = "TLS padding";
break;
# endif /* TLSEXT_TYPE_padding */
# if defined(TLSEXT_TYPE_early_data)
case TLSEXT_TYPE_early_data:
extension_name = "early data";
break;
# endif /* TLSEXT_TYPE_early_data */
# if defined(TLSEXT_TYPE_post_handshake_auth)
case TLSEXT_TYPE_post_handshake_auth:
extension_name = "post handshake auth";
break;
# endif /* TLSEXT_TYPE_post_handshake_auth */
default:
print_basic_info = TRUE;
break;
}
if (print_basic_info == TRUE) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.tlsext] TLS %s extension \"%s\" (ID %d, %d %s)",
server ? "server" : "client", extension_name, type, tlsext_datalen,
tlsext_datalen != 1 ? "bytes" : "byte");
}
}
#endif /* OPENSSL_NO_TLSEXT */
static void tls_msg_cb(int io_flag, int version, int content_type,
const void *buf, size_t buflen, SSL *ssl, void *arg) {
char *action_str = NULL;
char *version_str = NULL;
char *bytes_str = buflen != 1 ? "bytes" : "byte";
if (io_flag == 0) {
action_str = "received";
} else if (io_flag == 1) {
action_str = "sent";
}
switch (version) {
case SSL2_VERSION:
version_str = "SSLv2";
break;
case SSL3_VERSION:
version_str = "SSLv3";
break;
case TLS1_VERSION:
version_str = "TLSv1";
break;
# if defined(TLS1_1_VERSION)
case TLS1_1_VERSION:
version_str = "TLSv1.1";
break;
# endif /* TLS1_1_VERSION */
# if defined(TLS1_2_VERSION)
case TLS1_2_VERSION:
version_str = "TLSv1.2";
break;
# endif /* TLS1_2_VERSION */
# if defined(TLS1_3_VERSION)
case TLS1_3_VERSION:
version_str = "TLSv1.3";
break;
# endif /* TLS1_3_VERSION */
default:
# if defined(SSL3_RT_HEADER)
/* OpenSSL calls this callback for SSL records received; filter those
* from true "unknowns".
*/
if (version == 0 &&
(content_type != SSL3_RT_HEADER ||
buflen != SSL3_RT_HEADER_LENGTH)) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.msg] unknown/unsupported version: %d", version);
}
# else
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.msg] unknown/unsupported version: %d", version);
# endif
break;
}
if (version == SSL3_VERSION ||
# if defined(TLS1_1_VERSION)
version == TLS1_1_VERSION ||
# endif /* TLS1_1_VERSION */
# if defined(TLS1_2_VERSION)
version == TLS1_2_VERSION ||
# endif /* TLS1_2_VERSION */
# if defined(TLS1_3_VERSION)
version == TLS1_3_VERSION ||
# endif /* TLS1_3_VERSION */
version == TLS1_VERSION) {
switch (content_type) {
case SSL3_RT_CHANGE_CIPHER_SPEC:
/* ChangeCipherSpec message */
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.msg] %s %s ChangeCipherSpec message (%u %s)",
action_str, version_str, (unsigned int) buflen, bytes_str);
break;
case SSL3_RT_ALERT: {
/* Alert messages */
pr_trace_msg(trace_channel, 27,
"%s %s Alert (%u %s)", action_str, version_str,
(unsigned int) buflen, bytes_str);
if (buflen == 2) {
char *severity_str = NULL;
/* Peek naughtily into the buffer. */
switch (((const unsigned char *) buf)[0]) {
case SSL3_AL_WARNING:
severity_str = "warning";
break;
case SSL3_AL_FATAL:
severity_str = "fatal";
break;
}
switch (((const unsigned char *) buf)[1]) {
case SSL3_AD_CLOSE_NOTIFY:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.msg] %s %s %s 'close_notify' Alert message (%u %s)",
action_str, version_str, severity_str, (unsigned int) buflen,
bytes_str);
break;
case SSL3_AD_UNEXPECTED_MESSAGE:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.msg] %s %s %s 'unexpected_message' Alert message "
"(%u %s)", action_str, version_str, severity_str,
(unsigned int) buflen, bytes_str);
break;
case SSL3_AD_BAD_RECORD_MAC:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.msg] %s %s %s 'bad_record_mac' Alert message (%u %s)",
action_str, version_str, severity_str, (unsigned int) buflen,
bytes_str);
break;
case 21:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.msg] %s %s %s 'decryption_failed' Alert message "
"(%u %s)", action_str, version_str, severity_str,
(unsigned int) buflen, bytes_str);
break;
case 22:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.msg] %s %s %s 'record_overflow' Alert message (%u %s)",
action_str, version_str, severity_str, (unsigned int) buflen,
bytes_str);
break;
case 30:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.msg] %s %s %s 'decompression_failure' Alert message "
"(%u %s)", action_str, version_str, severity_str,
(unsigned int) buflen, bytes_str);
break;
case SSL3_AD_HANDSHAKE_FAILURE:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.msg] %s %s %s 'handshake_failure' Alert message "
"(%u %s)", action_str, version_str, severity_str,
(unsigned int) buflen, bytes_str);
break;
# if defined(SSL3_AD_BAD_CERTIFICATE)
case SSL3_AD_BAD_CERTIFICATE:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.msg] %s %s %s 'bad_certificate' Alert message "
"(%u %s)", action_str, version_str, severity_str,
(unsigned int) buflen, bytes_str);
break;
# endif /* SSL3_AD_BAD_CERTIFICATE */
# if defined(SSL3_AD_CERTIFICATE_REVOKED)
case SSL3_AD_CERTIFICATE_REVOKED:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.msg] %s %s %s 'certificate_revoked' Alert message "
"(%u %s)", action_str, version_str, severity_str,
(unsigned int) buflen, bytes_str);
break;
# endif /* SSL3_AD_CERTIFICATE_REVOKED */
# if defined(SSL3_AD_CERTIFICATE_EXPIRED)
case SSL3_AD_CERTIFICATE_EXPIRED:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.msg] %s %s %s 'certificate_expired' Alert message "
"(%u %s)", action_str, version_str, severity_str,
(unsigned int) buflen, bytes_str);
break;
# endif /* SSL3_AD_CERTIFICATE_EXPIRED */
# if defined(SSL3_AD_CERTIFICATE_UNKNOWN)
case SSL3_AD_CERTIFICATE_UNKNOWN:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.msg] %s %s %s 'certificate_unknown' Alert message "
"(%u %s)", action_str, version_str, severity_str,
(unsigned int) buflen, bytes_str);
break;
# endif /* SSL3_AD_CERTIFICATE_UNKNOWN */
# if defined(SSL3_AD_ILLEGAL_PARAMETER)
case SSL3_AD_ILLEGAL_PARAMETER:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.msg] %s %s %s 'illegal_parameter' Alert message "
"(%u %s)", action_str, version_str, severity_str,
(unsigned int) buflen, bytes_str);
break;
# endif /* SSL3_AD_ILLEGAL_PARAMETER */
}
} else {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.msg] %s %s Alert message, unknown type (%u %s)", action_str,
version_str, (unsigned int) buflen, bytes_str);
}
break;
}
case SSL3_RT_HANDSHAKE: {
/* Handshake messages */
pr_trace_msg(trace_channel, 27,
"%s %s Handshake (%u %s)", action_str, version_str,
(unsigned int) buflen, bytes_str);
if (buflen > 0) {
/* Peek naughtily into the buffer. */
switch (((const unsigned char *) buf)[0]) {
case SSL3_MT_HELLO_REQUEST:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.msg] %s %s 'HelloRequest' Handshake message (%u %s)",
action_str, version_str, (unsigned int) buflen, bytes_str);
break;
case SSL3_MT_CLIENT_HELLO: {
const unsigned char *msg;
size_t msglen;
msg = buf;
msglen = buflen;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.msg] %s %s 'ClientHello' Handshake message (%u %s)",
action_str, version_str, (unsigned int) buflen, bytes_str);
tls_print_client_hello(io_flag, version, content_type, msg + 4,
msglen - 4, ssl, arg);
break;
}
case SSL3_MT_SERVER_HELLO: {
const unsigned char *msg;
size_t msglen;
msg = buf;
msglen = buflen;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.msg] %s %s 'ServerHello' Handshake message (%u %s)",
action_str, version_str, (unsigned int) buflen, bytes_str);
tls_print_server_hello(io_flag, version, content_type, msg + 4,
msglen - 4, ssl, arg);
break;
}
# if defined(SSL3_MT_NEWSESSION_TICKET)
case SSL3_MT_NEWSESSION_TICKET: {
const unsigned char *msg;
size_t msglen;
msg = buf;
msglen = buflen;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.msg] %s %s 'NewSessionTicket' Handshake message (%u %s)",
action_str, version_str, (unsigned int) buflen, bytes_str);
tls_print_ticket(io_flag, version, content_type, msg + 4,
msglen - 4, ssl, arg);
break;
}
# endif /* SSL3_MT_NEWSESSION_TICKET */
case SSL3_MT_CERTIFICATE:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.msg] %s %s 'Certificate' Handshake message (%u %s)",
action_str, version_str, (unsigned int) buflen, bytes_str);
break;
case SSL3_MT_SERVER_KEY_EXCHANGE:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.msg] %s %s 'ServerKeyExchange' Handshake message "
"(%u %s)", action_str, version_str, (unsigned int) buflen,
bytes_str);
break;
case SSL3_MT_CERTIFICATE_REQUEST:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.msg] %s %s 'CertificateRequest' Handshake message "
"(%u %s)", action_str, version_str, (unsigned int) buflen,
bytes_str);
break;
case SSL3_MT_SERVER_DONE:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.msg] %s %s 'ServerHelloDone' Handshake message (%u %s)",
action_str, version_str, (unsigned int) buflen, bytes_str);
break;
case SSL3_MT_CERTIFICATE_VERIFY:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.msg] %s %s 'CertificateVerify' Handshake message "
"(%u %s)", action_str, version_str, (unsigned int) buflen,
bytes_str);
break;
case SSL3_MT_CLIENT_KEY_EXCHANGE:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.msg] %s %s 'ClientKeyExchange' Handshake message "
"(%u %s)", action_str, version_str, (unsigned int) buflen,
bytes_str);
break;
case SSL3_MT_FINISHED:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.msg] %s %s 'Finished' Handshake message (%u %s)",
action_str, version_str, (unsigned int) buflen, bytes_str);
break;
# if defined(SSL3_MT_CERTIFICATE_STATUS)
case SSL3_MT_CERTIFICATE_STATUS:
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.msg] %s %s 'CertificateStatus' Handshake message (%u %s)",
action_str, version_str, (unsigned int) buflen, bytes_str);
break;
# endif /* SSL3_MT_CERTIFICATE_STATUS */
# if defined(SSL3_MT_ENCRYPTED_EXTENSIONS)
case SSL3_MT_ENCRYPTED_EXTENSIONS: {
const unsigned char *msg;
size_t msglen;
BIO *bio;
char *data = NULL;
long datalen;
msg = buf;
msglen = buflen;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.msg] %s %s 'EncryptedExtensions' Handshake message "
"(%u %s)", action_str, version_str, (unsigned int) buflen,
bytes_str);
bio = BIO_new(BIO_s_mem());
msg += 4;
msglen -= 4;
BIO_puts(bio, "\nEncryptedExtensions:\n");
tls_print_extensions(bio, "extensions", TRUE, &msg, &msglen);
datalen = BIO_get_mem_data(bio, &data);
if (data != NULL) {
data[datalen] = '\0';
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.msg] %.*s", (int) datalen, data);
}
BIO_free(bio);
break;
}
# endif /* SSL3_MT_ENCRYPTED_EXTENSIONS */
}
} else {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.msg] %s %s Handshake message, unknown type %d (%u %s)",
action_str, version_str, content_type, (unsigned int) buflen,
bytes_str);
}
break;
}
}
# if defined(SSL3_RT_HEADER)
} else if (version == 0 &&
content_type == SSL3_RT_HEADER &&
buflen == SSL3_RT_HEADER_LENGTH) {
const unsigned char *msg;
const char *record_type;
unsigned int msg_len;
msg = buf;
record_type = tls_get_label(msg[0], tls_record_type_labels);
msg_len = msg[buflen - 2] << 8 | msg[buflen - 1];
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.msg] %s protocol record message (content_type = %s, len = %u)",
action_str, record_type, msg_len);
# endif
} else {
/* This case might indicate an issue with OpenSSL itself; the version
* given to the msg_callback function was not initialized, or not set to
* one of the recognized SSL/TLS versions. Weird.
*/
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.msg] %s message of unknown version %d, type %d (%u %s)",
action_str, version, content_type, (unsigned int) buflen, bytes_str);
}
}
# endif /* OpenSSL-0.9.7 or later */
#endif /* PR_USE_OPENSSL */
int proxy_tls_sess_init(pool *p, struct proxy_session *proxy_sess, int flags) {
#if defined(PR_USE_OPENSSL)
config_rec *c;
unsigned int enabled_proto_count = 0, tls_protocol = PROXY_TLS_PROTO_DEFAULT;
int disabled_proto, res, xerrno = 0;
const char *enabled_proto_str = NULL;
char *ca_file = NULL, *ca_path = NULL, *cert_file = NULL, *key_file = NULL,
*crl_file = NULL, *crl_path = NULL;
if (proxy_sess == NULL) {
errno = EINVAL;
return -1;
}
if (proxy_sess->use_ftp == FALSE) {
return 0;
}
c = find_config(main_server->conf, CONF_PARAM, "ProxyTLSEngine", FALSE);
if (c != NULL) {
tls_engine = *((int *) c->argv[0]);
}
if (tls_engine == PROXY_TLS_ENGINE_OFF) {
return 0;
}
if (p == NULL) {
errno = EINVAL;
return -1;
}
if (ssl_ctx == NULL) {
/* We haven't been initialized properly! */
pr_trace_msg(trace_channel, 2, "%s",
"missing required SSL_CTX initialization!");
errno = EPERM;
return -1;
}
c = find_config(main_server->conf, CONF_PARAM, "ProxyTLSOptions", FALSE);
while (c != NULL) {
unsigned long opts = 0;
pr_signals_handle();
opts = *((unsigned long *) c->argv[0]);
tls_opts |= opts;
c = find_config_next(c, c->next, CONF_PARAM, "ProxyTLSOptions", FALSE);
}
PRIVS_ROOT
tls_ds.dsh = (tls_ds.open)(proxy_pool, tls_tables_path, tls_opts);
xerrno = errno;
PRIVS_RELINQUISH
if (tls_ds.dsh == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error opening TLS datastore: %s", strerror(xerrno));
errno = xerrno;
return -1;
}
c = find_config(main_server->conf, CONF_PARAM, "ProxyTLSProtocol", FALSE);
if (c != NULL) {
tls_protocol = *((unsigned int *) c->argv[0]);
}
disabled_proto = get_disabled_protocols(tls_protocol);
/* Per the comments in , SSL_CTX_set_options() uses |= on
* the previous value. This means we can easily OR in our new option
* values with any previously set values.
*/
enabled_proto_str = get_enabled_protocols_str(main_server->pool,
tls_protocol, &enabled_proto_count);
pr_log_debug(DEBUG8, MOD_PROXY_VERSION ": supporting %s %s",
enabled_proto_str,
enabled_proto_count != 1 ? "protocols" : "protocol only");
SSL_CTX_set_options(ssl_ctx, disabled_proto);
c = find_config(main_server->conf, CONF_PARAM, "ProxyTLSCipherSuite", FALSE);
while (c != NULL) {
int protocol;
pr_signals_handle();
protocol = *((int *) c->argv[1]);
#if OPENSSL_VERSION_NUMBER >= 0x10101000L && \
defined(TLS1_3_VERSION)
if (protocol == PROXY_TLS_PROTO_TLS_V1_3) {
tlsv13_cipher_suite = c->argv[0];
} else {
tls_cipher_suite = c->argv[0];
}
#else
tls_cipher_suite = c->argv[0];
#endif /* TLS1_3_VERSION */
c = find_config_next(c, c->next, CONF_PARAM, "ProxyTLSCipherSuite", FALSE);
}
if (tls_cipher_suite == NULL) {
tls_cipher_suite = PROXY_TLS_DEFAULT_CIPHER_SUITE;
}
SSL_CTX_set_cipher_list(ssl_ctx, tls_cipher_suite);
#if OPENSSL_VERSION_NUMBER >= 0x10101000L && \
defined(TLS1_3_VERSION)
if (tlsv13_cipher_suite != NULL) {
SSL_CTX_set_ciphersuites(ssl_ctx, tlsv13_cipher_suite);
}
#endif /* TLS1_3_VERSION */
c = find_config(main_server->conf, CONF_PARAM, "ProxyTLSTimeoutHandshake",
FALSE);
if (c != NULL) {
handshake_timeout = *((unsigned int *) c->argv[0]);
}
c = find_config(main_server->conf, CONF_PARAM, "ProxyTLSCACertificateFile",
FALSE);
if (c != NULL) {
ca_file = c->argv[0];
} else {
ca_file = PR_CONFIG_DIR "/cacerts.pem";
if (!file_exists2(p, ca_file)) {
pr_trace_msg(trace_channel, 9,
"warning: no default ProxyTLSCACertificateFile found at '%s'", ca_file);
ca_file = NULL;
}
}
c = find_config(main_server->conf, CONF_PARAM, "ProxyTLSCACertificatePath",
FALSE);
if (c != NULL) {
ca_path = c->argv[0];
}
if (ca_file != NULL ||
ca_path != NULL) {
long verify_flags = 0;
X509_VERIFY_PARAM *verify_param;
/* Set the locations used for verifying certificates. */
PRIVS_ROOT
if (SSL_CTX_load_verify_locations(ssl_ctx, ca_file, ca_path) != 1) {
PRIVS_RELINQUISH
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to set CA verification using file '%s' or "
"directory '%s': %s", ca_file ? ca_file : "(none)",
ca_path ? ca_path : "(none)", proxy_tls_get_errors());
errno = EPERM;
return -1;
}
PRIVS_RELINQUISH
verify_param = X509_VERIFY_PARAM_new();
#if 0
/* NOTE: Many server certs may not have a CRL provider configured; such certs
* would be deemed invalid/unusable by these CRL_CHECK flags. So they are
* disabled, for now.
*/
# if defined(X509_V_FLAG_CRL_CHECK)
verify_flags |= X509_V_FLAG_CRL_CHECK;
# endif
# if defined(X509_V_FLAG_CRL_CHECK_ALL)
verify_flags |= X509_V_FLAG_CRL_CHECK_ALL;
# endif
#endif
# if defined(X509_V_FLAG_TRUSTED_FIRST)
verify_flags |= X509_V_FLAG_TRUSTED_FIRST;
# endif
# if defined(X509_V_FLAG_PARTIAL_CHAIN)
verify_flags |= X509_V_FLAG_PARTIAL_CHAIN;
# endif
if (X509_VERIFY_PARAM_set_flags(verify_param, verify_flags) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error preparing X509 verification parameters: %s",
proxy_tls_get_errors());
} else {
if (SSL_CTX_set1_param(ssl_ctx, verify_param) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error setting X509 verification parameters: %s",
proxy_tls_get_errors());
}
}
} else {
/* Default to using locations set in the OpenSSL config file. */
pr_trace_msg(trace_channel, 9,
"using default OpenSSL CA verification locations (see $SSL_CERT_DIR "
"environment variable)");
if (SSL_CTX_set_default_verify_paths(ssl_ctx) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error setting default CA verification locations: %s",
proxy_tls_get_errors());
}
}
c = find_config(main_server->conf, CONF_PARAM, "ProxyTLSCARevocationFile",
FALSE);
if (c != NULL) {
crl_file = c->argv[0];
}
c = find_config(main_server->conf, CONF_PARAM, "ProxyTLSCARevocationPath",
FALSE);
if (c != NULL) {
crl_path = c->argv[0];
}
if (crl_file != NULL ||
crl_path != NULL) {
X509_STORE *crl_store;
crl_store = X509_STORE_new();
if (crl_store == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error allocating CRL store: %s", proxy_tls_get_errors());
errno = EPERM;
return -1;
}
if (X509_STORE_load_locations(crl_store, crl_file, crl_path) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error loading ProxyTLSCARevocation files: %s", proxy_tls_get_errors());
} else {
SSL_CTX_set_cert_store(ssl_ctx, crl_store);
}
}
c = find_config(main_server->conf, CONF_PARAM, "ProxyTLSVerifyServer", FALSE);
if (c != NULL) {
tls_verify_server = *((int *) c->argv[0]);
}
c = find_config(main_server->conf, CONF_PARAM, "ProxyTLSCertificateFile",
FALSE);
if (c != NULL) {
cert_file = c->argv[0];
}
c = find_config(main_server->conf, CONF_PARAM, "ProxyTLSCertificateKeyFile",
FALSE);
if (c != NULL) {
key_file = c->argv[0];
} else {
key_file = cert_file;
}
if (cert_file != NULL) {
int ok = TRUE;
PRIVS_ROOT
res = SSL_CTX_use_certificate_file(ssl_ctx, cert_file, SSL_FILETYPE_PEM);
PRIVS_RELINQUISH
if (res != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error loading certificate from ProxyTLSCertificateFile '%s': %s",
cert_file, proxy_tls_get_errors());
ok = FALSE;
}
if (ok) {
PRIVS_ROOT
res = SSL_CTX_use_PrivateKey_file(ssl_ctx, key_file, SSL_FILETYPE_PEM);
PRIVS_RELINQUISH
if (res != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error loading private key from ProxyTLSCertificateKeyFile '%s': %s",
key_file, proxy_tls_get_errors());
ok = FALSE;
}
}
if (ok) {
res = SSL_CTX_check_private_key(ssl_ctx);
if (res != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"warning: ProxyTLSCertificateKeyFile '%s' private key does not "
"match ProxyTLSCertificateFile '%s' certificate", key_file,
cert_file);
}
}
}
# if defined(PSK_MAX_PSK_LEN)
c = find_config(main_server->conf, CONF_PARAM, "ProxyTLSPreSharedKey", FALSE);
if (c != NULL) {
const char *identity, *path;
pr_signals_handle();
identity = c->argv[0];
path = c->argv[1];
/* Advance past the "hex:" format prefix. */
path += 4;
res = tls_load_psk(identity, path);
if (res < 0) {
pr_log_debug(DEBUG2, MOD_PROXY_VERSION
": error loading ProxyTLSPreSharedKey file '%s': %s", path,
strerror(errno));
}
}
if (tls_psk_name != NULL) {
pr_trace_msg(trace_channel, 9,
"enabling support for PSK identities");
SSL_CTX_set_psk_client_callback(ssl_ctx, tls_psk_cb);
}
# endif /* PSK support */
# if !defined(OPENSSL_NO_TLSEXT) && defined(TLSEXT_STATUSTYPE_ocsp)
SSL_CTX_set_tlsext_status_cb(ssl_ctx, tls_ocsp_response_cb);
# endif /* OCSP support */
if (tls_opts & PROXY_TLS_OPT_ALLOW_WEAK_SECURITY) {
# if OPENSSL_VERSION_NUMBER >= 0x10100000L
SSL_CTX_set_security_level(ssl_ctx, 0);
# endif /* OpenSSL-1.1.0 and later */
}
if (tls_opts & PROXY_TLS_OPT_ENABLE_DIAGS) {
SSL_CTX_set_info_callback(ssl_ctx, tls_info_cb);
# if OPENSSL_VERSION_NUMBER > 0x000907000L
SSL_CTX_set_msg_callback(ssl_ctx, tls_msg_cb);
# endif
}
if (netio_install_ctrl() < 0) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error installing control connection proxy NetIO: %s", strerror(xerrno));
errno = xerrno;
return -1;
}
#endif /* PR_USE_OPENSSL */
return 0;
}
int proxy_tls_sess_free(pool *p) {
if (p == NULL) {
errno = EINVAL;
return -1;
}
#if defined(PR_USE_OPENSSL)
/* Reset any state, but only if we have not already negotiated an SSL
* session.
*/
if (session.rfc2228_mech == NULL) {
handshake_timeout = 30;
tls_opts = 0UL;
tls_engine = PROXY_TLS_ENGINE_AUTO;
tls_verify_server = TRUE;
tls_cipher_suite = NULL;
# if OPENSSL_VERSION_NUMBER >= 0x10101000L && \
defined(TLS1_3_VERSION)
tlsv13_cipher_suite = NULL;
# endif /* TLS1_3_VERSION */
# if defined(PSK_MAX_PSK_LEN)
tls_psk_name = NULL;
tls_psk_bn = NULL;
tls_psk_used = FALSE;
# endif /* PSK support */
if (tls_ds.dsh != NULL) {
(void) (tls_ds.close)(p, tls_ds.dsh);
tls_ds.dsh = NULL;
}
if (ssl_ctx != NULL) {
if (init_ssl_ctx() < 0) {
return -1;
}
}
if (tls_ctrl_netio != NULL) {
pr_netio_t *using_netio = NULL;
if (proxy_netio_using(PR_NETIO_STRM_CTRL, &using_netio) == 0) {
if (using_netio == tls_ctrl_netio) {
proxy_netio_use(PR_NETIO_STRM_CTRL, NULL);
}
}
destroy_pool(tls_ctrl_netio->pool);
tls_ctrl_netio = NULL;
}
}
#endif /* PR_USE_OPENSSL */
return 0;
}
proftpd-mod_proxy-0.9.5/lib/proxy/tls/ 0000775 0000000 0000000 00000000000 14757370167 0017773 5 ustar 00root root 0000000 0000000 proftpd-mod_proxy-0.9.5/lib/proxy/tls/db.c 0000664 0000000 0000000 00000032461 14757370167 0020532 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy TLS Database implementation
* Copyright (c) 2017-2021 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "proxy/db.h"
#include "proxy/tls.h"
#include "proxy/tls/db.h"
#ifdef PR_USE_OPENSSL
extern xaset_t *server_list;
static const char *trace_channel = "proxy.tls.db";
#define PROXY_TLS_DB_SCHEMA_NAME "proxy_tls"
#define PROXY_TLS_DB_SCHEMA_VERSION 3
static unsigned long db_opts = 0UL;
static int tls_db_add_sess(pool *p, void *dbh, const char *key,
SSL_SESSION *sess) {
int res, vhost_id, xerrno = 0;
const char *stmt, *errstr = NULL;
BIO *bio;
char *data = NULL;
long datalen = 0;
array_header *results;
bio = BIO_new(BIO_s_mem());
BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL);
res = PEM_write_bio_SSL_SESSION(bio, sess);
if (res != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error writing PEM-encoded SSL session data: %s", proxy_tls_get_errors());
}
(void) BIO_flush(bio);
datalen = BIO_get_mem_data(bio, &data);
if (data == NULL) {
pr_trace_msg(trace_channel, 9,
"no PEM data found for SSL session, not caching");
BIO_free(bio);
return 0;
}
data[datalen] = '\0';
if (db_opts & PROXY_TLS_OPT_ENABLE_DIAGS) {
BIO *diags_bio;
diags_bio = BIO_new(BIO_s_mem());
if (diags_bio != NULL) {
if (SSL_SESSION_print(diags_bio, sess) == 1) {
char *diags_data = NULL;
long diags_datalen = 0;
diags_datalen = BIO_get_mem_data(diags_bio, &diags_data);
if (diags_data != NULL) {
diags_data[diags_datalen] = '\0';
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.db] caching SSL session (%lu bytes):\n%s",
(unsigned long) datalen, diags_data);
}
}
}
}
/* We use INSERT OR REPLACE here to get upsert semantics; we only want/
* need one cached SSL session per URI.
*/
stmt = "INSERT OR REPLACE INTO proxy_tls_sessions (vhost_id, backend_uri, session) VALUES (?, ?, ?);";
res = proxy_db_prepare_stmt(p, dbh, stmt);
if (res < 0) {
xerrno = errno;
BIO_free(bio);
errno = xerrno;
return -1;
}
vhost_id = main_server->sid;
res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT,
(void *) &vhost_id, 0);
if (res < 0) {
xerrno = errno;
BIO_free(bio);
errno = xerrno;
return -1;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT,
(void *) key, -1);
if (res < 0) {
xerrno = errno;
BIO_free(bio);
errno = xerrno;
return -1;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 3, PROXY_DB_BIND_TYPE_TEXT,
(void *) data, -1);
if (res < 0) {
xerrno = errno;
BIO_free(bio);
errno = xerrno;
return -1;
}
results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr);
if (results == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr ? errstr : strerror(errno));
BIO_free(bio);
errno = EPERM;
return -1;
}
BIO_free(bio);
pr_trace_msg(trace_channel, 17, "cached SSL session for key '%s'", key);
return 0;
}
static int tls_db_remove_sess(pool *p, void *dbh, const char *key) {
int res, vhost_id;
const char *stmt, *errstr = NULL;
array_header *results;
stmt = "DELETE FROM proxy_tls_sessions WHERE vhost_id = ? AND backend_uri = ?;";
res = proxy_db_prepare_stmt(p, dbh, stmt);
if (res < 0) {
return -1;
}
vhost_id = main_server->sid;
res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT,
(void *) &vhost_id, 0);
if (res < 0) {
return -1;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT,
(void *) key, -1);
if (res < 0) {
return -1;
}
results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr);
if (results == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr ? errstr : strerror(errno));
errno = EPERM;
return -1;
}
return 0;
}
static SSL_SESSION *tls_db_get_sess(pool *p, void *dbh, const char *key) {
int res, vhost_id;
BIO *bio;
const char *stmt, *errstr = NULL;
array_header *results;
char *data = NULL;
size_t datalen;
SSL_SESSION *sess = NULL;
stmt = "SELECT session FROM proxy_tls_sessions WHERE vhost_id = ? AND backend_uri = ?;";
res = proxy_db_prepare_stmt(p, dbh, stmt);
if (res < 0) {
return NULL;
}
vhost_id = main_server->sid;
res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT,
(void *) &vhost_id, 0);
if (res < 0) {
return NULL;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT,
(void *) key, -1);
if (res < 0) {
return NULL;
}
results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr);
if (results == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr ? errstr : strerror(errno));
errno = EPERM;
return NULL;
}
if (results->nelts == 0) {
errno = ENOENT;
return NULL;
}
data = ((char **) results->elts)[0];
datalen = strlen(data) + 1;
bio = BIO_new_mem_buf(data, datalen);
sess = PEM_read_bio_SSL_SESSION(bio, NULL, 0, NULL);
if (sess == NULL) {
pr_trace_msg(trace_channel, 3,
"error converting database entry to SSL session: %s",
proxy_tls_get_errors());
}
BIO_free(bio);
if (sess == NULL) {
errno = ENOENT;
return NULL;
}
return sess;
}
static int tls_db_count_sess(pool *p, void *dbh) {
int count = 0, res;
const char *stmt, *errstr = NULL;
array_header *results;
stmt = "SELECT COUNT(*) FROM proxy_tls_sessions;";
res = proxy_db_prepare_stmt(p, dbh, stmt);
if (res < 0) {
return -1;
}
results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr);
if (results == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr ? errstr : strerror(errno));
errno = EPERM;
return -1;
}
if (results->nelts != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"expected 1 result from statement '%s', got %d", stmt,
results->nelts);
errno = EINVAL;
return -1;
}
count = atoi(((char **) results->elts)[0]);
return count;
}
/* Initialization routines */
static int tls_db_add_schema(pool *p, void *dbh, const char *db_path) {
int res;
const char *stmt, *errstr = NULL;
/* CREATE TABLE proxy_tls_vhosts (
* vhost_id INTEGER NOT NULL PRIMARY KEY,
* vhost_name TEXT NOT NULL
* );
*/
stmt = "CREATE TABLE IF NOT EXISTS proxy_tls_vhosts (vhost_id INTEGER NOT NULL PRIMARY KEY, vhost_name TEXT NOT NULL);";
res = proxy_db_exec_stmt(p, dbh, stmt, &errstr);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr);
errno = EPERM;
return -1;
}
/* CREATE TABLE proxy_tls_sessions (
* backend_uri STRING NOT NULL PRIMARY KEY,
* vhost_id INTEGER NOT NULL,
* session TEXT NOT NULL,
* FOREIGN KEY (vhost_id) REFERENCES proxy_tls_vhosts (vhost_id)
* );
*/
stmt = "CREATE TABLE IF NOT EXISTS proxy_tls_sessions (backend_uri STRING NOT NULL PRIMARY KEY, vhost_id INTEGER NOT NULL, session TEXT NOT NULL, FOREIGN KEY (vhost_id) REFERENCES proxy_tls_hosts (vhost_id));";
res = proxy_db_exec_stmt(p, dbh, stmt, &errstr);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr);
errno = EPERM;
return -1;
}
/* Note that we deliberately do NOT truncate the session cache table. */
return 0;
}
static int tls_truncate_db_tables(pool *p, void *dbh) {
int res;
const char *stmt, *errstr = NULL;
stmt = "DELETE FROM proxy_tls_vhosts;";
res = proxy_db_exec_stmt(p, dbh, stmt, &errstr);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr);
errno = EPERM;
return -1;
}
/* Note that we deliberately do NOT truncate the session cache table. */
return 0;
}
static int tls_db_add_vhost(pool *p, void *dbh, server_rec *s) {
int res, xerrno = 0;
const char *stmt, *errstr = NULL;
array_header *results;
stmt = "INSERT INTO proxy_tls_vhosts (vhost_id, vhost_name) VALUES (?, ?);";
res = proxy_db_prepare_stmt(p, dbh, stmt);
if (res < 0) {
xerrno = errno;
(void) pr_log_debug(DEBUG3, MOD_PROXY_VERSION
": error preparing statement '%s': %s", stmt, strerror(xerrno));
errno = xerrno;
return -1;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 1, PROXY_DB_BIND_TYPE_INT,
(void *) &(s->sid), 0);
if (res < 0) {
return -1;
}
res = proxy_db_bind_stmt(p, dbh, stmt, 2, PROXY_DB_BIND_TYPE_TEXT,
(void *) s->ServerName, -1);
if (res < 0) {
return -1;
}
results = proxy_db_exec_prepared_stmt(p, dbh, stmt, &errstr);
if (results == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error executing '%s': %s", stmt, errstr ? errstr : strerror(errno));
errno = EPERM;
return -1;
}
return 0;
}
static int tls_db_init(pool *p, const char *tables_path, int flags) {
int db_flags, res, xerrno = 0;
server_rec *s;
struct proxy_dbh *dbh = NULL;
const char *db_path = NULL;
if (tables_path == NULL) {
errno = EINVAL;
return -1;
}
db_path = pdircat(p, tables_path, "proxy-tls.db", NULL);
db_flags = PROXY_DB_OPEN_FL_SCHEMA_VERSION_CHECK|PROXY_DB_OPEN_FL_INTEGRITY_CHECK|PROXY_DB_OPEN_FL_VACUUM;
if (flags & PROXY_DB_OPEN_FL_SKIP_VACUUM) {
/* If the caller needs us to skip the vacuum, we will. */
db_flags &= ~PROXY_DB_OPEN_FL_VACUUM;
}
PRIVS_ROOT
dbh = proxy_db_open_with_version(p, db_path, PROXY_TLS_DB_SCHEMA_NAME,
PROXY_TLS_DB_SCHEMA_VERSION, db_flags);
xerrno = errno;
PRIVS_RELINQUISH
if (dbh == NULL) {
(void) pr_log_pri(PR_LOG_NOTICE, MOD_PROXY_VERSION
": error opening database '%s' for schema '%s', version %u: %s",
db_path, PROXY_TLS_DB_SCHEMA_NAME, PROXY_TLS_DB_SCHEMA_VERSION,
strerror(xerrno));
errno = xerrno;
return -1;
}
res = tls_db_add_schema(p, dbh, db_path);
if (res < 0) {
xerrno = errno;
(void) pr_log_debug(DEBUG0, MOD_PROXY_VERSION
": error creating schema in database '%s' for '%s': %s", db_path,
PROXY_TLS_DB_SCHEMA_NAME, strerror(xerrno));
(void) proxy_db_close(p, dbh);
errno = xerrno;
return -1;
}
res = tls_truncate_db_tables(p, dbh);
if (res < 0) {
xerrno = errno;
(void) proxy_db_close(p, dbh);
errno = xerrno;
return -1;
}
for (s = (server_rec *) server_list->xas_list; s; s = s->next) {
res = tls_db_add_vhost(p, dbh, s);
if (res < 0) {
xerrno = errno;
(void) pr_log_debug(DEBUG0, MOD_PROXY_VERSION
": error adding database entry for server '%s' in '%s': %s",
s->ServerName, PROXY_TLS_DB_SCHEMA_NAME, strerror(xerrno));
(void) proxy_db_close(p, dbh);
errno = xerrno;
return -1;
}
}
(void) proxy_db_close(p, dbh);
return 0;
}
static int tls_db_close(pool *p, void *dbh) {
if (dbh != NULL) {
if (proxy_db_close(p, dbh) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error closing %s database: %s", PROXY_TLS_DB_SCHEMA_NAME,
strerror(errno));
}
}
return 0;
}
static void *tls_db_open(pool *p, const char *tables_dir, unsigned long opts) {
int xerrno = 0;
struct proxy_dbh *dbh;
const char *db_path;
db_path = pdircat(p, tables_dir, "proxy-tls.db", NULL);
PRIVS_ROOT
dbh = proxy_db_open_with_version(p, db_path, PROXY_TLS_DB_SCHEMA_NAME,
PROXY_TLS_DB_SCHEMA_VERSION, 0);
xerrno = errno;
PRIVS_RELINQUISH
if (dbh == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error opening database '%s' for schema '%s', version %u: %s",
db_path, PROXY_TLS_DB_SCHEMA_NAME, PROXY_TLS_DB_SCHEMA_VERSION,
strerror(xerrno));
errno = xerrno;
return NULL;
}
db_opts = opts;
return dbh;
}
#endif /* PR_USE_OPENSSL */
int proxy_tls_db_as_datastore(struct proxy_tls_datastore *ds, void *ds_data,
size_t ds_datasz) {
if (ds == NULL) {
errno = EINVAL;
return -1;
}
(void) ds_data;
(void) ds_datasz;
#ifdef PR_USE_OPENSSL
ds->add_sess = tls_db_add_sess;
ds->remove_sess = tls_db_remove_sess;
ds->get_sess = tls_db_get_sess;
ds->count_sess = tls_db_count_sess;
ds->init = tls_db_init;
ds->open = tls_db_open;
ds->close = tls_db_close;
#endif /* PR_USE_OPENSSL */
return 0;
}
proftpd-mod_proxy-0.9.5/lib/proxy/tls/redis.c 0000664 0000000 0000000 00000022724 14757370167 0021254 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy TLS Redis implementation
* Copyright (c) 2017-2020 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "redis.h"
#include "proxy/tls.h"
#include "proxy/tls/redis.h"
#ifdef PR_USE_OPENSSL
extern xaset_t *server_list;
static const char *trace_channel = "proxy.tls.redis";
static void *redis_prefix = NULL;
static size_t redis_prefixsz = 0;
static unsigned long redis_opts = 0UL;
static char *make_key(pool *p, unsigned int vhost_id) {
char *key;
size_t keysz;
keysz = 64;
key = pcalloc(p, keysz + 1);
snprintf(key, keysz, "proxy_tls_sessions:vhost#%u", vhost_id);
return key;
}
static int tls_redis_add_sess(pool *p, void *redis, const char *sess_key,
SSL_SESSION *sess) {
int res, xerrno = 0;
pool *tmp_pool;
char *key;
BIO *bio;
char *data = NULL;
long datalen = 0;
bio = BIO_new(BIO_s_mem());
BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL);
res = PEM_write_bio_SSL_SESSION(bio, sess);
if (res != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error writing PEM-encoded SSL session data: %s", proxy_tls_get_errors());
}
(void) BIO_flush(bio);
datalen = BIO_get_mem_data(bio, &data);
if (data == NULL) {
pr_trace_msg(trace_channel, 9,
"no PEM data found for SSL session, not caching");
BIO_free(bio);
return 0;
}
data[datalen] = '\0';
if (redis_opts & PROXY_TLS_OPT_ENABLE_DIAGS) {
BIO *diags_bio;
diags_bio = BIO_new(BIO_s_mem());
if (diags_bio != NULL) {
if (SSL_SESSION_print(diags_bio, sess) == 1) {
char *diags_data = NULL;
long diags_datalen = 0;
diags_datalen = BIO_get_mem_data(diags_bio, &diags_data);
if (diags_data != NULL) {
diags_data[diags_datalen] = '\0';
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"[tls.redis] caching SSL session (%lu bytes):\n%s",
(unsigned long) datalen, diags_data);
}
}
}
}
tmp_pool = make_sub_pool(p);
key = make_key(tmp_pool, main_server->sid);
res = pr_redis_hash_set(redis, &proxy_module, key, sess_key, data, datalen);
xerrno = errno;
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error setting value for field '%s' in Redis hash '%s': %s", sess_key,
key, strerror(xerrno));
destroy_pool(tmp_pool);
BIO_free(bio);
errno = xerrno;
return -1;
}
pr_trace_msg(trace_channel, 17, "cached SSL session (%lu bytes) for key '%s'",
(unsigned long) datalen, sess_key);
destroy_pool(tmp_pool);
BIO_free(bio);
return 0;
}
static int tls_redis_remove_sess(pool *p, void *redis, const char *sess_key) {
int res, xerrno;
pool *tmp_pool;
char *key;
tmp_pool = make_sub_pool(p);
key = make_key(tmp_pool, main_server->sid);
res = pr_redis_hash_delete(redis, &proxy_module, key, sess_key);
xerrno = errno;
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error deleting field '%s' from Redis hash '%s': %s", sess_key, key,
strerror(xerrno));
destroy_pool(tmp_pool);
errno = xerrno;
return -1;
}
pr_trace_msg(trace_channel, 17, "removed cached SSL session for key '%s'",
sess_key);
destroy_pool(tmp_pool);
return 0;
}
static SSL_SESSION *tls_redis_get_sess(pool *p, void *redis,
const char *sess_key) {
int res, xerrno;
pool *tmp_pool;
BIO *bio;
char *key;
char *data = NULL;
size_t datalen = 0;
SSL_SESSION *sess = NULL;
tmp_pool = make_sub_pool(p);
key = make_key(tmp_pool, main_server->sid);
res = pr_redis_hash_get(tmp_pool, redis, &proxy_module, key, sess_key,
(void **) &data, &datalen);
xerrno = errno;
if (res < 0) {
if (xerrno != ENOENT) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error getting value for field '%s' from Redis hash '%s': %s", sess_key,
key, strerror(xerrno));
}
destroy_pool(tmp_pool);
errno = xerrno;
return NULL;
}
pr_trace_msg(trace_channel, 19,
"retrieved cached session (%lu bytes) for key '%s'",
(unsigned long) datalen, sess_key);
bio = BIO_new_mem_buf((char *) data, datalen);
sess = PEM_read_bio_SSL_SESSION(bio, NULL, 0, NULL);
destroy_pool(tmp_pool);
if (sess == NULL) {
pr_trace_msg(trace_channel, 3,
"error converting database entry to SSL session: %s",
proxy_tls_get_errors());
}
BIO_free(bio);
if (sess == NULL) {
errno = ENOENT;
return NULL;
}
pr_trace_msg(trace_channel, 17, "retrieved cached SSL session for key '%s'",
sess_key);
return sess;
}
static int tls_redis_count_sess(pool *p, void *redis) {
int res, xerrno;
uint64_t count = 0;
pool *tmp_pool;
char *key;
tmp_pool = make_sub_pool(p);
key = make_key(tmp_pool, main_server->sid);
res = pr_redis_hash_count(redis, &proxy_module, key, &count);
xerrno = errno;
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error getting size of Redis hash '%s': %s", key, strerror(xerrno));
destroy_pool(tmp_pool);
errno = xerrno;
return -1;
}
destroy_pool(tmp_pool);
return (int) count;
}
/* Initialization routines */
static int tls_redis_truncate_tables(pool *p, pr_redis_t *redis,
unsigned int vhost_id) {
register unsigned int i;
int res, xerrno;
pool *tmp_pool;
const char *key;
array_header *fields = NULL;
tmp_pool = make_sub_pool(p);
key = make_key(tmp_pool, vhost_id);
res = pr_redis_hash_keys(tmp_pool, redis, &proxy_module, key, &fields);
xerrno = errno;
if (res < 0) {
if (xerrno == ENOENT) {
/* Ignore. */
res = 0;
} else {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error obtaining fields from Redis hash '%s': %s", key,
strerror(errno));
}
destroy_pool(tmp_pool);
errno = xerrno;
return res;
}
pr_trace_msg(trace_channel, 17, "deleting %u %s for hash '%s'",
fields->nelts, fields->nelts != 1 ? "fields" : "field", key);
for (i = 0; i < fields->nelts; i++) {
char *field;
field = ((char **) fields->elts)[i];
pr_trace_msg(trace_channel, 17, "deleting field '%s' from Redis hash '%s'",
field, key);
res = pr_redis_hash_delete(redis, &proxy_module, key, field);
if (res < 0) {
pr_trace_msg(trace_channel, 4,
"error deleting field '%s' from Redis hash '%s': %s", field, key,
strerror(errno));
}
}
destroy_pool(tmp_pool);
return 0;
}
static int tls_redis_init(pool *p, const char *tables_path, int flags) {
int res, xerrno = 0;
server_rec *s;
pr_redis_t *redis = NULL;
(void) tables_path;
(void) flags;
redis = pr_redis_conn_new(p, &proxy_module, 0);
xerrno = errno;
if (redis == NULL) {
(void) pr_log_pri(PR_LOG_NOTICE, MOD_PROXY_VERSION
": error opening Redis connection: %s", strerror(xerrno));
errno = xerrno;
return -1;
}
(void) pr_redis_conn_set_namespace(redis, &proxy_module, redis_prefix,
redis_prefixsz);
for (s = (server_rec *) server_list->xas_list; s; s = s->next) {
res = tls_redis_truncate_tables(p, redis, s->sid);
if (res < 0) {
pr_trace_msg(trace_channel, 3,
"error truncating Redis keys for server '%s': %s", s->ServerName,
strerror(errno));
}
}
(void) pr_redis_conn_close(redis);
return 0;
}
static int tls_redis_close(pool *p, void *redis) {
if (redis != NULL) {
if (pr_redis_conn_close(redis) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error closing Redis connection: %s", strerror(errno));
}
}
return 0;
}
static void *tls_redis_open(pool *p, const char *tables_dir,
unsigned long opts) {
int xerrno = 0;
pr_redis_t *redis;
redis = pr_redis_conn_new(p, &proxy_module, 0);
xerrno = errno;
if (redis == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error opening Redis connection: %s", strerror(xerrno));
errno = xerrno;
return NULL;
}
(void) pr_redis_conn_set_namespace(redis, &proxy_module, redis_prefix,
redis_prefixsz);
redis_opts = opts;
return redis;
}
#endif /* PR_USE_OPENSSL */
int proxy_tls_redis_as_datastore(struct proxy_tls_datastore *ds, void *ds_data,
size_t ds_datasz) {
if (ds == NULL) {
errno = EINVAL;
return -1;
}
#ifdef PR_USE_OPENSSL
redis_prefix = ds_data;
redis_prefixsz = ds_datasz;
ds->add_sess = tls_redis_add_sess;
ds->remove_sess = tls_redis_remove_sess;
ds->get_sess = tls_redis_get_sess;
ds->count_sess = tls_redis_count_sess;
ds->init = tls_redis_init;
ds->open = tls_redis_open;
ds->close = tls_redis_close;
#endif /* PR_USE_OPENSSL */
return 0;
}
proftpd-mod_proxy-0.9.5/lib/proxy/uri.c 0000664 0000000 0000000 00000024044 14757370167 0020140 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy URI implementation
* Copyright (c) 2012-2020 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "mod_proxy.h"
#include "proxy/uri.h"
/* Relevant RFCs:
*
* RFC 1738: Uniform Resource Locators (obsolete)
* RFC 3986: Uniform Resource Identifier - Generic Syntax
*/
static const char *trace_channel = "proxy.uri";
static char *uri_parse_host(pool *p, const char *orig_uri,
const char *uri, char **remaining) {
char *host = NULL, *ptr = NULL;
/* We have either of:
*
* host<:...>
* [host]<:...>
*
* Look for an opening square bracket, to see if we have an IPv6 address
* in the URI.
*/
if (uri[0] == '[') {
ptr = strchr(uri + 1, ']');
if (ptr == NULL) {
/* If there is no ']', then it's a badly-formatted URI. */
pr_trace_msg(trace_channel, 4,
"badly formatted IPv6 address in host info '%.100s'", orig_uri);
errno = EINVAL;
return NULL;
}
host = pstrndup(p, uri + 1, ptr - uri - 1);
if (remaining != NULL) {
size_t urilen;
urilen = strlen(ptr);
if (urilen > 0) {
*remaining = ptr + 1;
} else {
*remaining = NULL;
}
}
pr_trace_msg(trace_channel, 17, "parsed host '%s' out of URI '%s'", host,
orig_uri);
return host;
}
ptr = strchr(uri + 1, ':');
if (ptr == NULL) {
if (remaining != NULL) {
*remaining = NULL;
}
host = pstrdup(p, uri);
pr_trace_msg(trace_channel, 17, "parsed host '%s' out of URI '%s'", host,
orig_uri);
return host;
}
if (remaining != NULL) {
*remaining = ptr;
}
host = pstrndup(p, uri, ptr - uri);
pr_trace_msg(trace_channel, 17, "parsed host '%s' out of URI '%s'", host,
orig_uri);
return host;
}
/* Determine whether "username:password@" are present. If so, then parse it
* out, and return a pointer to the portion of the URI after the parsed-out
* userinfo.
*/
static char *uri_parse_userinfo(pool *p, const char *orig_uri,
const char *uri, char **username, char **password) {
char *ptr, *ptr2, *rem_uri = NULL, *userinfo, *user = NULL, *passwd = NULL;
/* We have either:
*
* host<:...>
* [host]<:...>
*
* thus no user info, OR:
*
* username:password@host...
* username:password@[host]...
* username:@host...
* username:pass@word@host...
* user@domain.com:pass@word@host...
*
* all of which have at least one occurrence of the '@' character.
*/
ptr = strchr(uri, '@');
if (ptr == NULL) {
/* No '@' character at all? No user info, then. */
if (username != NULL) {
*username = NULL;
}
if (password != NULL) {
*password = NULL;
}
return pstrdup(p, uri);
}
/* To handle the case where the password field might itself contain an
* '@' character, we first search from the end for '@'. If found, then we
* search for '@' from the beginning. If also found, AND if both ocurrences
* are the same, then we have a plain "username:password@" string.
*
* Note that we can handle '@' characters within passwords (or usernames),
* but we currently cannot handle ':' characters within usernames.
*/
ptr2 = strrchr(uri, '@');
if (ptr2 != NULL) {
if (ptr != ptr2) {
/* Use the last found '@' as the delimiter. */
ptr = ptr2;
}
}
userinfo = pstrndup(p, uri, ptr - uri);
rem_uri = ptr + 1;
ptr = strchr(userinfo, ':');
if (ptr == NULL) {
pr_trace_msg(trace_channel, 4,
"badly formatted userinfo '%.100s' (missing ':' character) in "
"URI '%.100s', ignoring", userinfo, orig_uri);
if (username != NULL) {
*username = NULL;
}
if (password != NULL) {
*password = NULL;
}
return rem_uri;
}
user = pstrndup(p, userinfo, ptr - userinfo);
if (username != NULL) {
*username = user;
}
/* Watch for empty passwords. */
if (*(ptr+1) == '\0') {
passwd = pstrdup(p, "");
} else {
passwd = pstrdup(p, ptr + 1);
}
if (password != NULL) {
*password = passwd;
}
pr_trace_msg(trace_channel, 17,
"parsed username '%s', password '%s' out of URI '%s'", user, passwd,
orig_uri);
return rem_uri;
}
int proxy_uri_parse(pool *p, const char *uri, char **scheme, char **host,
unsigned int *port, char **username, char **password) {
char *ptr, *ptr2;
size_t idx, len;
if (uri == NULL ||
scheme == NULL ||
host == NULL ||
port == NULL) {
errno = EINVAL;
return -1;
}
/* First, look for a ':' */
ptr = strchr(uri, ':');
if (ptr == NULL) {
pr_trace_msg(trace_channel, 4, "missing colon in URI '%.100s'", uri);
errno = EINVAL;
return -1;
}
len = (ptr - uri);
*scheme = pstrndup(p, uri, len);
idx = strspn(*scheme, "abcdefghijklmnopqrstuvwxyz+.-");
if (idx < len &&
(*scheme)[idx] != '\0') {
/* Invalid character in the scheme string, according to RFC 1738 rules. */
pr_trace_msg(trace_channel, 4,
"invalid character (%c) at index %lu in scheme '%.100s'", (*scheme)[idx],
(unsigned long) idx, *scheme);
errno = EINVAL;
return -1;
}
/* The double-slashes must immediately follow the colon. */
if (*(ptr + 1) != '/' ||
*(ptr + 2) != '/') {
pr_trace_msg(trace_channel, 4,
"missing required '//' following colon in URI '%.100s'", uri);
errno = EINVAL;
return -1;
}
ptr += 3;
if (*ptr == '\0') {
/* The given URL looked like "scheme://". */
pr_trace_msg(trace_channel, 4,
"missing required authority following '//' in URI '%.100s'", uri);
errno = EINVAL;
return -1;
}
/* Possible URIs at this point:
*
* scheme://host:port/path/...
* scheme://host:port/
* scheme://host:port
* scheme://host
* scheme://username:password@host...
*
* And, in the case where 'host' is an IPv6 address:
*
* scheme://[host]:port/path/...
* scheme://[host]:port/
* scheme://[host]:port
* scheme://[host]
* scheme://username:password@[host]...
*/
/* We explicitly do NOT support URL-encoded characters in the URIs we
* will handle.
*/
ptr2 = strchr(ptr, '%');
if (ptr2 != NULL) {
pr_trace_msg(trace_channel, 4,
"invalid character (%%) at index %ld in scheme-specific info '%.100s'",
(long) (ptr2 - ptr), ptr);
errno = EINVAL;
return -1;
}
ptr = uri_parse_userinfo(p, uri, ptr, username, password);
ptr2 = strchr(ptr, ':');
if (ptr2 == NULL) {
*host = uri_parse_host(p, uri, ptr, NULL);
if (strcmp(*scheme, "ftp") == 0 ||
strcmp(*scheme, "ftps") == 0) {
*port = 21;
} else if (strcmp(*scheme, "sftp") == 0) {
*port = 22;
} else {
if (pr_strnrstr(*scheme, 0, "+srv", 0, PR_STR_FL_IGNORE_CASE) != TRUE &&
pr_strnrstr(*scheme, 0, "+txt", 0, PR_STR_FL_IGNORE_CASE) != TRUE) {
pr_trace_msg(trace_channel, 4,
"unable to determine port for scheme '%.100s'", *scheme);
errno = EINVAL;
return -1;
}
}
} else {
*host = uri_parse_host(p, uri, ptr, &ptr2);
}
/* Optional port field present? */
if (ptr2 != NULL) {
ptr2 = strchr(ptr2, ':');
}
if (ptr2 == NULL) {
/* XXX How to configure "implicit" FTPS, if at all? */
if (strcmp(*scheme, "ftp") == 0 ||
strcmp(*scheme, "ftps") == 0) {
*port = 21;
} else if (strcmp(*scheme, "sftp") == 0) {
*port = 22;
} else {
if (pr_strnrstr(*scheme, 0, "+srv", 0, PR_STR_FL_IGNORE_CASE) != TRUE &&
pr_strnrstr(*scheme, 0, "+txt", 0, PR_STR_FL_IGNORE_CASE) != TRUE) {
pr_trace_msg(trace_channel, 4,
"unable to determine port for scheme '%.100s'", *scheme);
errno = EINVAL;
return -1;
}
}
} else {
register unsigned int i;
char *ptr3, *portspec;
size_t portspeclen;
/* Look for any possible trailing '/'. */
ptr3 = strchr(ptr2, '/');
if (ptr3 == NULL) {
portspec = ptr2 + 1;
portspeclen = strlen(portspec);
} else {
portspeclen = ptr3 - (ptr2 + 1);
portspec = pstrndup(p, ptr2 + 1, portspeclen);
}
/* Ensure that only numeric characters appear in the portspec. */
for (i = 0; i < portspeclen; i++) {
if (isdigit((int) portspec[i]) == 0) {
pr_trace_msg(trace_channel, 4,
"invalid character (%c) at index %d in port specification '%.100s'",
portspec[i], i, portspec);
errno = EINVAL;
return -1;
}
}
/* The above check will rule out any negative numbers, since it will
* reject the minus character. Thus we only need to check for a zero
* port, or a number that's outside the 1-65535 range.
*/
*port = atoi(portspec);
if (*port == 0 ||
*port >= 65536) {
pr_trace_msg(trace_channel, 4,
"port specification '%.100s' yields invalid port number %d",
portspec, *port);
errno = EINVAL;
return -1;
}
}
/* We deliberately ignore any configured for SRV, TXT scheme variants.
* The ports to use will be obtained from the DNS records for such
* schemes.
*/
if (pr_strnrstr(*scheme, 0, "+srv", 0, PR_STR_FL_IGNORE_CASE) == TRUE ||
pr_strnrstr(*scheme, 0, "+txt", 0, PR_STR_FL_IGNORE_CASE) == TRUE) {
*port = 0;
}
return 0;
}
proftpd-mod_proxy-0.9.5/mod_proxy.c 0000664 0000000 0000000 00000527233 14757370167 0017442 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy
* Copyright (c) 2012-2025 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*
* -----DO NOT EDIT BELOW THIS LINE-----
* $Archive: mod_proxy.a $
* $Libraries: -lsqlite3 -lssl -lcrypto$
*/
#include "mod_proxy.h"
#include "proxy/random.h"
#include "proxy/db.h"
#include "proxy/session.h"
#include "proxy/conn.h"
#include "proxy/netio.h"
#include "proxy/inet.h"
#include "proxy/ssh.h"
#include "proxy/tls.h"
#include "proxy/forward.h"
#include "proxy/reverse.h"
#include "proxy/ftp/conn.h"
#include "proxy/ftp/ctrl.h"
#include "proxy/ftp/data.h"
#include "proxy/ftp/dirlist.h"
#include "proxy/ftp/facts.h"
#include "proxy/ftp/msg.h"
#include "proxy/ftp/sess.h"
#include "proxy/ftp/xfer.h"
#include "proxy/ssh/ssh2.h"
#include "proxy/ssh/auth.h"
#include "proxy/ssh/crypto.h"
#if defined(HAVE_OSSL_PROVIDER_LOAD_OPENSSL)
# include
#endif /* HAVE_OSSL_PROVIDER_LOAD_OPENSSL */
/* Proxy role */
#define PROXY_ROLE_REVERSE 1
#define PROXY_ROLE_FORWARD 2
/* How long (in secs) to wait to connect to real server? */
#define PROXY_CONNECT_DEFAULT_TIMEOUT 5
/* How long (in secs) to wait for the end-of-data-transfer response? */
#define PROXY_LINGER_DEFAULT_TIMEOUT 3
extern xaset_t *server_list;
extern module xfer_module;
/* From response.c */
extern pr_response_t *resp_list, *resp_err_list;
module proxy_module;
int proxy_logfd = -1;
pool *proxy_pool = NULL;
unsigned long proxy_opts = 0UL;
unsigned int proxy_sess_state = 0U;
int proxy_datastore = PROXY_DATASTORE_SQLITE;
void *proxy_datastore_data = NULL;
size_t proxy_datastore_datasz = 0;
static int proxy_engine = FALSE;
static unsigned int proxy_login_attempts = 0;
static int proxy_role = PROXY_ROLE_REVERSE;
static const char *proxy_tables_dir = NULL;
static int proxy_tls_xfer_prot_policy = PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_REQUIRED;
#if defined(HAVE_OSSL_PROVIDER_LOAD_OPENSSL)
static OSSL_PROVIDER *legacy_provider = NULL;
#endif /* HAVE_OSSL_PROVIDER_LOAD_OPENSSL */
static const char *trace_channel = "proxy";
/* Necessary function prototypes. */
static int proxy_sess_init(void);
static void proxy_timeoutidle_ev(const void *, void *);
static void proxy_timeoutnoxfer_ev(const void *, void *);
static void proxy_timeoutstalled_ev(const void *, void *);
static int recv_resp(cmd_rec *cmd, struct proxy_session *proxy_sess,
pr_response_t **rp) {
int res, xerrno = 0;
pr_response_t *resp;
unsigned int resp_nlines = 0;
resp = proxy_ftp_ctrl_recv_resp(cmd->tmp_pool, proxy_sess->backend_ctrl_conn,
&resp_nlines, 0);
if (resp == NULL) {
xerrno = errno;
/* For a certain number of conditions, if we cannot read the response
* from the backend, then we should just close the frontend, otherwise
* we might "leak" to the client the fact that we are fronting some
* backend server rather than being the server.
*/
if (xerrno == ECONNRESET ||
xerrno == ECONNABORTED ||
xerrno == ENOENT ||
xerrno == EPIPE) {
pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION,
"Backend control connection lost");
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error receiving %s response from backend: %s", (char *) cmd->argv[0],
strerror(xerrno));
pr_response_add_err(R_500, _("%s: %s"), (char *) cmd->argv[0],
strerror(xerrno));
pr_response_flush(&resp_err_list);
errno = xerrno;
return -1;
}
res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool, proxy_sess->frontend_ctrl_conn,
resp, resp_nlines);
if (res < 0) {
xerrno = errno;
pr_response_block(TRUE);
errno = xerrno;
return -1;
}
if (rp != NULL) {
*rp = resp;
}
return 0;
}
MODRET proxy_cmd(cmd_rec *cmd, struct proxy_session *proxy_sess,
pr_response_t **rp) {
int res, xerrno = 0;
res = proxy_ftp_ctrl_send_cmd(cmd->tmp_pool, proxy_sess->backend_ctrl_conn,
cmd);
xerrno = errno;
if (res < 0) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error sending %s to backend: %s", (char *) cmd->argv[0],
strerror(xerrno));
pr_response_add_err(R_500, _("%s: %s"), (char *) cmd->argv[0],
strerror(xerrno));
pr_response_flush(&resp_err_list);
errno = xerrno;
return PR_ERROR(cmd);
}
if (recv_resp(cmd, proxy_sess, rp) < 0) {
return PR_ERROR(cmd);
}
pr_response_block(TRUE);
return PR_HANDLED(cmd);
}
MODRET proxy_abort(cmd_rec *cmd, struct proxy_session *proxy_sess,
pr_response_t **rp) {
int res, xerrno = 0;
res = proxy_ftp_ctrl_send_abort(cmd->tmp_pool,
proxy_sess->backend_ctrl_conn, cmd);
xerrno = errno;
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error sending %s to backend: %s", (char *) cmd->argv[0],
strerror(xerrno));
pr_response_add_err(R_500, _("%s: %s"), (char *) cmd->argv[0],
strerror(xerrno));
pr_response_flush(&resp_err_list);
errno = xerrno;
return PR_ERROR(cmd);
}
if (proxy_sess->backend_data_conn != NULL) {
pr_trace_msg(trace_channel, 19, "received ABOR on frontend connection, "
"closing backend data connection");
proxy_inet_close(session.pool, proxy_sess->backend_data_conn);
pr_inet_close(session.pool, proxy_sess->backend_data_conn);
proxy_sess->backend_data_conn = NULL;
}
if (recv_resp(cmd, proxy_sess, rp) < 0) {
return PR_ERROR(cmd);
}
/* The ABOR command might have two responses, as when there is a data
* transfer in progress: one response for the data transfer, and one for
* handling the ABOR command itself.
*/
if ((proxy_sess->frontend_sess_flags & SF_XFER) ||
(proxy_sess->backend_sess_flags & SF_XFER)) {
if (recv_resp(cmd, proxy_sess, rp) < 0) {
return PR_ERROR(cmd);
}
}
pr_response_block(TRUE);
return PR_HANDLED(cmd);
}
MODRET proxy_data_cmd(cmd_rec *cmd, struct proxy_session *proxy_sess) {
int res, xerrno = 0;
modret_t *mr;
pr_response_t *resp = NULL;
unsigned int resp_nlines = 0;
mr = proxy_cmd(cmd, proxy_sess, &resp);
if (!MODRET_ISHANDLED(mr)) {
pr_response_block(TRUE);
return mr;
}
if (resp->num[0] != '1') {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"received non-1xx response from backend for %s: %s %s",
(char *) cmd->argv[0], resp->num, resp->msg);
pr_response_block(FALSE);
pr_response_add_err(resp->num, "%s", resp->msg);
pr_response_flush(&resp_err_list);
pr_response_block(TRUE);
errno = EINVAL;
return PR_ERROR(cmd);
}
/* Now we wait for our closing response. */
resp = proxy_ftp_ctrl_recv_resp(cmd->tmp_pool, proxy_sess->backend_ctrl_conn,
&resp_nlines, 0);
if (resp == NULL) {
xerrno = errno;
pr_response_block(FALSE);
/* For a certain number of conditions, if we cannot read the response
* from the backend, then we should just close the frontend, otherwise
* we might "leak" to the client the fact that we are fronting some
* backend server rather than being the server.
*/
if (xerrno == ECONNRESET ||
xerrno == ECONNABORTED ||
xerrno == ENOENT ||
xerrno == EPIPE) {
pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION,
"Backend control connection lost");
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error receiving %s response from backend: %s", (char *) cmd->argv[0],
strerror(xerrno));
pr_response_add_err(R_500, _("%s: %s"), (char *) cmd->argv[0],
strerror(xerrno));
pr_response_flush(&resp_err_list);
pr_response_block(TRUE);
errno = xerrno;
return PR_ERROR(cmd);
}
pr_response_block(FALSE);
res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool, proxy_sess->frontend_ctrl_conn,
resp, resp_nlines);
if (res < 0) {
xerrno = errno;
pr_response_block(TRUE);
errno = xerrno;
return PR_ERROR(cmd);
}
pr_response_block(TRUE);
return PR_HANDLED(cmd);
}
static int proxy_stalled_timeout_cb(CALLBACK_FRAME) {
int timeout_stalled;
timeout_stalled = pr_data_get_timeout(PR_DATA_TIMEOUT_STALLED);
pr_event_generate("core.timeout-stalled", NULL);
pr_log_pri(PR_LOG_NOTICE, "Data transfer stall timeout: %d %s",
timeout_stalled, timeout_stalled != 1 ? "seconds" : "second");
pr_session_disconnect(NULL, PR_SESS_DISCONNECT_TIMEOUT,
"TimeoutStalled during data transfer");
/* Do not restart the timer (should never be reached). */
return 0;
}
static int proxy_mkdir(const char *dir, uid_t uid, gid_t gid, mode_t mode) {
mode_t prev_mask;
struct stat st;
int res = -1;
res = stat(dir, &st);
if (res < 0 &&
errno != ENOENT) {
return -1;
}
/* The directory already exists. */
if (res == 0) {
return 0;
}
/* The given mode is absolute, not subject to any Umask setting. */
prev_mask = umask(0);
if (mkdir(dir, mode) < 0) {
int xerrno = errno;
(void) umask(prev_mask);
errno = xerrno;
return -1;
}
umask(prev_mask);
if (chown(dir, uid, gid) < 0) {
return -1;
}
return 0;
}
static int proxy_mkpath(pool *p, const char *path, uid_t uid, gid_t gid,
mode_t mode) {
char *currpath = NULL, *tmppath = NULL;
struct stat st;
if (stat(path, &st) == 0) {
/* Path already exists, nothing to be done. */
errno = EEXIST;
return -1;
}
tmppath = pstrdup(p, path);
currpath = "/";
while (tmppath && *tmppath) {
char *currdir = strsep(&tmppath, "/");
currpath = pdircat(p, currpath, currdir, NULL);
if (proxy_mkdir(currpath, uid, gid, mode) < 0) {
return -1;
}
pr_signals_handle();
}
return 0;
}
static void proxy_remove_symbols(void) {
int res;
/* Remove mod_xfer's PRE_CMD handlers; they will only interfere
* with proxied data transfers (see GitHub issue #3).
*/
res = pr_stash_remove_cmd(C_APPE, &xfer_module, PRE_CMD, NULL, -1);
if (res < 0) {
pr_trace_msg(trace_channel, 1,
"error removing PRE_CMD APPE mod_xfer handler: %s", strerror(errno));
} else {
pr_trace_msg(trace_channel, 9, "removed PRE_CMD APPE mod_xfer handlers");
}
res = pr_stash_remove_cmd(C_RETR, &xfer_module, PRE_CMD, NULL, -1);
if (res < 0) {
pr_trace_msg(trace_channel, 1,
"error removing PRE_CMD RETR mod_xfer handler: %s", strerror(errno));
} else {
pr_trace_msg(trace_channel, 9, "removed PRE_CMD RETR mod_xfer handlers");
}
res = pr_stash_remove_cmd(C_STOR, &xfer_module, PRE_CMD, NULL, -1);
if (res < 0) {
pr_trace_msg(trace_channel, 1,
"error removing PRE_CMD STOR mod_xfer handler: %s", strerror(errno));
} else {
pr_trace_msg(trace_channel, 9, "removed PRE_CMD STOR mod_xfer handlers");
}
res = pr_stash_remove_cmd(C_STOU, &xfer_module, PRE_CMD, NULL, -1);
if (res < 0) {
pr_trace_msg(trace_channel, 1,
"error removing PRE_CMD STOU mod_xfer handler: %s", strerror(errno));
} else {
pr_trace_msg(trace_channel, 9, "removed PRE_CMD STOU mod_xfer handlers");
}
}
static void proxy_restrict_session(void) {
const char *proxy_chroot = NULL;
config_rec *c;
char *xferlog = PR_XFERLOG_PATH;
/* Open the TransferLog here, BEFORE we chroot. */
c = find_config(main_server->conf, CONF_PARAM, "TransferLog", FALSE);
if (c != NULL) {
xferlog = c->argv[0];
}
PRIVS_ROOT
if (strcasecmp(xferlog, "none") == 0) {
xferlog_open(NULL);
} else {
xferlog_open(xferlog);
}
if (getuid() == PR_ROOT_UID) {
int res;
/* Chroot to the ProxyTables/empty/ directory before dropping root privs. */
proxy_chroot = pdircat(proxy_pool, proxy_tables_dir, "empty", NULL);
res = chroot(proxy_chroot);
if (res < 0) {
int xerrno = errno;
PRIVS_RELINQUISH
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to chroot to ProxyTables/empty/ directory '%s': %s",
proxy_chroot, strerror(xerrno));
pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_MODULE_ACL,
"Unable to chroot proxy session");
} else {
pr_trace_msg(trace_channel, 9, "chrooted session to '%s'", proxy_chroot);
}
if (chdir("/") < 0) {
int xerrno = errno;
PRIVS_RELINQUISH
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to chdir to root directory within chroot: %s",
strerror(xerrno));
pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_MODULE_ACL,
"Unable to chroot proxy session");
}
}
/* Make the proxy session process have the identity of the configured daemon
* User/Group.
*/
PRIVS_REVOKE
session.disable_id_switching = TRUE;
if (proxy_chroot != NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"proxy session running as UID %lu, GID %lu, restricted to '%s'",
(unsigned long) getuid(), (unsigned long) getgid(), proxy_chroot);
} else {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"proxy session running as UID %lu, GID %lu, located in '%s'",
(unsigned long) getuid(), (unsigned long) getgid(), getcwd(NULL, 0));
}
}
/* Configuration handlers
*/
/* usage: ProxyDataTransferPolicy "active"|"passive"|"pasv"|"epsv"|"port"|
* "eprt"|"client"
*/
MODRET set_proxydataxferpolicy(cmd_rec *cmd) {
config_rec *c;
int cmd_id = -1;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
cmd_id = pr_cmd_get_id(cmd->argv[1]);
if (cmd_id < 0) {
if (strncasecmp(cmd->argv[1], "active", 7) == 0) {
/* Try to use EPRT over PORT. */
cmd_id = PR_CMD_EPRT_ID;
} else if (strncasecmp(cmd->argv[1], "passive", 8) == 0) {
/* Try to use EPSV over PASV. */
cmd_id = PR_CMD_EPSV_ID;
} else if (strncasecmp(cmd->argv[1], "client", 7) == 0) {
cmd_id = 0;
} else {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unsupported DataTransferPolicy: ",
(char *) cmd->argv[1], NULL));
}
}
if (cmd_id != PR_CMD_PASV_ID &&
cmd_id != PR_CMD_EPSV_ID &&
cmd_id != PR_CMD_PORT_ID &&
cmd_id != PR_CMD_EPRT_ID &&
cmd_id != 0) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unsupported DataTransferPolicy: ",
(char *) cmd->argv[1], NULL));
}
c = add_config_param(cmd->argv[0], 1, NULL);
c->argv[0] = palloc(c->pool, sizeof(int));
*((int *) c->argv[0]) = cmd_id;
return PR_HANDLED(cmd);
}
/* usage: ProxyDatastore type [info] */
MODRET set_proxydatastore(cmd_rec *cmd) {
int ds = -1;
const char *ds_name = NULL;
void *ds_data = NULL;
size_t ds_datasz = 0;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT);
ds_name = cmd->argv[1];
if (strcasecmp(ds_name, "sqlite") == 0) {
ds = PROXY_DATASTORE_SQLITE;
ds_data = NULL;
ds_datasz = 0;
#ifdef PR_USE_REDIS
} else if (strcasecmp(ds_name, "redis") == 0) {
if (cmd->argc != 3) {
CONF_ERROR(cmd, "missing required Redis key prefix");
}
ds = PROXY_DATASTORE_REDIS;
ds_data = pstrdup(proxy_pool, cmd->argv[2]);
ds_datasz = strlen(ds_data);
#endif /* PR_USE_REDIS */
} else {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unsupported ProxyDatastore: ",
ds_name, NULL));
}
proxy_datastore = ds;
proxy_datastore_data = ds_data;
proxy_datastore_datasz = ds_datasz;
return PR_HANDLED(cmd);
}
/* usage: ProxyDirectoryListPolicy "client"|"LIST" [opt1 ... ]*/
MODRET set_proxydirlistpolicy(cmd_rec *cmd) {
config_rec *c;
int policy_id = -1;
unsigned long opts = 0UL;
if (cmd->argc < 2) {
CONF_ERROR(cmd, "wrong number of parameters");
}
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
if (strcasecmp(cmd->argv[1], "client") == 0) {
policy_id = PROXY_SESS_DIRECTORY_LIST_POLICY_DEFAULT;
} else if (strcasecmp(cmd->argv[1], "LIST") == 0) {
policy_id = PROXY_SESS_DIRECTORY_LIST_POLICY_LIST;
} else {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unsupported DirectoryListPolicy: ",
(char *) cmd->argv[1], NULL));
}
if (cmd->argc > 2) {
register unsigned int i;
for (i = 2; i < cmd->argc; i++) {
if (strcasecmp(cmd->argv[i], "UseSlink") == 0) {
opts |= PROXY_FTP_DIRLIST_OPT_USE_SLINK;
} else {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
"unknown DirectoryListPolicy option: ", (char *) cmd->argv[i], NULL));
}
}
}
c = add_config_param(cmd->argv[0], 2, NULL, NULL);
c->argv[0] = palloc(c->pool, sizeof(int));
*((int *) c->argv[0]) = policy_id;
c->argv[1] = palloc(c->pool, sizeof(unsigned long));
*((unsigned long *) c->argv[1]) = opts;
return PR_HANDLED(cmd);
}
/* usage: ProxyEngine on|off */
MODRET set_proxyengine(cmd_rec *cmd) {
int engine = 1;
config_rec *c;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
engine = get_boolean(cmd, 1);
if (engine == -1) {
CONF_ERROR(cmd, "expected Boolean parameter");
}
c = add_config_param(cmd->argv[0], 1, NULL);
c->argv[0] = pcalloc(c->pool, sizeof(int));
*((int *) c->argv[0]) = engine;
return PR_HANDLED(cmd);
}
/* usage: ProxyForwardEnabled on|off */
MODRET set_proxyforwardenabled(cmd_rec *cmd) {
int enabled = -1, *note = NULL, res;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_CLASS);
enabled = get_boolean(cmd, 1);
if (enabled < 0) {
CONF_ERROR(cmd, "expected Boolean parameter");
}
/* Stash this setting in the notes for this class. */
note = palloc(cmd->server->pool, sizeof(int));
*note = enabled;
res = pr_class_add_note(PROXY_FORWARD_ENABLED_NOTE, note, sizeof(int));
if (res < 0) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error storing parameter: ",
strerror(errno), NULL));
}
return PR_HANDLED(cmd);
}
/* usage: ProxyForwardMethod method */
MODRET set_proxyforwardmethod(cmd_rec *cmd) {
config_rec *c;
int forward_method = -1;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
forward_method = proxy_forward_get_method(cmd->argv[1]);
if (forward_method < 0) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
"unknown/unsupported forward method: ", (char *) cmd->argv[1], NULL));
}
c = add_config_param(cmd->argv[0], 1, NULL);
c->argv[0] = palloc(c->pool, sizeof(int));
*((int *) c->argv[0]) = forward_method;
return PR_HANDLED(cmd);
}
/* usage: ProxyForwardTo [!]pattern [flags] */
MODRET set_proxyforwardto(cmd_rec *cmd) {
#ifdef PR_USE_REGEX
config_rec *c;
pr_regex_t *pre = NULL;
int negated = FALSE, regex_flags = REG_EXTENDED|REG_NOSUB, res = 0;
char *pattern;
if (cmd->argc-1 < 1 ||
cmd->argc-1 > 2) {
CONF_ERROR(cmd, "bad number of parameters");
}
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
/* Make sure that, if present, the flags parameter is correctly formatted. */
if (cmd->argc-1 == 2) {
int flags = 0;
/* We need to parse the flags parameter here, to see if any flags which
* affect the compilation of the regex (e.g. NC) are present.
*/
flags = pr_filter_parse_flags(cmd->tmp_pool, cmd->argv[2]);
if (flags < 0) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
": badly formatted flags parameter: '", (char *) cmd->argv[2], "'",
NULL));
}
if (flags == 0) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
": unknown filter flags '", (char *) cmd->argv[2], "'", NULL));
}
regex_flags |= flags;
}
pre = pr_regexp_alloc(&proxy_module);
pattern = cmd->argv[1];
if (*pattern == '!') {
negated = TRUE;
pattern++;
}
res = pr_regexp_compile(pre, pattern, regex_flags);
if (res != 0) {
char errstr[200] = {'\0'};
pr_regexp_error(res, pre, errstr, sizeof(errstr));
pr_regexp_free(NULL, pre);
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "'", pattern, "' failed regex "
"compilation: ", errstr, NULL));
}
c = add_config_param(cmd->argv[0], 2, pre, NULL);
c->argv[1] = palloc(c->pool, sizeof(int));
*((int *) c->argv[1]) = negated;
return PR_HANDLED(cmd);
#else /* no regular expression support at the moment */
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "The ", param, " directive cannot be "
"used on this system, as you do not have POSIX compliant regex support",
NULL));
#endif
}
/* usage: ProxyLog path|"none" */
MODRET set_proxylog(cmd_rec *cmd) {
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
(void) add_config_param_str(cmd->argv[0], 1, cmd->argv[1]);
return PR_HANDLED(cmd);
}
/* usage: ProxyOptions opt1 ... optN */
MODRET set_proxyoptions(cmd_rec *cmd) {
register unsigned int i;
config_rec *c;
unsigned long opts = 0UL;
if (cmd->argc-1 == 0) {
CONF_ERROR(cmd, "wrong number of parameters");
}
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
c = add_config_param(cmd->argv[0], 1, NULL);
for (i = 1; i < cmd->argc; i++) {
if (strcmp(cmd->argv[i], "UseProxyProtocol") == 0 ||
strcmp(cmd->argv[i], "UseProxyProtocolV1") == 0) {
opts |= PROXY_OPT_USE_PROXY_PROTOCOL_V1;
} else if (strcmp(cmd->argv[i], "UseProxyProtocolV2") == 0) {
opts |= PROXY_OPT_USE_PROXY_PROTOCOL_V2;
} else if (strcmp(cmd->argv[i], "UseProxyProtocolV2TLVs") == 0) {
opts |= PROXY_OPT_USE_PROXY_PROTOCOL_V2_TLVS;
} else if (strcmp(cmd->argv[i], "ShowFeatures") == 0) {
opts |= PROXY_OPT_SHOW_FEATURES;
} else if (strcmp(cmd->argv[i], "UseReverseProxyAuth") == 0) {
opts |= PROXY_OPT_USE_REVERSE_PROXY_AUTH;
} else if (strcmp(cmd->argv[i], "UseDirectDataTransfers") == 0) {
opts |= PROXY_OPT_USE_DIRECT_DATA_TRANSFERS;
} else if (strcmp(cmd->argv[i], "IgnoreConfigPerms") == 0) {
opts |= PROXY_OPT_IGNORE_CONFIG_PERMS;
} else if (strcmp(cmd->argv[i], "AllowForeignAddress") == 0) {
opts |= PROXY_OPT_ALLOW_FOREIGN_ADDRESS;
} else if (strcmp(cmd->argv[i], "IgnoreForeignAddress") == 0) {
opts |= PROXY_OPT_IGNORE_FOREIGN_ADDRESS;
} else {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown ProxyOption '",
(char *) cmd->argv[i], "'", NULL));
}
}
c->argv[0] = pcalloc(c->pool, sizeof(unsigned long));
*((unsigned long *) c->argv[0]) = opts;
if (pr_module_exists("mod_ifsession.c")) {
/* These are needed in case this directive is used with mod_ifsession
* configuration.
*/
c->flags |= CF_MULTI;
}
return PR_HANDLED(cmd);
}
/* usage: ProxyRetryCount count */
MODRET set_proxyretrycount(cmd_rec *cmd) {
config_rec *c;
int retry_count = -1;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
retry_count = atoi(cmd->argv[1]);
if (retry_count < 1) {
CONF_ERROR(cmd, "retry count must be one or more");
}
c = add_config_param(cmd->argv[0], 1, NULL);
c->argv[0] = palloc(c->pool, sizeof(int));
*((int *) c->argv[0]) = retry_count;
return PR_HANDLED(cmd);
}
/* usage: ProxyReverseConnectPolicy [policy] */
MODRET set_proxyreverseconnectpolicy(cmd_rec *cmd) {
config_rec *c;
int connect_policy = -1;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
connect_policy = proxy_reverse_connect_get_policy_id(cmd->argv[1]);
if (connect_policy < 0) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
"unknown/unsupported connect policy: ", (char *) cmd->argv[1], NULL));
}
c = add_config_param(cmd->argv[0], 1, NULL);
c->argv[0] = palloc(c->pool, sizeof(int));
*((int *) c->argv[0]) = connect_policy;
return PR_HANDLED(cmd);
}
/* usage: ProxyReverseServers server1 ... server N
* file:/path/to/server/list.txt
* sql:/SQLNamedQuery
*/
MODRET set_proxyreverseservers(cmd_rec *cmd) {
config_rec *c;
array_header *backend_servers;
char *uri = NULL;
unsigned int flags = PROXY_CONN_CREATE_FL_USE_DNS_TTL;
if (cmd->argc-1 < 1) {
CONF_ERROR(cmd, "wrong number of parameters");
}
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
c = add_config_param(cmd->argv[0], 2, NULL, NULL);
backend_servers = make_array(c->pool, 1, sizeof(struct proxy_conn *));
if (cmd->argc-1 == 1) {
/* We are dealing with one of the following possibilities:
*
* file:/path/to/file.txt
* sql://SQLNamedQuery/...
*
*/
if (strncmp(cmd->argv[1], "file:", 5) == 0) {
char *param, *path;
param = cmd->argv[1];
path = param + 5;
/* If the path contains the %U or %g variables, then defer loading of
* this file until the USER name is known.
*/
if (strstr(path, "%U") == NULL &&
strstr(path, "%g") == NULL) {
int xerrno;
/* For now, load the list of servers at sess init time. In
* the future, we will want to load it at postparse time, mapped
* to the appropriate server_rec, and clear/reload on 'core.restart'.
*/
PRIVS_ROOT
backend_servers = proxy_reverse_json_parse_uris(cmd->server->pool,
path, flags);
xerrno = errno;
PRIVS_RELINQUISH
if (backend_servers == NULL) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
"error reading ProxyReverseServers file '", path, "': ",
strerror(xerrno), NULL));
}
if (backend_servers->nelts == 0) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
"no usable URLs found in file '", path, "'", NULL));
}
} else {
/* Only provide a URI for dynamic lookup, e.g. per-user/group/etc. */
uri = cmd->argv[1];
}
} else if (strncmp(cmd->argv[1], "sql:/", 5) == 0) {
/* Unfortunately there's not very much we can do to validate these
* SQL URIs at the moment. They point to a SQLNamedQuery, which
* may not have been parsed yet from the config file, or which may be
* in a scope. Thus we simply store them for now, and
* let the lookup routines do the necessary validation.
*/
uri = cmd->argv[1];
} else {
/* Treat it as a server-spec (i.e. a URI) */
const struct proxy_conn *pconn;
pconn = proxy_conn_create(c->pool, cmd->argv[1], flags);
if (pconn == NULL) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error parsing '",
(char *) cmd->argv[1], "': ", strerror(errno), NULL));
}
*((const struct proxy_conn **) push_array(backend_servers)) = pconn;
}
} else {
register unsigned int i;
/* More than one parameter, which means they are all URIs. */
for (i = 1; i < cmd->argc; i++) {
const struct proxy_conn *pconn;
pconn = proxy_conn_create(c->pool, cmd->argv[i], flags);
if (pconn == NULL) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error parsing '",
(char *) cmd->argv[i], "': ", strerror(errno), NULL));
}
*((const struct proxy_conn **) push_array(backend_servers)) = pconn;
}
}
c->argv[0] = backend_servers;
if (uri != NULL) {
c->argv[1] = pstrdup(c->pool, uri);
}
if (pr_module_exists("mod_ifsession.c")) {
/* These are needed in case this directive is used with mod_ifsession
* configuration.
*/
c->flags |= CF_MULTI;
}
return PR_HANDLED(cmd);
}
/* usage: ProxyRole "forward"|"reverse" */
MODRET set_proxyrole(cmd_rec *cmd) {
int role = 0;
config_rec *c;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
if (strcasecmp(cmd->argv[1], "forward") == 0) {
role = PROXY_ROLE_FORWARD;
} else if (strcasecmp(cmd->argv[1], "reverse") == 0) {
role = PROXY_ROLE_REVERSE;
} else {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unknown proxy role '",
(char *) cmd->argv[1], "'", NULL));
}
c = add_config_param(cmd->argv[0], 1, NULL);
c->argv[0] = palloc(c->pool, sizeof(int));
*((int *) c->argv[0]) = role;
return PR_HANDLED(cmd);
}
/* usage: ProxySFTPCiphers algos */
MODRET set_proxysftpciphers(cmd_rec *cmd) {
#if defined(PR_USE_OPENSSL)
register unsigned int i;
config_rec *c;
if (cmd->argc < 2) {
CONF_ERROR(cmd, "Wrong number of parameters");
}
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
for (i = 1; i < cmd->argc; i++) {
if (proxy_ssh_crypto_get_cipher(cmd->argv[i], NULL, NULL, NULL) == NULL) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
"unsupported cipher algorithm: ", (char *) cmd->argv[i], NULL));
}
}
c = add_config_param(cmd->argv[0], cmd->argc-1, NULL);
for (i = 1; i < cmd->argc; i++) {
c->argv[i-1] = pstrdup(c->pool, cmd->argv[i]);
}
return PR_HANDLED(cmd);
#else
CONF_ERROR(cmd,
"Use of the ProxySFTPCiphers directive requires OpenSSL support (--enable-openssl)");
#endif /* PR_USE_OPENSSL */
}
/* usage: ProxySFTPCompression on|off|delayed */
MODRET set_proxysftpcompression(cmd_rec *cmd) {
config_rec *c;
int comp;
if (cmd->argc != 2) {
CONF_ERROR(cmd, "Wrong number of parameters");
}
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
#if defined(HAVE_ZLIB_H)
comp = get_boolean(cmd, 1);
if (comp == -1) {
if (strcasecmp(cmd->argv[1], "delayed") != 0) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
"unknown compression setting: ", cmd->argv[1], NULL));
}
comp = 2;
}
#else
pr_log_debug(DEBUG0, MOD_PROXY_VERSION
": platform lacks zlib support, ignoring ProxySFTPCompression");
comp = 0;
#endif /* !HAVE_ZLIB_H */
c = add_config_param(cmd->argv[0], 1, NULL);
c->argv[0] = pcalloc(c->pool, sizeof(int));
*((int *) c->argv[0]) = comp;
return PR_HANDLED(cmd);
}
/* usage: ProxySFTPDigests algos */
MODRET set_proxysftpdigests(cmd_rec *cmd) {
#if defined(PR_USE_OPENSSL)
register unsigned int i;
config_rec *c;
if (cmd->argc < 2) {
CONF_ERROR(cmd, "Wrong number of parameters");
}
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
for (i = 1; i < cmd->argc; i++) {
if (proxy_ssh_crypto_get_digest(cmd->argv[i], NULL) == NULL) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
"unsupported digest algorithm: ", cmd->argv[i], NULL));
}
}
c = add_config_param(cmd->argv[0], cmd->argc-1, NULL);
for (i = 1; i < cmd->argc; i++) {
c->argv[i-1] = pstrdup(c->pool, cmd->argv[i]);
}
return PR_HANDLED(cmd);
#else
CONF_ERROR(cmd,
"Use of the ProxySFTPDigests directive requires OpenSSL support (--enable-openssl)");
#endif /* PR_USE_OPENSSL */
}
/* usage: ProxySFTPHostKey path|"agent:/..." */
MODRET set_proxysftphostkey(cmd_rec *cmd) {
struct stat st;
int flags = 0;
config_rec *c;
const char *path = NULL;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
path = cmd->argv[1];
if (strncmp(cmd->argv[1], "agent:", 6) != 0 &&
flags == 0) {
int res, xerrno;
path = cmd->argv[1];
if (*path != '/') {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "must be an absolute path: ",
path, NULL));
}
PRIVS_ROOT
res = stat(path, &st);
xerrno = errno;
PRIVS_RELINQUISH
if (res < 0) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable to check '", path,
"': ", strerror(xerrno), NULL));
}
if ((st.st_mode & S_IRWXG) ||
(st.st_mode & S_IRWXO)) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable to use '", path,
"' as host key, as it is group- or world-accessible", NULL));
}
}
c = add_config_param_str(cmd->argv[0], 2, NULL, NULL);
c->argv[0] = pstrdup(c->pool, path);
c->argv[1] = palloc(c->pool, sizeof(int));
*((int *) c->argv[1]) = flags;
return PR_HANDLED(cmd);
}
/* usage: ProxySFTPKeyExchanges algos */
MODRET set_proxysftpkeyexchanges(cmd_rec *cmd) {
#if defined(PR_USE_OPENSSL)
register unsigned int i;
config_rec *c;
char *exchanges = "";
if (cmd->argc < 2) {
CONF_ERROR(cmd, "Wrong number of parameters");
}
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
for (i = 1; i < cmd->argc; i++) {
if (strcmp(cmd->argv[i], "diffie-hellman-group1-sha1") != 0 &&
strcmp(cmd->argv[i], "diffie-hellman-group14-sha1") != 0 &&
#if (OPENSSL_VERSION_NUMBER > 0x000907000L && defined(OPENSSL_FIPS)) || \
(OPENSSL_VERSION_NUMBER > 0x000908000L)
strcmp(cmd->argv[i], "diffie-hellman-group14-sha256") != 0 &&
strcmp(cmd->argv[i], "diffie-hellman-group16-sha512") != 0 &&
strcmp(cmd->argv[i], "diffie-hellman-group18-sha512") != 0 &&
strcmp(cmd->argv[i], "diffie-hellman-group-exchange-sha256") != 0 &&
#endif
strcmp(cmd->argv[i], "diffie-hellman-group-exchange-sha1") != 0 &&
#if defined(PR_USE_OPENSSL_ECC)
strcmp(cmd->argv[i], "ecdh-sha2-nistp256") != 0 &&
strcmp(cmd->argv[i], "ecdh-sha2-nistp384") != 0 &&
strcmp(cmd->argv[i], "ecdh-sha2-nistp521") != 0 &&
#endif /* PR_USE_OPENSSL_ECC */
#if defined(HAVE_SODIUM_H) && defined(HAVE_SHA256_OPENSSL)
strcmp(cmd->argv[i], "curve25519-sha256") != 0 &&
strcmp(cmd->argv[i], "curve25519-sha256@libssh.org") != 0 &&
#endif /* HAVE_SODIUM_H and HAVE_SHA256_OPENSSL */
#if defined(HAVE_X448_OPENSSL) && defined(HAVE_SHA512_OPENSSL)
strcmp(cmd->argv[i], "curve448-sha512") != 0 &&
#endif /* HAVE_X448_OPENSSL and HAVE_SHA512_OPENSSL */
strcmp(cmd->argv[i], "rsa1024-sha1") != 0) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
"unsupported key exchange algorithm: ", cmd->argv[i], NULL));
}
}
c = add_config_param(cmd->argv[0], 1, NULL);
for (i = 1; i < cmd->argc; i++) {
exchanges = pstrcat(c->pool, exchanges, *exchanges ? "," : "", cmd->argv[i],
NULL);
}
c->argv[0] = exchanges;
return PR_HANDLED(cmd);
#else
CONF_ERROR(cmd,
"Use of the ProxySFTPKeyExchanges directive requires OpenSSL support (--enable-openssl)");
#endif /* PR_USE_OPENSSL */
}
/* usage: ProxySFTPOptions opts */
MODRET set_proxysftpoptions(cmd_rec *cmd) {
register unsigned int i;
config_rec *c;
unsigned long opts = 0UL;
if (cmd->argc-1 == 0) {
CONF_ERROR(cmd, "wrong number of parameters");
}
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
c = add_config_param(cmd->argv[0], 1, NULL);
for (i = 1; i < cmd->argc; i++) {
if (strcmp(cmd->argv[i], "OldProtocolCompat") == 0) {
opts |= PROXY_OPT_SSH_OLD_PROTO_COMPAT;
/* This option also automatically enables PessimisticKexint,
* as per the comments in RFC4253, Section 5.1.
*/
opts |= PROXY_OPT_SSH_PESSIMISTIC_KEXINIT;
} else if (strcmp(cmd->argv[i], "PessimisticKexinit") == 0) {
opts |= PROXY_OPT_SSH_PESSIMISTIC_KEXINIT;
} else if (strcmp(cmd->argv[i], "AllowWeakDH") == 0) {
opts |= PROXY_OPT_SSH_ALLOW_WEAK_DH;
} else if (strcmp(cmd->argv[i], "NoExtensionNegotiation") == 0) {
opts |= PROXY_OPT_SSH_NO_EXT_INFO;
} else if (strcmp(cmd->argv[i], "NoHostkeyRotation") == 0) {
opts |= PROXY_OPT_SSH_NO_HOSTKEY_ROTATION;
} else if (strcmp(cmd->argv[i], "NoStrictKex") == 0) {
opts |= PROXY_OPT_SSH_NO_STRICT_KEX;
} else {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown ProxySFTPOption '",
cmd->argv[i], "'", NULL));
}
}
c->argv[0] = pcalloc(c->pool, sizeof(unsigned long));
*((unsigned long *) c->argv[0]) = opts;
if (pr_module_exists("mod_ifsession.c")) {
/* These are needed in case this directive is used with mod_ifsession
* configuration.
*/
c->flags |= CF_MULTI;
}
return PR_HANDLED(cmd);
}
/* usage: ProxySFTPPassPhraseProvider path */
MODRET set_proxysftppassphraseprovider(cmd_rec *cmd) {
struct stat st;
char *path;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT);
path = cmd->argv[1];
if (*path != '/') {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "must be a full path: '", path, "'",
NULL));
}
if (stat(path, &st) < 0) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error checking '", path, "': ",
strerror(errno), NULL));
}
if (!S_ISREG(st.st_mode)) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable to use '", path,
": Not a regular file", NULL));
}
add_config_param_str(cmd->argv[0], 1, path);
return PR_HANDLED(cmd);
}
/* usage: ProxySFTPServerAlive count interval */
MODRET set_proxysftpserveralive(cmd_rec *cmd) {
int count, interval;
config_rec *c;
CHECK_ARGS(cmd, 2);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
count = atoi(cmd->argv[1]);
if (count < 0) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "max count '",
(char *) cmd->argv[1], "' must be equal to or greater than zero", NULL));
}
interval = atoi(cmd->argv[2]);
if (interval < 0) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "interval '", (char *) cmd->argv[2],
"' must be equal to or greater than zero", NULL));
}
c = add_config_param(cmd->argv[0], 2, NULL, NULL);
c->argv[0] = palloc(c->pool, sizeof(unsigned int));
*((unsigned int *) c->argv[0]) = count;
c->argv[1] = palloc(c->pool, sizeof(unsigned int));
*((unsigned int *) c->argv[1]) = interval;
return PR_HANDLED(cmd);
}
/* usage: ProxySFTPServerMatch pattern key1 val1 ... */
MODRET set_proxysftpservermatch(cmd_rec *cmd) {
#if defined(PR_USE_REGEX)
register unsigned int i;
config_rec *c;
pr_table_t *tab;
pr_regex_t *pre;
int res;
if (cmd->argc < 4) {
CONF_ERROR(cmd, "Wrong number of parameters");
} else {
int npairs;
/* Make sure we have an even number of args for the key/value pairs. */
npairs = cmd->argc - 2;
if (npairs % 2 != 0) {
CONF_ERROR(cmd, "Wrong number of parameters");
}
}
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
pre = pr_regexp_alloc(&proxy_module);
res = pr_regexp_compile(pre, cmd->argv[1], REG_EXTENDED|REG_NOSUB);
if (res != 0) {
char errstr[200];
memset(errstr, '\0', sizeof(errstr));
pr_regexp_error(res, pre, errstr, sizeof(errstr));
pr_regexp_free(NULL, pre);
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "'", (char *) cmd->argv[1],
"' failed regex compilation: ", errstr, NULL));
}
c = add_config_param(cmd->argv[0], 3, NULL, NULL, NULL);
c->argv[0] = pstrdup(c->pool, cmd->argv[1]);
c->argv[1] = pre;
tab = pr_table_alloc(c->pool, 0);
c->argv[2] = tab;
for (i = 2; i < cmd->argc; i++) {
if (strcmp(cmd->argv[i], "pessimisticNewkeys") == 0) {
int pessimistic_newkeys;
void *value;
pessimistic_newkeys = get_boolean(cmd, i+1);
if (pessimistic_newkeys == -1) {
CONF_ERROR(cmd, "expected Boolean parameter");
}
value = palloc(c->pool, sizeof(int));
*((int *) value) = pessimistic_newkeys;
if (pr_table_add(tab, pstrdup(c->pool, "pessimisticNewkeys"), value,
sizeof(int)) < 0) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
"error storing 'pessimisticNewkeys' value: ", strerror(errno), NULL));
}
/* Don't forget to advance i past the value. */
i++;
} else {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
": unknown ProxySFTPServerMatch key: '", cmd->argv[i], "'", NULL));
}
}
return PR_HANDLED(cmd);
#else /* no regular expression support at the moment */
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "The ", cmd->argv[0],
" directive cannot be used on this system, as you do not have POSIX "
"compliant regex support", NULL));
#endif
}
/* usage: ProxySFTPVerifyServer on|off */
MODRET set_proxysftpverifyserver(cmd_rec *cmd) {
int setting = -1;
config_rec *c = NULL;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
setting = get_boolean(cmd, 1);
if (setting == -1) {
CONF_ERROR(cmd, "expected Boolean parameter");
}
c = add_config_param(cmd->argv[0], 1, NULL);
c->argv[0] = pcalloc(c->pool, sizeof(int));
*((int *) c->argv[0]) = setting;
return PR_HANDLED(cmd);
}
/* usage: ProxySourceAddress address */
MODRET set_proxysourceaddress(cmd_rec *cmd) {
config_rec *c = NULL;
const pr_netaddr_t *src_addr = NULL;
unsigned int addr_flags = PR_NETADDR_GET_ADDR_FL_INCL_DEVICE;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
src_addr = pr_netaddr_get_addr2(cmd->server->pool, cmd->argv[1], NULL,
addr_flags);
if (src_addr == NULL) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable to resolve '",
(char *) cmd->argv[1], "'", NULL));
}
c = add_config_param(cmd->argv[0], 1, NULL);
c->argv[0] = (void *) src_addr;
return PR_HANDLED(cmd);
}
/* Helper function to extract the just the mode/perms from st.st_mode. */
static mode_t extract_mode(struct stat *st) {
mode_t mode;
mode = st->st_mode;
mode &= ~S_IFMT;
#ifdef S_IFJOURNAL
/* AIX uses this non-standard bit, which can cause issues. */
mode &= ~S_IFJOURNAL;
#endif /* S_IFJOURNAL */
return mode;
}
/* usage: ProxyTables path */
MODRET set_proxytables(cmd_rec *cmd) {
int res;
struct stat st;
char *path;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT);
path = cmd->argv[1];
if (*path != '/') {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "must be a full path: '", path,
"'", NULL));
}
res = stat(path, &st);
if (res < 0) {
char *proxy_chroot;
if (errno != ENOENT) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable to stat '", path,
"': ", strerror(errno), NULL));
}
pr_log_debug(DEBUG0, MOD_PROXY_VERSION
": ProxyTables directory '%s' does not exist, creating it", path);
/* Create the directory. */
res = proxy_mkpath(cmd->tmp_pool, path, geteuid(), getegid(), 0755);
if (res < 0) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable to create directory '",
path, "': ", strerror(errno), NULL));
}
/* Also create the empty/ directory underneath, for the chroot. */
proxy_chroot = pdircat(cmd->tmp_pool, path, "empty", NULL);
res = proxy_mkpath(cmd->tmp_pool, proxy_chroot, geteuid(), getegid(), 0111);
if (res < 0) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable to create directory '",
proxy_chroot, "': ", strerror(errno), NULL));
}
pr_log_debug(DEBUG2, MOD_PROXY_VERSION
": created ProxyTables directory '%s'", path);
} else {
char *proxy_chroot;
if (!S_ISDIR(st.st_mode)) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable to use '", path,
"': Not a directory", NULL));
}
/* See if the chroot directory empty/ already exists as well. And enforce
* the permissions on that directory.
*/
proxy_chroot = pdircat(cmd->tmp_pool, path, "empty", NULL);
res = stat(proxy_chroot, &st);
if (res < 0) {
if (errno != ENOENT) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable to stat '", proxy_chroot,
"': ", strerror(errno), NULL));
}
res = proxy_mkpath(cmd->tmp_pool, proxy_chroot, geteuid(), getegid(),
0111);
if (res < 0) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable to create directory '",
proxy_chroot, "': ", strerror(errno), NULL));
}
} else {
mode_t dir_mode, expected_mode;
if (!S_ISDIR(st.st_mode)) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "path '", proxy_chroot,
"' is not a directory as expected", NULL));
}
dir_mode = extract_mode(&st);
expected_mode = (S_IXUSR|S_IXGRP|S_IXOTH);
if (dir_mode != expected_mode) {
char mode_text[32];
memset(mode_text, '\0', sizeof(mode_text));
snprintf(mode_text, sizeof(mode_text)-1, "%04o", (int) dir_mode);
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "directory '", proxy_chroot,
"' has incorrect permissions (", mode_text, ", not 0111 as required)",
NULL));
}
}
}
add_config_param_str(cmd->argv[0], 1, cmd->argv[1]);
return PR_HANDLED(cmd);
}
/* usage: ProxyTimeoutConnect secs */
MODRET set_proxytimeoutconnect(cmd_rec *cmd) {
int timeout = -1;
config_rec *c = NULL;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
if (pr_str_get_duration(cmd->argv[1], &timeout) < 0) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error parsing timeout value '",
(char *) cmd->argv[1], "': ", strerror(errno), NULL));
}
c = add_config_param(cmd->argv[0], 1, NULL);
c->argv[0] = pcalloc(c->pool, sizeof(int));
*((int *) c->argv[0]) = timeout;
return PR_HANDLED(cmd);
}
/* usage: ProxyTimeoutLinger secs */
MODRET set_proxytimeoutlinger(cmd_rec *cmd) {
int timeout = -1;
config_rec *c = NULL;
char *timespec;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
timespec = cmd->argv[1];
if (pr_str_get_duration(timespec, &timeout) < 0) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error parsing timeout value '",
timespec, "': ", strerror(errno), NULL));
}
c = add_config_param(cmd->argv[0], 1, NULL);
c->argv[0] = pcalloc(c->pool, sizeof(int));
*((int *) c->argv[0]) = timeout;
return PR_HANDLED(cmd);
}
/* usage: ProxyTLSCACertificateFile path */
MODRET set_proxytlscacertfile(cmd_rec *cmd) {
#ifdef PR_USE_OPENSSL
int res;
char *path;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
path = cmd->argv[1];
PRIVS_ROOT
res = file_exists2(cmd->tmp_pool, path);
PRIVS_RELINQUISH
if (!res) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "'", path, "' does not exist",
NULL));
}
if (*path != '/') {
CONF_ERROR(cmd, "parameter must be an absolute path");
}
add_config_param_str(cmd->argv[0], 1, path);
return PR_HANDLED(cmd);
#else
CONF_ERROR(cmd, "Missing required OpenSSL support (see --enable-openssl configure option)");
#endif /* PR_USE_OPENSSL */
}
/* usage: ProxyTLSCACertificatePath path */
MODRET set_proxytlscacertpath(cmd_rec *cmd) {
#ifdef PR_USE_OPENSSL
int res;
char *path;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
path = cmd->argv[1];
PRIVS_ROOT
res = dir_exists2(cmd->tmp_pool, path);
PRIVS_RELINQUISH
if (!res) {
CONF_ERROR(cmd, "parameter must be a directory path");
}
if (*path != '/') {
CONF_ERROR(cmd, "parameter must be an absolute path");
}
add_config_param_str(cmd->argv[0], 1, path);
return PR_HANDLED(cmd);
#else
CONF_ERROR(cmd, "Missing required OpenSSL support (see --enable-openssl configure option)");
#endif /* PR_USE_OPENSSL */
}
/* usage: ProxyTLSCARevocationFile path */
MODRET set_proxytlscacrlfile(cmd_rec *cmd) {
#ifdef PR_USE_OPENSSL
int res;
char *path;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
path = cmd->argv[1];
PRIVS_ROOT
res = file_exists2(cmd->tmp_pool, path);
PRIVS_RELINQUISH
if (!res) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "'", path, "' does not exist",
NULL));
}
if (*path != '/') {
CONF_ERROR(cmd, "parameter must be an absolute path");
}
add_config_param_str(cmd->argv[0], 1, path);
return PR_HANDLED(cmd);
#else
CONF_ERROR(cmd, "Missing required OpenSSL support (see --enable-openssl configure option)");
#endif /* PR_USE_OPENSSL */
}
/* usage: ProxyTLSCARevocationPath path */
MODRET set_proxytlscacrlpath(cmd_rec *cmd) {
#ifdef PR_USE_OPENSSL
int res;
char *path;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
path = cmd->argv[1];
PRIVS_ROOT
res = dir_exists2(cmd->tmp_pool, path);
PRIVS_RELINQUISH
if (!res) {
CONF_ERROR(cmd, "parameter must be a directory path");
}
if (*path != '/') {
CONF_ERROR(cmd, "parameter must be an absolute path");
}
add_config_param_str(cmd->argv[0], 1, path);
return PR_HANDLED(cmd);
#else
CONF_ERROR(cmd, "Missing required OpenSSL support (see --enable-openssl configure option)");
#endif /* PR_USE_OPENSSL */
}
/* usage: ProxyTLSCertificateFile path */
MODRET set_proxytlscertfile(cmd_rec *cmd) {
#ifdef PR_USE_OPENSSL
int res;
char *path;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
path = cmd->argv[1];
PRIVS_ROOT
res = file_exists2(cmd->tmp_pool, path);
PRIVS_RELINQUISH
if (!res) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "'", path, "' does not exist",
NULL));
}
if (*path != '/') {
CONF_ERROR(cmd, "parameter must be an absolute path");
}
add_config_param_str(cmd->argv[0], 1, path);
return PR_HANDLED(cmd);
#else
CONF_ERROR(cmd, "Missing required OpenSSL support (see --enable-openssl configure option)");
#endif /* PR_USE_OPENSSL */
}
/* usage: ProxyTLSCertificateKeyFile path */
MODRET set_proxytlscertkeyfile(cmd_rec *cmd) {
#ifdef PR_USE_OPENSSL
int res;
char *path;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
path = cmd->argv[1];
PRIVS_ROOT
res = file_exists2(cmd->tmp_pool, path);
PRIVS_RELINQUISH
if (!res) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "'", path, "' does not exist",
NULL));
}
if (*path != '/') {
CONF_ERROR(cmd, "parameter must be an absolute path");
}
add_config_param_str(cmd->argv[0], 1, path);
return PR_HANDLED(cmd);
#else
CONF_ERROR(cmd, "Missing required OpenSSL support (see --enable-openssl configure option)");
#endif /* PR_USE_OPENSSL */
}
/* usage: ProxyTLSCipherSuite [protocol] ciphers */
MODRET set_proxytlsciphersuite(cmd_rec *cmd) {
#if defined(PR_USE_OPENSSL)
config_rec *c = NULL;
char *ciphersuite = NULL;
int protocol = 0;
SSL_CTX *ctx;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
if (cmd->argc-1 == 1) {
ciphersuite = cmd->argv[1];
/* Currently, OpenSSL ciphersuite names for TLSv1.3 all use underscores;
* ciphersuite names for TLSv1.2 and older do NOT use underscores.
*
* So if we see an underscore in the configured ciphersuites here, we
* know that the optional protocol parameter has NOT been used, and that
* a TLSv1.3 ciphersuite is being configured -- and that this situation
* will be silently ignored by OpenSSL.
*/
if (strchr(ciphersuite, '_') != NULL) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
"use of TLSv1.3 ciphersuite in '", ciphersuite,
"' requires protocol parameter; use 'ProxyTLSCipherSuite TLSv1.3 ",
ciphersuite, "'", NULL));
}
} else if (cmd->argc-1 == 2) {
char *protocol_text;
protocol_text = cmd->argv[1];
if (strcasecmp(protocol_text, "TLSv1.3") == 0) {
protocol = PROXY_TLS_PROTO_TLS_V1_3;
} else if (strcasecmp(protocol_text, "TLSv1.2") == 0) {
protocol = PROXY_TLS_PROTO_TLS_V1_2;
} else if (strcasecmp(protocol_text, "TLSv1.1") == 0) {
protocol = PROXY_TLS_PROTO_TLS_V1_1;
} else if (strcasecmp(protocol_text, "TLSv1.0") == 0) {
protocol = PROXY_TLS_PROTO_TLS_V1;
} else {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
"unknown/unsupported protocol specifier: ", protocol_text, NULL));
}
ciphersuite = cmd->argv[2];
}
c = add_config_param(cmd->argv[0], 2, NULL, NULL);
if (protocol == PROXY_TLS_PROTO_TLS_V1_3) {
ciphersuite = pstrdup(c->pool, ciphersuite);
} else {
/* Make sure that EXPORT ciphers cannot be used, per Bug#4163. Note that
* this breaks system profiles, so handle them specially.
*/
if (strncmp(ciphersuite, "PROFILE=", 8) == 0) {
ciphersuite = pstrdup(c->pool, ciphersuite);
} else {
ciphersuite = pstrcat(c->pool, "!EXPORT:", ciphersuite, NULL);
}
}
/* Check that our construct ciphersuite is acceptable. */
ctx = SSL_CTX_new(SSLv23_client_method());
if (ctx != NULL) {
int res = 1;
if (protocol == PROXY_TLS_PROTO_TLS_V1_3) {
#if OPENSSL_VERSION_NUMBER >= 0x10101000L && \
defined(TLS1_3_VERSION)
res = SSL_CTX_set_ciphersuites(ctx, ciphersuite);
#endif /* TLS1_3_VERSION */
} else {
res = SSL_CTX_set_cipher_list(ctx, ciphersuite);
}
if (res != 1) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
"unable to use ciphersuite '", ciphersuite, "': ",
proxy_tls_get_errors(), NULL));
}
SSL_CTX_free(ctx);
}
c->argv[0] = ciphersuite;
c->argv[1] = palloc(c->pool, sizeof(int));
*((int *) c->argv[1]) = protocol;
if (pr_module_exists("mod_ifsession.c")) {
/* These are needed in case this directive is used with mod_ifsession
* configuration.
*/
c->flags |= CF_MULTI;
}
return PR_HANDLED(cmd);
#else
CONF_ERROR(cmd, "Missing required OpenSSL support (see --enable-openssl configure option)");
#endif /* PR_USE_OPENSSL */
}
/* usage: ProxyTLSEngine on|off|auto|MatchClient */
MODRET set_proxytlsengine(cmd_rec *cmd) {
#ifdef PR_USE_OPENSSL
int engine = -1;
config_rec *c;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_GLOBAL|CONF_VIRTUAL);
engine = get_boolean(cmd, 1);
if (engine == -1) {
if (strcasecmp(cmd->argv[1], "auto") == 0) {
engine = PROXY_TLS_ENGINE_AUTO;
} else if (strcasecmp(cmd->argv[1], "MatchClient") == 0) {
engine = PROXY_TLS_ENGINE_MATCH_CLIENT;
} else {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unknown ProxyTLSEngine value: '",
cmd->argv[1], "'", NULL));
}
} else {
if (engine == TRUE) {
engine = PROXY_TLS_ENGINE_ON;
} else {
engine = PROXY_TLS_ENGINE_OFF;
}
}
c = add_config_param(cmd->argv[0], 1, NULL);
c->argv[0] = palloc(c->pool, sizeof(int));
*((int *) c->argv[0]) = engine;
return PR_HANDLED(cmd);
#else
CONF_ERROR(cmd, "Missing required OpenSSL support (see --enable-openssl configure option)");
#endif /* PR_USE_OPENSSL */
}
/* usage: ProxyTLSOptions ... */
MODRET set_proxytlsoptions(cmd_rec *cmd) {
#if defined(PR_USE_OPENSSL)
config_rec *c = NULL;
register unsigned int i = 0;
unsigned long opts = 0UL;
if (cmd->argc-1 == 0) {
CONF_ERROR(cmd, "wrong number of parameters");
}
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
c = add_config_param(cmd->argv[0], 1, NULL);
for (i = 1; i < cmd->argc; i++) {
if (strcmp(cmd->argv[i], "AllowWeakSecurity") == 0) {
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
opts |= PROXY_TLS_OPT_ALLOW_WEAK_SECURITY;
#else
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "The ", cmd->argv[i],
" option cannot be used on this system, as your OpenSSL version "
"is too old; requires OpenSSL-1.1.0 or later", NULL));
#endif /* OpenSSL-1.1.0 and later */
} else if (strcmp(cmd->argv[i], "EnableDiags") == 0) {
opts |= PROXY_TLS_OPT_ENABLE_DIAGS;
} else if (strcmp(cmd->argv[i], "NoSessionCache") == 0) {
opts |= PROXY_TLS_OPT_NO_SESSION_CACHE;
} else if (strcmp(cmd->argv[i], "NoSessionTickets") == 0) {
opts |= PROXY_TLS_OPT_NO_SESSION_TICKETS;
} else {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown ProxyTLSOption '",
cmd->argv[i], "'", NULL));
}
}
c->argv[0] = pcalloc(c->pool, sizeof(unsigned long));
*((unsigned long *) c->argv[0]) = opts;
if (pr_module_exists("mod_ifsession.c")) {
/* These are needed in case this directive is used with mod_ifsession
* configuration.
*/
c->flags |= CF_MULTI;
}
return PR_HANDLED(cmd);
#else
CONF_ERROR(cmd, "Missing required OpenSSL support (see --enable-openssl configure option)");
#endif /* PR_USE_OPENSSL */
}
/* usage: ProxyTLSPreSharedKey name path */
MODRET set_proxytlspresharedkey(cmd_rec *cmd) {
#ifdef PR_USE_OPENSSL
# if defined(PSK_MAX_PSK_LEN)
size_t identity_len, path_len;
char *path;
CHECK_ARGS(cmd, 2);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
identity_len = strlen(cmd->argv[1]);
if (identity_len > PSK_MAX_IDENTITY_LEN) {
char buf[32];
memset(buf, '\0', sizeof(buf));
snprintf(buf, sizeof(buf)-1, "%d", (int) PSK_MAX_IDENTITY_LEN);
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
"ProxyTLSPreSharedKey identity '", cmd->argv[1],
"' exceeds maximum length ", buf, cmd->argv[2], NULL))
}
/* Ensure that the given path starts with "hex:", denoting the
* format of the key at the given path. Support for other formats, e.g.
* bcrypt or somesuch, will be added later.
*/
path = cmd->argv[2];
path_len = strlen(path);
if (path_len < 5 ||
strncmp(cmd->argv[2], "hex:", 4) != 0) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
"unsupported ProxyTLSPreSharedKey format: ", path, NULL))
}
(void) add_config_param_str(cmd->argv[0], 2, cmd->argv[1], path);
# else
pr_log_debug(DEBUG0,
"%s is not supported by this build/version of OpenSSL, ignoring",
(char *) cmd->argv[0]);
# endif /* PSK support */
return PR_HANDLED(cmd);
#else
CONF_ERROR(cmd, "Missing required OpenSSL support (see --enable-openssl configure option)");
#endif /* PR_USE_OPENSSL */
}
/* usage: ProxyTLSProtocol protocols */
MODRET set_proxytlsprotocol(cmd_rec *cmd) {
#ifdef PR_USE_OPENSSL
register unsigned int i;
config_rec *c;
unsigned int tls_protocol = 0;
if (cmd->argc-1 == 0) {
CONF_ERROR(cmd, "wrong number of parameters");
}
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
if (strcasecmp(cmd->argv[1], "all") == 0) {
/* We're in an additive/subtractive type of configuration. */
tls_protocol = PROXY_TLS_PROTO_ALL;
for (i = 2; i < cmd->argc; i++) {
int disable = FALSE;
char *proto_name;
proto_name = cmd->argv[i];
if (*proto_name == '+') {
proto_name++;
} else if (*proto_name == '-') {
disable = TRUE;
proto_name++;
} else {
/* Using the additive/subtractive approach requires a +/- prefix;
* it's malformed without such prefaces.
*/
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "missing required +/- prefix: ",
proto_name, NULL));
}
if (strcasecmp(proto_name, "SSLv3") == 0) {
if (disable) {
tls_protocol &= ~PROXY_TLS_PROTO_SSL_V3;
} else {
tls_protocol |= PROXY_TLS_PROTO_SSL_V3;
}
} else if (strcasecmp(proto_name, "TLSv1") == 0 ||
strcasecmp(proto_name, "TLSv1.0") == 0) {
if (disable) {
tls_protocol &= ~PROXY_TLS_PROTO_TLS_V1;
} else {
tls_protocol |= PROXY_TLS_PROTO_TLS_V1;
}
} else if (strcasecmp(proto_name, "TLSv1.1") == 0) {
# if defined(TLS1_1_VERSION)
if (disable) {
tls_protocol &= ~PROXY_TLS_PROTO_TLS_V1_1;
} else {
tls_protocol |= PROXY_TLS_PROTO_TLS_V1_1;
}
# else
CONF_ERROR(cmd, "Your OpenSSL installation does not support TLSv1.1");
# endif /* OpenSSL 1.0.1 or later */
} else if (strcasecmp(proto_name, "TLSv1.2") == 0) {
# if defined(TLS1_2_VERSION)
if (disable) {
tls_protocol &= ~PROXY_TLS_PROTO_TLS_V1_2;
} else {
tls_protocol |= PROXY_TLS_PROTO_TLS_V1_2;
}
# else
CONF_ERROR(cmd, "Your OpenSSL installation does not support TLSv1.2");
# endif /* OpenSSL 1.0.1 or later */
} else if (strcasecmp(proto_name, "TLSv1.3") == 0) {
# if defined(TLS1_3_VERSION)
if (disable) {
tls_protocol &= ~PROXY_TLS_PROTO_TLS_V1_3;
} else {
tls_protocol |= PROXY_TLS_PROTO_TLS_V1_3;
}
# else
CONF_ERROR(cmd, "Your OpenSSL installation does not support TLSv1.3");
# endif /* OpenSSL 1.1.1 or later */
} else {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unknown protocol: '",
cmd->argv[i], "'", NULL));
}
}
} else {
for (i = 1; i < cmd->argc; i++) {
if (strcasecmp(cmd->argv[i], "SSLv23") == 0) {
tls_protocol |= PROXY_TLS_PROTO_SSL_V3;
tls_protocol |= PROXY_TLS_PROTO_TLS_V1;
} else if (strcasecmp(cmd->argv[i], "SSLv3") == 0) {
tls_protocol |= PROXY_TLS_PROTO_SSL_V3;
} else if (strcasecmp(cmd->argv[i], "TLSv1") == 0 ||
strcasecmp(cmd->argv[i], "TLSv1.0") == 0) {
tls_protocol |= PROXY_TLS_PROTO_TLS_V1;
} else if (strcasecmp(cmd->argv[i], "TLSv1.1") == 0) {
# if defined(TLS1_1_VERSION)
tls_protocol |= PROXY_TLS_PROTO_TLS_V1_1;
# else
CONF_ERROR(cmd, "Your OpenSSL installation does not support TLSv1.1");
# endif /* OpenSSL 1.0.1 or later */
} else if (strcasecmp(cmd->argv[i], "TLSv1.2") == 0) {
# if defined(TLS1_2_VERSION)
tls_protocol |= PROXY_TLS_PROTO_TLS_V1_2;
# else
CONF_ERROR(cmd, "Your OpenSSL installation does not support TLSv1.2");
# endif /* OpenSSL 1.0.1 or later */
} else if (strcasecmp(cmd->argv[i], "TLSv1.3") == 0) {
# if defined(TLS1_3_VERSION)
tls_protocol |= PROXY_TLS_PROTO_TLS_V1_3;
# else
CONF_ERROR(cmd, "Your OpenSSL installation does not support TLSv1.3");
# endif /* OpenSSL 1.1.1 or later */
} else {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unknown protocol: '",
cmd->argv[i], "'", NULL));
}
}
}
c = add_config_param(cmd->argv[0], 1, NULL);
c->argv[0] = palloc(c->pool, sizeof(unsigned int));
*((unsigned int *) c->argv[0]) = tls_protocol;
return PR_HANDLED(cmd);
#else
CONF_ERROR(cmd, "Missing required OpenSSL support (see --enable-openssl configure option)");
#endif /* PR_USE_OPENSSL */
}
/* usage: ProxyTLSTimeoutHandshake timeout */
MODRET set_proxytlstimeouthandshake(cmd_rec *cmd) {
#ifdef PR_USE_OPENSSL
int timeout = -1;
config_rec *c = NULL;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
if (pr_str_get_duration(cmd->argv[1], &timeout) < 0) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error parsing timeout value '",
cmd->argv[1], "': ", strerror(errno), NULL));
}
c = add_config_param(cmd->argv[0], 1, NULL);
c->argv[0] = pcalloc(c->pool, sizeof(unsigned int));
*((unsigned int *) c->argv[0]) = timeout;
return PR_HANDLED(cmd);
#else
CONF_ERROR(cmd, "Missing required OpenSSL support (see --enable-openssl configure option)");
#endif /* PR_USE_OPENSSL */
}
/* usage: ProxyTLSTransferProtectionPolicy client|required|clear */
MODRET set_proxytlsxferprotpolicy(cmd_rec *cmd) {
#ifdef PR_USE_OPENSSL
config_rec *c;
const char *policy;
int require_prot = 0;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
policy = cmd->argv[1];
if (strcasecmp(policy, "required") == 0) {
require_prot = PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_REQUIRED;
} else if (strcasecmp(policy, "client") == 0) {
require_prot = PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_CLIENT;
} else if (strcasecmp(policy, "clear") == 0) {
require_prot = PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_CLEAR;
} else {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
"unsupported ProxyTLSTransferProtectionPolicy: ", policy, NULL));
}
c = add_config_param(cmd->argv[0], 1, NULL);
c->argv[0] = palloc(c->pool, sizeof(int));
*((int *) c->argv[0]) = require_prot;
return PR_HANDLED(cmd);
#else
CONF_ERROR(cmd, "Missing required OpenSSL support (see --enable-openssl configure option)");
#endif /* PR_USE_OPENSSL */
}
/* usage: ProxyTLSVerifyServer on|off */
MODRET set_proxytlsverifyserver(cmd_rec *cmd) {
#ifdef PR_USE_OPENSSL
int verify = -1;
config_rec *c = NULL;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
verify = get_boolean(cmd, 1);
if (verify == -1) {
CONF_ERROR(cmd, "expected Boolean parameter");
}
c = add_config_param(cmd->argv[0], 1, NULL);
c->argv[0] = pcalloc(c->pool, sizeof(int));
*((int *) c->argv[0]) = verify;
return PR_HANDLED(cmd);
#else
CONF_ERROR(cmd, "Missing required OpenSSL support (see --enable-openssl configure option)");
#endif /* PR_USE_OPENSSL */
}
static void proxy_process_cmd(void) {
int res = 0;
cmd_rec *cmd = NULL;
/* TODO: Insert select(2) call here, where we wait for readability on
* the client control connection.
*
* Bonus points for handling aborts on either control connection,
* broken data connections, blocked/slow writes to client (how much
* can/should we buffer? what about short writes to the client?),
* timeouts, etc.
*/
res = pr_cmd_read(&cmd);
if (res < 0) {
if (PR_NETIO_ERRNO(session.c->instrm) == EINTR) {
/* Simple interrupted syscall */
return;
}
#ifndef PR_DEVEL_NO_DAEMON
/* Otherwise, EOF */
pr_session_disconnect(NULL, PR_SESS_DISCONNECT_CLIENT_EOF, NULL);
#else
return;
#endif /* PR_DEVEL_NO_DAEMON */
}
pr_timer_reset(PR_TIMER_IDLE, ANY_MODULE);
if (cmd != NULL) {
/* We unblock responses here so that if any PRE_CMD handlers generate
* responses (usually errors), those responses are sent to the
* connecting client.
*/
pr_response_block(FALSE);
/* TODO: If we need to, we can exert finer-grained control over
* command dispatching/routing here. For example, this is where we
* could block responses for PRE_CMD handlers, or skip certain
* modules' handlers.
*/
pr_cmd_dispatch(cmd);
destroy_pool(cmd->pool);
pr_response_block(TRUE);
} else {
pr_event_generate("core.invalid-command", NULL);
pr_response_send(R_500, _("Invalid command: try being more creative"));
}
/* Release any working memory allocated in inet */
pr_inet_clear();
}
/* mod_proxy event/dispatch loop. */
static void proxy_cmd_loop(server_rec *s, conn_t *conn) {
while (TRUE) {
pr_signals_handle();
proxy_process_cmd();
}
}
/* Command handlers
*/
static int proxy_data_handle_resp(pool *p, struct proxy_session *proxy_sess,
cmd_rec *cmd) {
int res, xerrno = 0;
pr_response_t *resp;
unsigned int resp_nlines = 0;
resp = proxy_ftp_ctrl_recv_resp(p, proxy_sess->backend_ctrl_conn,
&resp_nlines, 0);
if (resp == NULL) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error receiving %s response from backend: %s", (char *) cmd->argv[0],
strerror(xerrno));
pr_response_add_err(R_500, _("%s: %s"), (char *) cmd->argv[0],
strerror(xerrno));
pr_response_flush(&resp_err_list);
if (proxy_sess->backend_data_conn != NULL) {
proxy_inet_close(session.pool, proxy_sess->backend_data_conn);
pr_inet_close(session.pool, proxy_sess->backend_data_conn);
proxy_sess->backend_data_conn = NULL;
}
errno = xerrno;
return -1;
}
/* If the backend server responds with 4xx/5xx here, close the frontend
* data connection.
*/
if (resp->num[0] == '4' ||
resp->num[0] == '5') {
res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool,
proxy_sess->frontend_ctrl_conn, resp, resp_nlines);
if (session.d != NULL) {
pr_inet_close(session.pool, proxy_sess->frontend_data_conn);
proxy_sess->frontend_data_conn = session.d = NULL;
}
if (proxy_sess->backend_data_conn != NULL) {
proxy_inet_close(session.pool, proxy_sess->backend_data_conn);
pr_inet_close(session.pool, proxy_sess->backend_data_conn);
proxy_sess->backend_data_conn = NULL;
}
errno = EPERM;
return -1;
}
res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool,
proxy_sess->frontend_ctrl_conn, resp, resp_nlines);
if (res < 0) {
xerrno = errno;
if (session.d != NULL) {
pr_inet_close(session.pool, proxy_sess->frontend_data_conn);
proxy_sess->frontend_data_conn = session.d = NULL;
}
if (proxy_sess->backend_data_conn != NULL) {
proxy_inet_close(session.pool, proxy_sess->backend_data_conn);
pr_inet_close(session.pool, proxy_sess->backend_data_conn);
proxy_sess->backend_data_conn = NULL;
}
pr_response_block(TRUE);
pr_response_add_err(R_500, _("%s: %s"), (char *) cmd->argv[0],
strerror(xerrno));
pr_response_flush(&resp_err_list);
errno = xerrno;
return -1;
}
return 0;
}
static int proxy_data_prepare_backend_conn(struct proxy_session *proxy_sess,
cmd_rec *cmd, conn_t **backend) {
int res, xerrno;
conn_t *backend_conn = NULL;
/* XXX Should handle EPSV_ALL here, too. */
if (proxy_sess->backend_sess_flags & SF_PASSIVE) {
const pr_netaddr_t *bind_addr = NULL, *local_addr = NULL;
/* Connect to the backend server now. We won't receive the initial
* response until we connect to the backend data address/port.
*/
/* Specify the specific address/interface to use as the source address for
* connections to the destination server.
*/
bind_addr = proxy_sess->src_addr;
/* Check the family of the remote address vs what we'll be using to connect.
* If there's a mismatch, we need to get an addr with the matching family.
*/
if (bind_addr != NULL &&
pr_netaddr_get_family(bind_addr) != pr_netaddr_get_family(proxy_sess->backend_data_addr)) {
/* In this scenario, the proxy has an IPv6 socket, but the remote/backend
* server has an IPv4 (or IPv4-mapped IPv6) address. OR it's the proxy
* which has an IPv4 socket, and the remote/backend server has an IPv6
* address.
*/
if (pr_netaddr_get_family(session.c->local_addr) == AF_INET) {
char *ip_str;
/* Convert the local address from an IPv4 to an IPv6 addr. */
ip_str = pcalloc(cmd->tmp_pool, INET6_ADDRSTRLEN + 1);
snprintf(ip_str, INET6_ADDRSTRLEN, "::ffff:%s",
pr_netaddr_get_ipstr(session.c->local_addr));
local_addr = pr_netaddr_get_addr(cmd->tmp_pool, ip_str, NULL);
} else {
local_addr = pr_netaddr_v6tov4(cmd->tmp_pool, session.c->local_addr);
if (local_addr == NULL) {
pr_trace_msg(trace_channel, 4,
"error converting IPv6 local address %s to IPv4 address: %s",
pr_netaddr_get_ipstr(session.c->local_addr), strerror(errno));
if (proxy_sess->src_addr == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"local address '%s' is an IPv6 address, and remote address "
"'%s' is an IPv4 address; consider using ProxySourceAddress "
"directive to configure an IPv4 address",
pr_netaddr_get_ipstr(session.c->local_addr),
pr_netaddr_get_ipstr(proxy_sess->backend_data_addr));
}
}
}
if (local_addr != NULL) {
pr_trace_msg(trace_channel, 7,
"converted local address %s to %s for passive backend transfer",
pr_netaddr_get_ipstr(session.c->local_addr),
pr_netaddr_get_ipstr(local_addr));
} else {
local_addr = session.c->local_addr;
}
}
if (bind_addr == NULL) {
bind_addr = local_addr;
}
if (pr_netaddr_is_loopback(bind_addr) == TRUE &&
pr_netaddr_is_loopback(proxy_sess->backend_ctrl_conn->remote_addr) != TRUE) {
const char *local_name;
local_name = pr_netaddr_get_localaddr_str(cmd->pool);
local_addr = pr_netaddr_get_addr(cmd->pool, local_name, NULL);
if (local_addr != NULL) {
int local_family, remote_family;
/* We need to make sure our local address family matches that
* of the remote address.
*/
local_family = pr_netaddr_get_family(local_addr);
remote_family = pr_netaddr_get_family(proxy_sess->backend_ctrl_conn->remote_addr);
if (local_family != remote_family) {
pr_netaddr_t *new_addr = NULL;
#if defined(PR_USE_IPV6)
if (local_family == AF_INET) {
new_addr = pr_netaddr_v4tov6(cmd->pool, local_addr);
} else {
new_addr = pr_netaddr_v6tov4(cmd->pool, local_addr);
}
#endif /* PR_USE_IPV6 */
if (new_addr != NULL) {
local_addr = new_addr;
}
}
pr_trace_msg(trace_channel, 14,
"%s is a loopback address, and unable to reach %s; using %s instead",
pr_netaddr_get_ipstr(bind_addr),
pr_netaddr_get_ipstr(proxy_sess->backend_data_addr),
pr_netaddr_get_ipstr(local_addr));
bind_addr = local_addr;
}
}
pr_trace_msg(trace_channel, 17,
"connecting to backend server for passive data transfer for %s",
(char *) cmd->argv[0]);
backend_conn = proxy_ftp_conn_connect(cmd->pool, bind_addr,
proxy_sess->backend_data_addr, FALSE);
if (backend_conn == NULL) {
pr_trace_msg(trace_channel, 9,
"error connecting to backend server for passive data transfer: %s",
strerror(errno));
}
proxy_sess->backend_data_conn = backend_conn;
/* Note that we delay the post-open on the backend data connection until
* after we have received the backend response. In the case of a 4xx/5xx
* response, then such a post-open (as for a TLS handshake) will fail
* (Issue #244).
*/
res = proxy_data_handle_resp(cmd->tmp_pool, proxy_sess, cmd);
if (res < 0) {
return -1;
}
if (proxy_netio_postopen(backend_conn->instrm) < 0) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"postopen error for backend data connection input stream: %s",
strerror(xerrno));
proxy_inet_close(session.pool, backend_conn);
pr_inet_close(session.pool, backend_conn);
proxy_sess->backend_data_conn = NULL;
errno = xerrno;
return -1;
}
if (proxy_netio_postopen(backend_conn->outstrm) < 0) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"postopen error for backend data connection output stream: %s",
strerror(xerrno));
proxy_inet_close(session.pool, backend_conn);
pr_inet_close(session.pool, backend_conn);
proxy_sess->backend_data_conn = NULL;
errno = xerrno;
return -1;
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"passive backend data connection opened - local : %s:%d",
pr_netaddr_get_ipstr(backend_conn->local_addr),
backend_conn->local_port);
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"passive backend data connection opened - remote : %s:%d",
pr_netaddr_get_ipstr(backend_conn->remote_addr),
backend_conn->remote_port);
} else if (proxy_sess->backend_sess_flags & SF_PORT) {
res = proxy_data_handle_resp(cmd->tmp_pool, proxy_sess, cmd);
if (res < 0) {
return -1;
}
pr_trace_msg(trace_channel, 17,
"accepting connection from backend server for active data "
"transfer for %s", (char *) cmd->argv[0]);
backend_conn = proxy_ftp_conn_accept(cmd->pool,
proxy_sess->backend_data_conn, proxy_sess->backend_ctrl_conn, FALSE);
if (backend_conn == NULL) {
xerrno = errno;
if (proxy_sess->backend_data_conn != NULL) {
proxy_inet_close(session.pool, proxy_sess->backend_data_conn);
pr_inet_close(session.pool, proxy_sess->backend_data_conn);
proxy_sess->backend_data_conn = NULL;
}
pr_response_add_err(R_425, _("%s: %s"), (char *) cmd->argv[0],
strerror(xerrno));
pr_response_flush(&resp_err_list);
errno = xerrno;
return -1;
}
/* We can close our listening socket now. */
proxy_inet_close(session.pool, proxy_sess->backend_data_conn);
pr_inet_close(session.pool, proxy_sess->backend_data_conn);
proxy_sess->backend_data_conn = backend_conn;
if (proxy_netio_postopen(backend_conn->instrm) < 0) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"postopen error for backend data connection input stream: %s",
strerror(xerrno));
proxy_inet_close(session.pool, backend_conn);
pr_inet_close(session.pool, backend_conn);
proxy_sess->backend_data_conn = NULL;
errno = xerrno;
return -1;
}
if (proxy_netio_postopen(backend_conn->outstrm) < 0) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"postopen error for backend data connection output stream: %s",
strerror(xerrno));
proxy_inet_close(session.pool, backend_conn);
pr_inet_close(session.pool, backend_conn);
proxy_sess->backend_data_conn = NULL;
errno = xerrno;
return -1;
}
pr_inet_set_nonblock(session.pool, backend_conn);
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"active backend data connection opened - local : %s:%d",
pr_netaddr_get_ipstr(backend_conn->local_addr),
backend_conn->local_port);
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"active backend data connection opened - remote : %s:%d",
pr_netaddr_get_ipstr(backend_conn->remote_addr),
backend_conn->remote_port);
}
*backend = backend_conn;
return 0;
}
static int proxy_data_prepare_frontend_conn(struct proxy_session *proxy_sess,
cmd_rec *cmd, conn_t **frontend) {
int xerrno;
conn_t *frontend_conn = NULL;
if (proxy_sess->frontend_sess_flags & SF_PASSIVE) {
pr_trace_msg(trace_channel, 17,
"accepting connection from frontend client for passive data "
"transfer for %s", (char *) cmd->argv[0]);
frontend_conn = proxy_ftp_conn_accept(cmd->pool,
proxy_sess->frontend_data_conn, proxy_sess->frontend_ctrl_conn, TRUE);
if (frontend_conn == NULL) {
xerrno = errno;
if (proxy_sess->frontend_data_conn != NULL) {
pr_inet_close(session.pool, proxy_sess->frontend_data_conn);
proxy_sess->frontend_data_conn = session.d = NULL;
}
pr_response_add_err(R_425, _("%s: %s"), (char *) cmd->argv[0],
strerror(xerrno));
pr_response_flush(&resp_err_list);
errno = xerrno;
return -1;
}
/* Note that we need to set session.d here with the opened conn, for the
* benefit of other callbacks (e.g. in mod_tls) invoked via these
* NetIO calls.
*/
session.d = frontend_conn;
if (pr_netio_postopen(frontend_conn->instrm) < 0) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"postopen error for frontend data connection input stream: %s",
strerror(xerrno));
pr_inet_close(session.pool, frontend_conn);
session.d = NULL;
errno = xerrno;
return -1;
}
if (pr_netio_postopen(frontend_conn->outstrm) < 0) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"postopen error for frontend data connection output stream: %s",
strerror(xerrno));
pr_inet_close(session.pool, frontend_conn);
session.d = NULL;
errno = xerrno;
return -1;
}
/* We can close our listening socket now. */
pr_inet_close(session.pool, proxy_sess->frontend_data_conn);
proxy_sess->frontend_data_conn = session.d = frontend_conn;
pr_inet_set_nonblock(session.pool, frontend_conn);
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"passive frontend data connection opened - local : %s:%d",
pr_netaddr_get_ipstr(frontend_conn->local_addr),
frontend_conn->local_port);
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"passive frontend data connection opened - remote : %s:%d",
pr_netaddr_get_ipstr(frontend_conn->remote_addr),
frontend_conn->remote_port);
} else if (proxy_sess->frontend_sess_flags & SF_PORT) {
const pr_netaddr_t *bind_addr;
/* Connect to the frontend server now. */
if (pr_netaddr_get_family(session.c->local_addr) == pr_netaddr_get_family(session.c->remote_addr)) {
bind_addr = session.c->local_addr;
} else {
/* In this scenario, the server has an IPv6 socket, but the remote client
* is an IPv4 (or IPv4-mapped IPv6) peer.
*/
bind_addr = pr_netaddr_v6tov4(session.xfer.p, session.c->local_addr);
}
pr_trace_msg(trace_channel, 17,
"connecting to frontend server for active data transfer for %s",
(char *) cmd->argv[0]);
frontend_conn = proxy_ftp_conn_connect(cmd->pool, bind_addr,
proxy_sess->frontend_data_addr, TRUE);
if (frontend_conn == NULL) {
xerrno = errno;
pr_response_add_err(R_425, _("%s: %s"), (char *) cmd->argv[0],
strerror(xerrno));
pr_response_flush(&resp_err_list);
errno = xerrno;
return -1;
}
/* Note that we need to set session.d here with the opened conn, for the
* benefit of other callbacks (e.g. in mod_tls) invoked via these
* NetIO calls.
*/
session.d = frontend_conn;
if (pr_netio_postopen(frontend_conn->instrm) < 0) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"postopen error for frontend data connection input stream: %s",
strerror(xerrno));
pr_inet_close(session.pool, frontend_conn);
session.d = NULL;
errno = xerrno;
return -1;
}
if (pr_netio_postopen(frontend_conn->outstrm) < 0) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"postopen error for frontend data connection output stream: %s",
strerror(xerrno));
pr_inet_close(session.pool, frontend_conn);
session.d = NULL;
errno = xerrno;
return -1;
}
proxy_sess->frontend_data_conn = session.d = frontend_conn;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"active frontend data connection opened - local : %s:%d",
pr_netaddr_get_ipstr(frontend_conn->local_addr),
frontend_conn->local_port);
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"active frontend data connection opened - remote : %s:%d",
pr_netaddr_get_ipstr(frontend_conn->remote_addr),
frontend_conn->remote_port);
}
*frontend = frontend_conn;
return 0;
}
static int proxy_data_prepare_conns(struct proxy_session *proxy_sess,
cmd_rec *cmd, conn_t **frontend, conn_t **backend) {
int res, xerrno = 0;
/* First we proxy the command to the backend server, which starts the
* process.
*/
res = proxy_ftp_ctrl_send_cmd(cmd->tmp_pool, proxy_sess->backend_ctrl_conn,
cmd);
if (res < 0) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error sending %s to backend: %s", (char *) cmd->argv[0],
strerror(xerrno));
pr_response_add_err(R_500, _("%s: %s"), (char *) cmd->argv[0],
strerror(xerrno));
pr_response_flush(&resp_err_list);
errno = xerrno;
return -1;
}
res = proxy_data_prepare_backend_conn(proxy_sess, cmd, backend);
if (res < 0) {
return -1;
}
res = proxy_data_prepare_frontend_conn(proxy_sess, cmd, frontend);
if (res < 0) {
return -1;
}
return 0;
}
MODRET proxy_data(struct proxy_session *proxy_sess, cmd_rec *cmd) {
int data_eof = FALSE, dst_xerrno = 0, res, xerrno;
int xfer_direction, xfer_ok = TRUE;
unsigned int resp_nlines = 0;
pr_response_t *resp;
conn_t *frontend_conn = NULL, *backend_conn = NULL;
off_t bytes_transferred = 0;
/* We are handling a data transfer command (e.g. LIST, RETR, etc).
*
* Thus we need to check the proxy_session->backend_sess_flags, and
* determine whether we are to connect to the backend server, or open a
* listening socket to which the backend will connect. Then we send the
* given command to the backend.
*
* At the same time, we will need to be managing the data connection
* from the frontend client separately; we will need to multiplex
* across the four connections: frontend control, frontend data,
* backend control, backend data.
*/
if (pr_cmd_cmp(cmd, PR_CMD_APPE_ID) == 0 ||
pr_cmd_cmp(cmd, PR_CMD_STOR_ID) == 0 ||
pr_cmd_cmp(cmd, PR_CMD_STOU_ID) == 0) {
/* Uploading, i.e. writing to backend data conn. */
xfer_direction = PR_NETIO_IO_WR;
session.xfer.path = pr_table_get(cmd->notes, "mod_xfer.store-path", NULL);
if (session.xfer.path == NULL) {
/* Add this note for the Jot API, and resolving %F/%f variables. */
(void) pr_table_add_dup(cmd->notes, "mod_xfer.store-path", cmd->arg, 0);
session.xfer.path = cmd->arg;
}
} else {
/* Downloading, i.e. reading from backend data conn.*/
xfer_direction = PR_NETIO_IO_RD;
session.xfer.path = pr_table_get(cmd->notes, "mod_xfer.retr-path", NULL);
if (session.xfer.path == NULL) {
/* Add this note for the Jot API, and resolving %F/%f variables. */
(void) pr_table_add_dup(cmd->notes, "mod_xfer.retr-path", cmd->arg, 0);
session.xfer.path = cmd->arg;
}
}
res = proxy_data_prepare_conns(proxy_sess, cmd, &frontend_conn,
&backend_conn);
if (res < 0) {
return PR_ERROR(cmd);
}
/* If we don't have our frontend/backend connections by now, it's a
* problem.
*/
if (frontend_conn == NULL ||
backend_conn == NULL) {
xerrno = EPERM;
pr_response_block(TRUE);
pr_response_add_err(R_425, _("%s: %s"), (char *) cmd->argv[0],
strerror(xerrno));
pr_response_flush(&resp_err_list);
errno = xerrno;
return PR_ERROR(cmd);
}
/* Allow aborts -- set the current NetIO stream to allow interrupted
* syscalls, so our SIGURG handler can interrupt it
*/
switch (xfer_direction) {
case PR_NETIO_IO_RD:
proxy_netio_set_poll_interval(backend_conn->instrm, 1);
pr_netio_set_poll_interval(frontend_conn->outstrm, 1);
break;
case PR_NETIO_IO_WR:
proxy_netio_set_poll_interval(backend_conn->outstrm, 1);
pr_netio_set_poll_interval(frontend_conn->instrm, 1);
break;
}
proxy_sess->frontend_sess_flags |= SF_XFER;
proxy_sess->backend_sess_flags |= SF_XFER;
/* Honor TransferRate directives. */
pr_throttle_init(cmd);
if (pr_data_get_timeout(PR_DATA_TIMEOUT_NO_TRANSFER) > 0) {
pr_timer_reset(PR_TIMER_NOXFER, ANY_MODULE);
}
if (pr_data_get_timeout(PR_DATA_TIMEOUT_STALLED) > 0) {
pr_timer_add(pr_data_get_timeout(PR_DATA_TIMEOUT_STALLED),
PR_TIMER_STALLED, &proxy_module, proxy_stalled_timeout_cb,
"TimeoutStalled");
}
/* XXX Note: when reading/writing data from data connections, do NOT
* perform any sort of ASCII translation; we leave the data as is.
* (Or maybe we SHOULD perform the ASCII translation here, in case of
* ASCII translation error; the backend server can then be told that
* the data are binary, and thus relieve the backend of the translation
* burden. Configurable?)
*/
while (TRUE) {
fd_set rfds;
struct timeval tv;
int backend_ctrlfd = -1, frontend_ctrlfd = -1, datafd = -1, maxfd = -1;
int frontend_data = FALSE;
conn_t *src_data_conn = NULL, *dst_data_conn = NULL;
pr_signals_handle();
if (data_eof == TRUE ||
xfer_ok == FALSE) {
tv.tv_sec = proxy_sess->linger_timeout;
} else {
tv.tv_sec = 15;
}
tv.tv_usec = 0;
FD_ZERO(&rfds);
/* The source/origin data connection depends on our direction:
* downloads (IO_RD) from the backend, uploads (IO_WR) to the backend.
*/
switch (xfer_direction) {
case PR_NETIO_IO_RD:
src_data_conn = proxy_sess->backend_data_conn;
dst_data_conn = proxy_sess->frontend_data_conn;
frontend_data = FALSE;
break;
case PR_NETIO_IO_WR:
src_data_conn = proxy_sess->frontend_data_conn;
dst_data_conn = proxy_sess->backend_data_conn;
frontend_data = TRUE;
break;
}
/* Note: don't start listening for responses from the backend control
* connection until we have all of the data (data_eof = TRUE), OR if
* encountered some other error with the transfer.
*/
if (data_eof == TRUE ||
xfer_ok == FALSE) {
backend_ctrlfd = PR_NETIO_FD(proxy_sess->backend_ctrl_conn->instrm);
FD_SET(backend_ctrlfd, &rfds);
if (backend_ctrlfd > maxfd) {
maxfd = backend_ctrlfd;
}
}
frontend_ctrlfd = PR_NETIO_FD(proxy_sess->frontend_ctrl_conn->instrm);
FD_SET(frontend_ctrlfd, &rfds);
if (frontend_ctrlfd > maxfd) {
maxfd = frontend_ctrlfd;
}
if (src_data_conn != NULL) {
datafd = PR_NETIO_FD(src_data_conn->instrm);
FD_SET(datafd, &rfds);
if (datafd > maxfd) {
maxfd = datafd;
}
}
res = select(maxfd + 1, &rfds, NULL, NULL, &tv);
if (res < 0) {
xerrno = errno;
if (xerrno == EINTR) {
pr_signals_handle();
continue;
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error calling select(2) while transferring data: %s",
strerror(xerrno));
if (proxy_sess->frontend_data_conn != NULL) {
pr_inet_close(session.pool, proxy_sess->frontend_data_conn);
proxy_sess->frontend_data_conn = session.d = NULL;
}
if (proxy_sess->backend_data_conn != NULL) {
proxy_inet_close(session.pool, proxy_sess->backend_data_conn);
pr_inet_close(session.pool, proxy_sess->backend_data_conn);
proxy_sess->backend_data_conn = NULL;
}
pr_timer_remove(PR_TIMER_STALLED, ANY_MODULE);
proxy_sess->frontend_sess_flags &= ~SF_XFER;
proxy_sess->backend_sess_flags &= ~SF_XFER;
pr_response_add_err(R_500, _("%s: %s"), (char *) cmd->argv[0],
strerror(xerrno));
pr_response_flush(&resp_err_list);
pr_response_block(TRUE);
errno = xerrno;
return PR_ERROR(cmd);
}
if (res == 0) {
if (data_eof == TRUE ||
xfer_ok == FALSE) {
if (data_eof == TRUE) {
/* We've timed out waiting for the end-of-transfer response on the
* backend control connection.
*/
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"timed out waiting for end-of-transfer response from backend "
"server, terminating transfer");
}
pr_timer_remove(PR_TIMER_STALLED, ANY_MODULE);
proxy_sess->frontend_sess_flags &= ~SF_XFER;
proxy_sess->backend_sess_flags &= ~SF_XFER;
xerrno = data_eof ? ETIMEDOUT : dst_xerrno;
pr_response_add_err(R_500, _("%s: %s"), (char *) cmd->argv[0],
strerror(xerrno));
pr_response_flush(&resp_err_list);
pr_response_block(TRUE);
errno = xerrno;
return PR_ERROR(cmd);
}
/* XXX Have MAX_RETRIES logic here. */
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"timed out waiting for readability on control/data connections, "
"trying again");
continue;
}
/* Any commands from the frontend client take priority */
if (frontend_ctrlfd >= 0 &&
FD_ISSET(frontend_ctrlfd, &rfds)) {
proxy_process_cmd();
pr_response_block(FALSE);
/* Check for closed frontend/backend data connections, as from an ABOR
* command.
*/
if (proxy_sess->frontend_data_conn == NULL ||
proxy_sess->backend_data_conn == NULL) {
if (pr_data_get_timeout(PR_DATA_TIMEOUT_STALLED) > 0) {
pr_timer_remove(PR_TIMER_STALLED, ANY_MODULE);
}
#if PROFTPD_VERSION_NUMBER >= 0x0001030901
pr_throttle_pause(bytes_transferred, TRUE, bytes_transferred);
#else
pr_throttle_pause(bytes_transferred, TRUE);
#endif /* Prior to ProFTPD 1.3.9rc1 */
pr_response_clear(&resp_list);
pr_response_clear(&resp_err_list);
return PR_HANDLED(cmd);
}
}
if (src_data_conn != NULL &&
datafd >= 0 &&
FD_ISSET(datafd, &rfds)) {
/* Some data arrived on the data connection... */
pr_buffer_t *pbuf = NULL;
pr_trace_msg(trace_channel, 19,
"handling data connection during data transfer");
pr_timer_reset(PR_TIMER_IDLE, ANY_MODULE);
pbuf = proxy_ftp_data_recv(cmd->tmp_pool, src_data_conn, frontend_data);
if (pbuf == NULL) {
xerrno = errno;
if (xerrno == EAGAIN) {
/* We have not yet received enough data from the backend to proceed;
* loop around to wait for more data.
*/
continue;
}
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error receiving from source data connection: %s",
strerror(xerrno));
} else {
size_t nread;
pr_timer_reset(PR_TIMER_NOXFER, ANY_MODULE);
pr_timer_reset(PR_TIMER_STALLED, ANY_MODULE);
nread = pbuf->current - pbuf->buf;
if (nread == 0) {
/* EOF on the data connection; close BOTH of them. In many
* cases, closing these connections causes any buffered data to
* be flushed out to the waiting peer.
*/
pr_trace_msg(trace_channel, 19,
"read EOF on data connection, closing frontend/backend data "
"connections");
proxy_inet_close(session.pool, proxy_sess->backend_data_conn);
pr_inet_close(session.pool, proxy_sess->backend_data_conn);
proxy_sess->backend_data_conn = NULL;
if (proxy_sess->frontend_data_conn != NULL) {
pr_inet_close(session.pool, proxy_sess->frontend_data_conn);
proxy_sess->frontend_data_conn = session.d = NULL;
}
proxy_sess->frontend_sess_flags &= ~SF_XFER;
proxy_sess->backend_sess_flags &= ~SF_XFER;
data_eof = TRUE;
} else {
size_t nwrote = 0;
char *ptr;
pr_trace_msg(trace_channel, 9,
"received %lu bytes of data from source data connection",
(unsigned long) nread);
session.xfer.total_bytes += nread;
bytes_transferred += nread;
#if PROFTPD_VERSION_NUMBER >= 0x0001030901
pr_throttle_pause(bytes_transferred, FALSE, bytes_transferred);
#else
pr_throttle_pause(bytes_transferred, FALSE);
#endif /* Prior to ProFTPD 1.3.9rc1 */
/* We use a loop in order to properly handle short writes.
*
* Since we are writing the pbuf from the head, we need to advance
* that pointer for every write. So we store a pointer to the
* original buffer here, to be restored after the writes.
*/
ptr = pbuf->buf;
while (nwrote != nread) {
int len;
len = proxy_ftp_data_send(cmd->tmp_pool, dst_data_conn, pbuf,
!frontend_data);
if (len < 0) {
xerrno = errno;
if (xerrno == EINTR) {
errno = EINTR;
pr_signals_handle();
continue;
}
errno = xerrno;
res = -1;
break;
}
nwrote += len;
pbuf->buf += len;
res = len;
}
/* Restore the pbuf. */
pbuf->buf = ptr;
if (nwrote == nread) {
pbuf->current = pbuf->buf;
pbuf->remaining = pbuf->buflen;
}
if (res < 0) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error writing %lu bytes of data to destination data "
"connection: %s", (unsigned long) nread, strerror(xerrno));
/* If this happens, close our connection prematurely.
* XXX Should we try to send an ABOR here, too? Or SIGURG?
* XXX Should we only do this for e.g. Broken pipe?
*/
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to proxy data between frontend/backend, "
"closing data connections");
proxy_inet_close(session.pool, proxy_sess->backend_data_conn);
pr_inet_close(session.pool, proxy_sess->backend_data_conn);
proxy_sess->backend_data_conn = NULL;
if (proxy_sess->frontend_data_conn != NULL) {
pr_inet_close(session.pool, proxy_sess->frontend_data_conn);
proxy_sess->frontend_data_conn = session.d = NULL;
}
proxy_sess->frontend_sess_flags &= ~SF_XFER;
proxy_sess->backend_sess_flags &= ~SF_XFER;
xfer_ok = FALSE;
dst_xerrno = xerrno;
}
}
}
}
/* Look for a response on the backend control connection if we've received
* EOF on the data connection.
*
* Note that the backend control connection might be readable before we've
* reached EOF on the data connection, but if we read its response in the
* middle of the transfer, we risk data truncation. I.e. the backend
* control response of e.g. 226 might be racing the data connection EOF, and
* we don't want to read the 226 response and ASSUME that we have all of
* the data; we need the explicit EOF for that.
*/
if (data_eof == TRUE ||
xfer_ok == FALSE) {
/* Hopefully we have some data on the ctrl connection... */
pr_trace_msg(trace_channel, 19,
"handling control connection after data transfer");
pr_timer_reset(PR_TIMER_IDLE, ANY_MODULE);
resp = proxy_ftp_ctrl_recv_resp(cmd->tmp_pool,
proxy_sess->backend_ctrl_conn, &resp_nlines, 0);
if (resp == NULL) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error receiving response from backend control connection: %s",
strerror(xerrno));
if (proxy_sess->frontend_data_conn != NULL) {
pr_inet_close(session.pool, proxy_sess->frontend_data_conn);
proxy_sess->frontend_data_conn = session.d = NULL;
}
if (proxy_sess->backend_data_conn != NULL) {
proxy_inet_close(session.pool, proxy_sess->backend_data_conn);
pr_inet_close(session.pool, proxy_sess->backend_data_conn);
proxy_sess->backend_data_conn = NULL;
}
/* For a certain number of conditions, if we cannot read the response
* from the backend, then we should just close the frontend, otherwise
* we might "leak" to the client the fact that we are fronting some
* backend server rather than being the server.
*/
if (xerrno == ECONNRESET ||
xerrno == ECONNABORTED ||
xerrno == ENOENT ||
xerrno == EPIPE ||
xerrno == EPERM) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"backend control connection closed (%s), closing proxy session",
strerror(xerrno));
pr_session_disconnect(&proxy_module,
PR_SESS_DISCONNECT_BY_APPLICATION,
"Backend control connection lost");
}
xfer_ok = FALSE;
break;
} else {
/* If not a 1xx response, close the destination data connection,
* BEFORE we send the response from the backend to the connected client.
*/
if (resp->num[0] != '1') {
switch (xfer_direction) {
case PR_NETIO_IO_RD:
if (proxy_sess->frontend_data_conn != NULL) {
pr_inet_close(session.pool, proxy_sess->frontend_data_conn);
proxy_sess->frontend_data_conn = session.d = NULL;
}
break;
case PR_NETIO_IO_WR:
if (proxy_sess->backend_data_conn != NULL) {
proxy_inet_close(session.pool, proxy_sess->backend_data_conn);
pr_inet_close(session.pool, proxy_sess->backend_data_conn);
proxy_sess->backend_data_conn = NULL;
}
break;
}
/* If the response was a 4xx or 5xx, then we need to note that as
* a failed transfer.
*/
/* XXX What about ABOR/aborted transfers? */
if (resp->num[0] == '4' ||
resp->num[0] == '5') {
xfer_ok = FALSE;
}
}
res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool,
proxy_sess->frontend_ctrl_conn, resp, resp_nlines);
if (res < 0) {
xerrno = errno;
pr_response_add_err(R_500, _("%s: %s"), (char *) cmd->argv[0],
strerror(xerrno));
pr_response_flush(&resp_err_list);
pr_timer_remove(PR_TIMER_STALLED, ANY_MODULE);
errno = xerrno;
return PR_ERROR(cmd);
}
/* If we get a 1xx response here, keep going. Otherwise, we're
* done with this data transfer.
*/
if (src_data_conn == NULL ||
(resp->num)[0] != '1') {
proxy_sess->frontend_sess_flags &= (SF_ALL^SF_PASSIVE);
proxy_sess->frontend_sess_flags &= (SF_ALL^(SF_ABORT|SF_XFER|SF_PASSIVE|SF_ASCII_OVERRIDE));
proxy_sess->backend_sess_flags &= (SF_ALL^SF_PASSIVE);
proxy_sess->backend_sess_flags &= (SF_ALL^(SF_ABORT|SF_XFER|SF_PASSIVE|SF_ASCII_OVERRIDE));
break;
}
}
}
}
if (pr_data_get_timeout(PR_DATA_TIMEOUT_STALLED) > 0) {
pr_timer_remove(PR_TIMER_STALLED, ANY_MODULE);
}
#if PROFTPD_VERSION_NUMBER >= 0x0001030901
pr_throttle_pause(bytes_transferred, TRUE, bytes_transferred);
#else
pr_throttle_pause(bytes_transferred, TRUE);
#endif /* Prior to ProFTPD 1.3.9rc1 */
proxy_sess->frontend_sess_flags &= ~SF_XFER;
proxy_sess->backend_sess_flags &= ~SF_XFER;
pr_response_clear(&resp_list);
pr_response_clear(&resp_err_list);
return (xfer_ok ? PR_HANDLED(cmd) : PR_ERROR(cmd));
}
static void proxy_dirlist_data_ev(const void *event_data, void *user_data) {
int res;
pr_buffer_t *pbuf;
char *buf, *text = NULL;
size_t buflen, textlen = 0;
pbuf = (pr_buffer_t *) event_data;
buf = pbuf->buf;
buflen = pbuf->current - pbuf->buf;
pr_trace_msg(trace_channel, 25, "received directory data (%lu bytes)",
(unsigned long) buflen);
res = proxy_ftp_dirlist_to_text(session.xfer.p, buf, buflen, pbuf->buflen,
&text, &textlen, user_data);
if (res < 0) {
pr_trace_msg(trace_channel, 3,
"unable to handle directory data: %s", strerror(errno));
return;
}
/* If we have no text to emit, it means we consumed the given buffer, but
* it was insufficient to generate output. Thus we empty the pbuf, and
* let the caller read more.
*/
if (text == NULL &&
textlen == 0) {
pbuf->current = pbuf->buf;
pbuf->remaining = pbuf->buflen;
return;
}
memcpy(pbuf->buf, text, textlen);
pbuf->current = pbuf->buf + textlen;
pbuf->remaining = pbuf->buflen - textlen;
}
MODRET proxy_directory_data(struct proxy_session *proxy_sess, cmd_rec *cmd) {
int xerrno;
char *list_opts, *list_path, *list_arg;
cmd_rec *list_cmd;
modret_t *mr;
unsigned long facts_opts;
/* We currently implement directory translation only for MLSD commands from
* frontend clients.
*/
if (cmd->cmd_id != PR_CMD_MLSD_ID) {
return proxy_data(proxy_sess, cmd);
}
if (proxy_sess->dirlist_policy == PROXY_SESS_DIRECTORY_LIST_POLICY_DEFAULT) {
return proxy_data(proxy_sess, cmd);
}
if (proxy_ftp_dirlist_init(cmd->tmp_pool, proxy_sess) < 0) {
/* TODO: What to do if this fails? */
}
pr_event_register(&proxy_module, "mod_proxy.data-read",
proxy_dirlist_data_ev, proxy_sess);
/* Replace the given MLSD `cmd` here with an appropriately rewritten LIST
* cmd, copying paths, adding options, etc.
*/
facts_opts = proxy_ftp_facts_get_opts();
if (facts_opts & PROXY_FTP_FACTS_OPT_SHOW_UNIX_OWNER_NAME) {
list_opts = list_arg = pstrdup(cmd->pool, "-al");
} else {
list_opts = list_arg = pstrdup(cmd->pool, "-aln");
}
if (cmd->argc == 1) {
list_path = pstrdup(cmd->pool, "");
} else {
list_path = pstrdup(cmd->pool, cmd->argv[1]);
list_arg = pstrcat(cmd->pool, list_arg, " ", list_path, NULL);
}
list_cmd = pr_cmd_alloc(cmd->pool, cmd->argc + 1, C_LIST, list_opts,
list_path);
list_cmd->arg = list_arg;
mr = proxy_data(proxy_sess, list_cmd);
xerrno = errno;
pr_event_unregister(&proxy_module, "mod_proxy.data-read",
proxy_dirlist_data_ev);
if (proxy_ftp_dirlist_finish(proxy_sess) < 0) {
/* TODO: What to do if this fails? */
}
errno = xerrno;
return mr;
}
MODRET proxy_eprt(cmd_rec *cmd, struct proxy_session *proxy_sess) {
int res, xerrno;
const pr_netaddr_t *remote_addr = NULL;
config_rec *c;
unsigned short remote_port;
int allow_foreign_address = FALSE;
CHECK_CMD_ARGS(cmd, 2);
/* We can't just send the frontend's EPRT, as is, to the backend.
* We need to connect to the frontend's EPRT; we need to open a listening
* socket and send its address to the backend in our EPRT command.
*/
/* XXX How to handle this if we are chrooted, without root privs, for
* e.g. source ports below 1024?
*/
remote_addr = proxy_ftp_msg_parse_ext_addr(proxy_sess->dataxfer_pool,
cmd->argv[1], session.c->remote_addr, cmd->cmd_id, NULL);
if (remote_addr == NULL) {
xerrno = errno;
pr_trace_msg(trace_channel, 2, "error parsing EPRT command '%s': %s",
(char *) cmd->argv[1], strerror(xerrno));
if (xerrno == EPROTOTYPE) {
#ifdef PR_USE_IPV6
if (pr_netaddr_use_ipv6()) {
pr_response_add_err(R_522,
_("Network protocol not supported, use (1,2)"));
} else {
pr_response_add_err(R_522,
_("Network protocol not supported, use (1)"));
}
#else
pr_response_add_err(R_522, _("Network protocol not supported, use (1)"));
#endif /* PR_USE_IPV6 */
} else {
pr_response_add_err(R_501, _("Illegal EPRT command"));
}
pr_response_flush(&resp_err_list);
errno = xerrno;
return PR_ERROR(cmd);
}
remote_port = ntohs(pr_netaddr_get_port(remote_addr));
/* If we are NOT listening on an RFC1918 address, BUT the client HAS
* sent us an RFC1918 address in its EPRT command (which we know to not be
* routable), then ignore that address, and use the client's remote address.
*/
if (pr_netaddr_is_rfc1918(session.c->local_addr) != TRUE &&
pr_netaddr_is_rfc1918(session.c->remote_addr) != TRUE &&
pr_netaddr_is_rfc1918(remote_addr) == TRUE) {
const char *rfc1918_ipstr;
rfc1918_ipstr = pr_netaddr_get_ipstr(remote_addr);
remote_addr = pr_netaddr_dup(session.pool, session.c->remote_addr);
/* Make sure the remote port is set on our duplicated netaddr, too
* (Issue #158).
*/
pr_netaddr_set_port2((pr_netaddr_t *) remote_addr, remote_port);
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"client sent RFC1918 address '%s' in EPRT command, ignoring it and "
"using '%s'", rfc1918_ipstr, pr_netaddr_get_ipstr(remote_addr));
}
/* Make sure that the address specified matches the address from which
* the control connection is coming.
*/
c = find_config(main_server->conf, CONF_PARAM, "AllowForeignAddress",
FALSE);
if (c != NULL) {
int allowed;
allowed = *((int *) c->argv[0]);
switch (allowed) {
case TRUE:
allow_foreign_address = TRUE;
break;
case FALSE:
break;
default: {
char *class_name;
const pr_class_t *cls;
class_name = c->argv[1];
cls = pr_class_find(class_name);
if (cls != NULL) {
if (pr_class_satisfied(cmd->tmp_pool, cls, remote_addr) == TRUE) {
allow_foreign_address = TRUE;
} else {
pr_log_debug(DEBUG8, " '%s' not satisfied by foreign "
"address '%s'", class_name, pr_netaddr_get_ipstr(remote_addr));
}
} else {
pr_log_debug(DEBUG8, " '%s' not found for filtering "
"AllowForeignAddress", class_name);
}
}
}
}
if (allow_foreign_address == FALSE) {
if (pr_netaddr_cmp(remote_addr, session.c->remote_addr) != 0 ||
!remote_port) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"Refused EPRT %s (address mismatch)", cmd->arg);
pr_response_add_err(R_500, _("Illegal EPRT command"));
pr_response_flush(&resp_err_list);
errno = EPERM;
return PR_ERROR(cmd);
}
}
/* Additionally, make sure that the port number used is a "high numbered"
* port, to avoid bounce attacks. For remote Windows machines, the
* port numbers mean little. However, there are also quite a few Unix
* machines out there for whom the port number matters...
*/
if (remote_port < 1024) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"Refused EPRT %s (port %d below 1024, possible bounce attack)", cmd->arg,
remote_port);
pr_response_add_err(R_500, _("Illegal EPRT command"));
pr_response_flush(&resp_err_list);
errno = EPERM;
return PR_ERROR(cmd);
}
proxy_sess->frontend_data_addr = remote_addr;
switch (proxy_sess->dataxfer_policy) {
case PR_CMD_PASV_ID:
case PR_CMD_EPSV_ID: {
const pr_netaddr_t *addr;
pr_response_t *resp;
unsigned int resp_nlines = 0;
addr = proxy_ftp_xfer_prepare_passive(proxy_sess->dataxfer_policy, cmd,
R_500, proxy_sess, 0);
if (addr == NULL) {
return PR_ERROR(cmd);
}
resp = palloc(cmd->tmp_pool, sizeof(pr_response_t));
resp->num = R_200;
resp->msg = _("EPRT command successful");
resp_nlines = 1;
res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool,
proxy_sess->frontend_ctrl_conn, resp, resp_nlines);
if (res < 0) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error sending '%s %s' response to frontend: %s", resp->num,
resp->msg, strerror(xerrno));
errno = xerrno;
return PR_ERROR(cmd);
}
proxy_sess->backend_data_addr = addr;
proxy_sess->backend_sess_flags |= SF_PASSIVE;
break;
}
case PR_CMD_PORT_ID:
case PR_CMD_EPRT_ID:
default: {
pr_response_t *resp;
unsigned int resp_nlines = 0;
res = proxy_ftp_xfer_prepare_active(proxy_sess->dataxfer_policy, cmd,
R_425, proxy_sess, 0);
if (res < 0) {
return PR_ERROR(cmd);
}
resp = palloc(cmd->tmp_pool, sizeof(pr_response_t));
resp->num = R_200;
resp->msg = _("EPRT command successful");
resp_nlines = 1;
res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool,
proxy_sess->frontend_ctrl_conn, resp, resp_nlines);
if (res < 0) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error sending '%s %s' response to frontend: %s", resp->num,
resp->msg, strerror(xerrno));
errno = xerrno;
return PR_ERROR(cmd);
}
proxy_sess->backend_sess_flags |= SF_PORT;
break;
}
}
/* If the command was successful, mark it in the session state/flags. */
proxy_sess->frontend_sess_flags |= SF_PORT;
return PR_HANDLED(cmd);
}
MODRET proxy_epsv(cmd_rec *cmd, struct proxy_session *proxy_sess) {
int res, xerrno;
conn_t *data_conn;
const char *epsv_msg;
char resp_msg[PR_RESPONSE_BUFFER_SIZE];
const pr_netaddr_t *bind_addr, *remote_addr;
pr_response_t *resp;
unsigned int resp_nlines = 1;
/* TODO: Handle any possible EPSV params, e.g. "EPSV ALL", properly. */
switch (proxy_sess->dataxfer_policy) {
case PR_CMD_PORT_ID:
case PR_CMD_EPRT_ID:
res = proxy_ftp_xfer_prepare_active(proxy_sess->dataxfer_policy, cmd,
R_425, proxy_sess, 0);
if (res < 0) {
return PR_ERROR(cmd);
}
proxy_sess->backend_sess_flags |= SF_PORT;
break;
case PR_CMD_PASV_ID:
case PR_CMD_EPSV_ID:
default:
remote_addr = proxy_ftp_xfer_prepare_passive(proxy_sess->dataxfer_policy,
cmd, R_500, proxy_sess, 0);
if (remote_addr == NULL) {
return PR_ERROR(cmd);
}
proxy_sess->backend_data_addr = remote_addr;
proxy_sess->backend_sess_flags |= SF_PASSIVE;
break;
}
/* We do NOT want to connect here, but would rather wait until the
* ensuing data transfer-initiating command. Otherwise, a client could
* spew PASV commands at us, and we would flood the backend server with
* data transfer connections needlessly.
*
* We DO, however, need to create our own listening connection, so that
* we can inform the client of the address/port to which IT is to
* connect for its part of the data transfer.
*
* Note that we do NOT use the ProxySourceAddress here, since this listening
* connection is for the frontend client.
*/
if (pr_netaddr_get_family(session.c->local_addr) == pr_netaddr_get_family(session.c->remote_addr)) {
bind_addr = session.c->local_addr;
} else {
/* In this scenario, the server has an IPv6 socket, but the remote client
* is an IPv4 (or IPv4-mapped IPv6) peer.
*/
bind_addr = pr_netaddr_v6tov4(cmd->pool, session.c->local_addr);
}
/* PassivePorts is handled by proxy_ftp_conn_listen(). */
data_conn = proxy_ftp_conn_listen(cmd->pool, bind_addr, FALSE);
if (data_conn == NULL) {
xerrno = errno;
proxy_inet_close(session.pool, proxy_sess->backend_data_conn);
pr_inet_close(session.pool, proxy_sess->backend_data_conn);
proxy_sess->backend_data_conn = NULL;
pr_response_add_err(R_425,
_("Unable to build data connection: Internal error"));
pr_response_flush(&resp_err_list);
errno = xerrno;
return PR_ERROR(cmd);
}
if (proxy_sess->frontend_data_conn != NULL) {
/* Make sure that we only have one frontend data connection. */
pr_inet_close(session.pool, proxy_sess->frontend_data_conn);
proxy_sess->frontend_data_conn = session.d = NULL;
}
proxy_sess->frontend_data_conn = session.d = data_conn;
epsv_msg = proxy_ftp_msg_fmt_ext_addr(cmd->tmp_pool, data_conn->local_addr,
data_conn->local_port, cmd->cmd_id, TRUE);
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"Entering Extended Passive Mode (%s)", epsv_msg);
/* Change the response to send back to the connecting client, telling it
* to use OUR address/port.
*/
resp = palloc(cmd->tmp_pool, sizeof(pr_response_t));
resp->num = R_229;
memset(resp_msg, '\0', sizeof(resp_msg));
snprintf(resp_msg, sizeof(resp_msg)-1, "Entering Extended Passive Mode (%s)",
epsv_msg);
resp->msg = pstrdup(cmd->tmp_pool, resp_msg);
res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool, proxy_sess->frontend_ctrl_conn,
resp, resp_nlines);
if (res < 0) {
xerrno = errno;
proxy_inet_close(session.pool, proxy_sess->backend_data_conn);
pr_inet_close(session.pool, proxy_sess->backend_data_conn);
proxy_sess->backend_data_conn = NULL;
proxy_inet_close(session.pool, data_conn);
pr_inet_close(session.pool, data_conn);
pr_response_block(TRUE);
pr_response_add_err(R_500, _("%s: %s"), (char *) cmd->argv[0],
strerror(xerrno));
pr_response_flush(&resp_err_list);
errno = xerrno;
return PR_ERROR(cmd);
}
proxy_sess->frontend_sess_flags |= SF_PASSIVE;
return PR_HANDLED(cmd);
}
MODRET proxy_pasv(cmd_rec *cmd, struct proxy_session *proxy_sess) {
int res, xerrno;
conn_t *data_conn;
const char *pasv_msg;
char resp_msg[PR_RESPONSE_BUFFER_SIZE];
const pr_netaddr_t *bind_addr, *remote_addr;
pr_response_t *resp;
unsigned int resp_nlines = 1;
switch (proxy_sess->dataxfer_policy) {
case PR_CMD_PORT_ID:
case PR_CMD_EPRT_ID:
res = proxy_ftp_xfer_prepare_active(proxy_sess->dataxfer_policy, cmd,
R_425, proxy_sess, 0);
if (res < 0) {
return PR_ERROR(cmd);
}
proxy_sess->backend_sess_flags |= SF_PORT;
break;
case PR_CMD_PASV_ID:
case PR_CMD_EPSV_ID:
default:
remote_addr = proxy_ftp_xfer_prepare_passive(proxy_sess->dataxfer_policy,
cmd, R_500, proxy_sess, 0);
if (remote_addr == NULL) {
return PR_ERROR(cmd);
}
proxy_sess->backend_data_addr = remote_addr;
proxy_sess->backend_sess_flags |= SF_PASSIVE;
break;
}
/* We do NOT want to connect here, but would rather wait until the
* ensuing data transfer-initiating command. Otherwise, a client could
* spew PASV commands at us, and we would flood the backend server with
* data transfer connections needlessly.
*
* We DO, however, need to create our own listening connection, so that
* we can inform the client of the address/port to which IT is to
* connect for its part of the data transfer.
*
* Note that we do NOT use the ProxySourceAddress here, since this listening
* connection is for the frontend client.
*/
if (pr_netaddr_get_family(session.c->local_addr) == pr_netaddr_get_family(session.c->remote_addr)) {
#ifdef PR_USE_IPV6
if (pr_netaddr_use_ipv6()) {
/* Make sure that the family is NOT IPv6, even though the family of the
* local and remote ends match. The PASV command cannot be used for
* IPv6 addresses (Bug#3745).
*/
if (pr_netaddr_get_family(session.c->local_addr) == AF_INET6) {
xerrno = EPERM;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"Unable to handle PASV for IPv6 address '%s', rejecting command",
pr_netaddr_get_ipstr(session.c->local_addr));
pr_response_add_err(R_501, "%s: %s", (char *) cmd->argv[0],
strerror(xerrno));
pr_cmd_set_errno(cmd, xerrno);
errno = xerrno;
return PR_ERROR(cmd);
}
}
#endif /* PR_USE_IPV6 */
bind_addr = session.c->local_addr;
} else {
bind_addr = pr_netaddr_v6tov4(cmd->pool, session.c->local_addr);
}
/* PassivePorts is handled by proxy_ftp_conn_listen(). */
data_conn = proxy_ftp_conn_listen(cmd->pool, bind_addr, TRUE);
if (data_conn == NULL) {
xerrno = errno;
proxy_inet_close(session.pool, proxy_sess->backend_data_conn);
pr_inet_close(session.pool, proxy_sess->backend_data_conn);
proxy_sess->backend_data_conn = NULL;
pr_response_add_err(R_425,
_("Unable to build data connection: Internal error"));
pr_response_flush(&resp_err_list);
errno = xerrno;
return PR_ERROR(cmd);
}
if (proxy_sess->frontend_data_conn != NULL) {
/* Make sure that we only have one frontend data connection. */
pr_inet_close(session.pool, proxy_sess->frontend_data_conn);
proxy_sess->frontend_data_conn = session.d = NULL;
}
proxy_sess->frontend_data_conn = session.d = data_conn;
pasv_msg = proxy_ftp_msg_fmt_addr(cmd->tmp_pool, data_conn->local_addr,
data_conn->local_port, TRUE);
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"Entering Passive Mode (%s).", pasv_msg);
/* Change the response to send back to the connecting client, telling it
* to use OUR address/port.
*/
resp = palloc(cmd->tmp_pool, sizeof(pr_response_t));
resp->num = R_227;
memset(resp_msg, '\0', sizeof(resp_msg));
snprintf(resp_msg, sizeof(resp_msg)-1, "Entering Passive Mode (%s).",
pasv_msg);
resp->msg = pstrdup(cmd->tmp_pool, resp_msg);
res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool, proxy_sess->frontend_ctrl_conn,
resp, resp_nlines);
if (res < 0) {
xerrno = errno;
proxy_inet_close(session.pool, proxy_sess->backend_data_conn);
pr_inet_close(session.pool, proxy_sess->backend_data_conn);
proxy_sess->backend_data_conn = NULL;
pr_inet_close(session.pool, data_conn);
proxy_sess->frontend_data_conn = session.d = NULL;
pr_response_block(TRUE);
pr_response_add_err(R_500, _("%s: %s"), (char *) cmd->argv[0],
strerror(xerrno));
pr_response_flush(&resp_err_list);
errno = xerrno;
return PR_ERROR(cmd);
}
proxy_sess->frontend_sess_flags |= SF_PASSIVE;
return PR_HANDLED(cmd);
}
MODRET proxy_port(cmd_rec *cmd, struct proxy_session *proxy_sess) {
int res, xerrno;
const pr_netaddr_t *remote_addr = NULL;
unsigned short remote_port;
config_rec *c;
int allow_foreign_address = FALSE;
CHECK_CMD_ARGS(cmd, 2);
/* We can't just send the frontend's PORT, as is, to the backend.
* We need to connect to the frontend's PORT; we need to open a listening
* socket and send its address to the backend in our PORT command.
*/
/* XXX How to handle this if we are chrooted, without root privs, for
* e.g. source ports below 1024?
*/
remote_addr = proxy_ftp_msg_parse_addr(proxy_sess->dataxfer_pool,
cmd->argv[1], pr_netaddr_get_family(session.c->remote_addr));
if (remote_addr == NULL) {
xerrno = errno;
pr_trace_msg("proxy", 2, "error parsing PORT command '%s': %s",
(char *) cmd->argv[1], strerror(xerrno));
pr_response_add_err(R_501, _("Illegal PORT command"));
pr_response_flush(&resp_err_list);
errno = xerrno;
return PR_ERROR(cmd);
}
remote_port = ntohs(pr_netaddr_get_port(remote_addr));
/* If we are NOT listening on an RFC1918 address, BUT the client HAS
* sent us an RFC1918 address in its PORT command (which we know to not be
* routable), then ignore that address, and use the client's remote address.
*/
if (pr_netaddr_is_rfc1918(session.c->local_addr) != TRUE &&
pr_netaddr_is_rfc1918(session.c->remote_addr) != TRUE &&
pr_netaddr_is_rfc1918(remote_addr) == TRUE) {
const char *rfc1918_ipstr;
rfc1918_ipstr = pr_netaddr_get_ipstr(remote_addr);
remote_addr = pr_netaddr_dup(session.pool, session.c->remote_addr);
/* Make sure the remote port is set on our duplicated netaddr, too
* (Issue #158).
*/
pr_netaddr_set_port2((pr_netaddr_t *) remote_addr, remote_port);
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"client sent RFC1918 address '%s' in PORT command, ignoring it and "
"using '%s'", rfc1918_ipstr, pr_netaddr_get_ipstr(remote_addr));
}
/* Make sure that the address specified matches the address from which
* the control connection is coming.
*/
c = find_config(TOPLEVEL_CONF, CONF_PARAM, "AllowForeignAddress", FALSE);
if (c != NULL) {
int allowed;
allowed = *((int *) c->argv[0]);
switch (allowed) {
case TRUE:
allow_foreign_address = TRUE;
break;
case FALSE:
break;
default: {
char *class_name;
const pr_class_t *cls;
class_name = c->argv[1];
cls = pr_class_find(class_name);
if (cls != NULL) {
if (pr_class_satisfied(cmd->tmp_pool, cls, remote_addr) == TRUE) {
allow_foreign_address = TRUE;
} else {
pr_log_debug(DEBUG8, " '%s' not satisfied by foreign "
"address '%s'", class_name, pr_netaddr_get_ipstr(remote_addr));
}
} else {
pr_log_debug(DEBUG8, " '%s' not found for filtering "
"AllowForeignAddress", class_name);
}
}
}
}
if (allow_foreign_address == FALSE) {
#ifdef PR_USE_IPV6
if (pr_netaddr_use_ipv6()) {
/* We can only compare the PORT-given address against the remote client
* address if the remote client address is an IPv4-mapped IPv6 address.
*/
if (pr_netaddr_get_family(session.c->remote_addr) == AF_INET6 &&
pr_netaddr_is_v4mappedv6(session.c->remote_addr) != TRUE) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"Refused PORT %s (IPv4/IPv6 address mismatch)", cmd->arg);
pr_response_add_err(R_500, _("Illegal PORT command"));
pr_response_flush(&resp_err_list);
errno = EPERM;
return PR_ERROR(cmd);
}
}
#endif /* PR_USE_IPV6 */
if (pr_netaddr_cmp(remote_addr, session.c->remote_addr) != 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"Refused PORT %s (address mismatch)", cmd->arg);
pr_response_add_err(R_500, _("Illegal PORT command"));
pr_response_flush(&resp_err_list);
errno = EPERM;
return PR_ERROR(cmd);
}
}
/* Additionally, make sure that the port number used is a "high numbered"
* port, to avoid bounce attacks. For remote Windows machines, the
* port numbers mean little. However, there are also quite a few Unix
* machines out there for whom the port number matters...
*/
if (remote_port < 1024) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"Refused PORT %s (port %d below 1024, possible bounce attack)", cmd->arg,
remote_port);
pr_response_add_err(R_500, _("Illegal PORT command"));
pr_response_flush(&resp_err_list);
errno = EPERM;
return PR_ERROR(cmd);
}
proxy_sess->frontend_data_addr = remote_addr;
switch (proxy_sess->dataxfer_policy) {
case PR_CMD_PASV_ID:
case PR_CMD_EPSV_ID: {
const pr_netaddr_t *addr;
pr_response_t *resp;
unsigned int resp_nlines = 0;
addr = proxy_ftp_xfer_prepare_passive(proxy_sess->dataxfer_policy, cmd,
R_500, proxy_sess, 0);
if (addr == NULL) {
return PR_ERROR(cmd);
}
resp = palloc(cmd->tmp_pool, sizeof(pr_response_t));
resp->num = R_200;
resp->msg = _("PORT command successful");
resp_nlines = 1;
res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool,
proxy_sess->frontend_ctrl_conn, resp, resp_nlines);
if (res < 0) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error sending '%s %s' response to frontend: %s", resp->num,
resp->msg, strerror(xerrno));
errno = xerrno;
return PR_ERROR(cmd);
}
proxy_sess->backend_data_addr = addr;
proxy_sess->backend_sess_flags |= SF_PASSIVE;
break;
}
case PR_CMD_PORT_ID:
case PR_CMD_EPRT_ID:
default: {
pr_response_t *resp;
unsigned int resp_nlines = 0;
res = proxy_ftp_xfer_prepare_active(proxy_sess->dataxfer_policy, cmd,
R_425, proxy_sess, 0);
if (res < 0) {
return PR_ERROR(cmd);
}
resp = palloc(cmd->tmp_pool, sizeof(pr_response_t));
resp->num = R_200;
resp->msg = _("PORT command successful");
resp_nlines = 1;
res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool,
proxy_sess->frontend_ctrl_conn, resp, resp_nlines);
if (res < 0) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error sending '%s %s' response to frontend: %s", resp->num,
resp->msg, strerror(xerrno));
errno = xerrno;
return PR_ERROR(cmd);
}
proxy_sess->backend_sess_flags |= SF_PORT;
break;
}
}
/* If the command was successful, mark it in the session state/flags. */
proxy_sess->frontend_sess_flags |= SF_PORT;
return PR_HANDLED(cmd);
}
MODRET proxy_feat(cmd_rec *cmd, struct proxy_session *proxy_sess) {
modret_t *mr = NULL;
pr_response_t *resp = NULL;
mr = proxy_cmd(cmd, proxy_sess, &resp);
/* If we don't already have our backend feature table allocated, as
* when the backend server won't support the FEAT command until AFTER
* authentication occurs, then try to piggyback on the frontend client's
* FEAT command, and fill our table now.
*/
if (proxy_sess->backend_features == NULL) {
if (MODRET_ISHANDLED(mr) &&
resp != NULL) {
const char *feat_crlf = "\r\n";
char *feats, *token;
size_t token_len = 0;
pr_trace_msg(trace_channel, 9,
"populating backend features based on FEAT response to frontend "
"client");
proxy_sess->backend_features = pr_table_nalloc(proxy_pool, 0, 4);
feats = pstrdup(cmd->tmp_pool, resp->msg);
token = pr_str_get_token2(&feats, (char *) feat_crlf, &token_len);
while (token != NULL) {
pr_signals_handle();
if (token_len > 0) {
/* The FEAT response lines in which we are interested all start with
* a single space, per RFC spec. Ignore any other lines.
*/
if (token[0] == ' ') {
char *key, *val, *ptr;
/* Find the next space in the string, to delimit our key/value
* pairs.
*/
ptr = strchr(token + 1, ' ');
if (ptr != NULL) {
key = pstrndup(proxy_pool, token + 1, ptr - token - 1);
val = pstrdup(proxy_pool, ptr + 1);
} else {
key = pstrdup(proxy_pool, token + 1);
val = pstrdup(proxy_pool, "");
}
pr_table_add(proxy_sess->backend_features, key, val, 0);
}
}
feats = token + token_len + 1;
token = pr_str_get_token2(&feats, (char *) feat_crlf, &token_len);
}
}
}
return mr;
}
static void proxy_login_failed(void) {
unsigned int max_logins = PROXY_SESS_MAX_LOGIN_ATTEMPTS;
config_rec *c;
c = find_config(main_server->conf, CONF_PARAM, "MaxLoginAttempts", FALSE);
if (c != NULL) {
max_logins = *((unsigned int *) c->argv[0]);
}
if (max_logins > 0 &&
++proxy_login_attempts >= max_logins) {
/* Generate an event for the benefit of modules like mod_ban and mod_snmp.
*/
pr_event_generate("mod_auth.max-login-attempts", session.c);
}
}
MODRET proxy_user(cmd_rec *cmd, struct proxy_session *proxy_sess,
int *block_responses) {
int successful = FALSE, res = 0, xerrno = 0;
/* Remove any exit handlers installed by mod_xfer. We do this here,
* rather than in sess_init, since our sess_init is called BEFORE the
* sess_init of mod_xfer.
*/
pr_event_unregister(&xfer_module, "core.exit", NULL);
if (proxy_sess_state & PROXY_SESS_STATE_BACKEND_AUTHENTICATED) {
/* If we've already authenticated, then let the backend server deal
* with this.
*/
return proxy_cmd(cmd, proxy_sess, NULL);
}
/* We handle errors differently for the roles.
*
* In the forward proxy case, the client knows _a priori_ that it is
* connecting through a proxy. In this case, we can relay more of the
* errors to the client.
*
* In the reverse proxy case, though, we want to hide such errors from
* the oblivious client, and not leak any more information than necessary.
*/
switch (proxy_role) {
case PROXY_ROLE_REVERSE:
res = proxy_reverse_handle_user(cmd, proxy_sess, &successful,
block_responses);
xerrno = errno;
pr_response_add_err(R_530, _("Login incorrect."));
break;
case PROXY_ROLE_FORWARD:
res = proxy_forward_handle_user(cmd, proxy_sess, &successful,
block_responses);
xerrno = errno;
if (xerrno != EINVAL) {
pr_response_add_err(R_500, _("%s: %s"), (char *) cmd->argv[0],
strerror(xerrno));
} else {
pr_response_add_err(R_530, _("Login incorrect."));
}
break;
}
if (res < 0) {
pr_response_flush(&resp_err_list);
errno = xerrno;
return PR_ERROR(cmd);
}
if (successful == TRUE) {
config_rec *c;
const char *notes_key = "mod_auth.orig-user";
char *user;
/* For 2xx/3xx responses (others?), stash the user name appropriately. */
user = cmd->arg;
(void) pr_table_remove(session.notes, notes_key, NULL);
if (pr_table_add_dup(session.notes, notes_key, user, 0) < 0) {
pr_log_debug(DEBUG3, "error stashing '%s' in session.notes: %s",
notes_key, strerror(errno));
}
/* Handle DefaultTransferMode here. */
c = find_config(main_server->conf, CONF_PARAM, "DefaultTransferMode",
FALSE);
if (c != NULL) {
if (strncasecmp(c->argv[0], "binary", 7) == 0) {
session.sf_flags &= (SF_ALL^SF_ASCII);
} else {
session.sf_flags |= SF_ASCII;
}
} else {
/* ASCII by default. */
session.sf_flags |= SF_ASCII;
}
} else {
if (res == 1) {
/* If we haven't been marked as successful, BUT the return value is 1,
* then use this as an indicator that an error response was sent already
* to the client.
*/
errno = xerrno;
return PR_ERROR(cmd);
}
}
if (res == 0) {
return PR_DECLINED(cmd);
}
return PR_HANDLED(cmd);
}
MODRET proxy_pass(cmd_rec *cmd, struct proxy_session *proxy_sess,
int *block_responses) {
int successful = FALSE, res = 0;
/* It's possible that the client only sent a PASS command with no arguments,
* effectively a blank/missing password. Some other FTP servers may not
* handle this as well as ProFTPD does.
*/
if (cmd->argc == 1) {
cmd_rec *new_cmd;
new_cmd = pr_cmd_alloc(cmd->pool, 2, C_PASS, pstrdup(cmd->pool, ""));
new_cmd->arg = pstrdup(new_cmd->pool, "");
cmd = new_cmd;
}
if (proxy_sess_state & PROXY_SESS_STATE_BACKEND_AUTHENTICATED) {
/* If we've already authenticated, then let the backend server deal with
* this.
*/
return proxy_cmd(cmd, proxy_sess, NULL);
}
switch (proxy_role) {
case PROXY_ROLE_REVERSE:
res = proxy_reverse_handle_pass(cmd, proxy_sess, &successful,
block_responses);
break;
case PROXY_ROLE_FORWARD:
res = proxy_forward_handle_pass(cmd, proxy_sess, &successful,
block_responses);
break;
}
if (res < 0) {
int xerrno = errno;
if (xerrno == ECONNRESET ||
xerrno == ECONNABORTED ||
xerrno == EPIPE) {
/* This indicates that the backend server closed the control
* connection on us. Given that, the only thing we can do is to close
* the frontend connection in turn.
*/
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"lost backend connection (%s) handling %s command", strerror(xerrno),
(char *) cmd->argv[0]);
pr_response_add_err(R_530, _("Login incorrect."));
pr_response_flush(&resp_err_list);
pr_session_disconnect(&proxy_module,
PR_SESS_DISCONNECT_BY_APPLICATION,
"Backend control connection lost");
}
if (xerrno != EINVAL) {
pr_response_add_err(R_500, _("%s: %s"), (char *) cmd->argv[0],
strerror(xerrno));
} else {
pr_response_add_err(R_530, _("Login incorrect."));
}
pr_response_flush(&resp_err_list);
proxy_login_failed();
errno = xerrno;
return PR_ERROR(cmd);
}
if (successful == TRUE) {
const char *user;
int proxy_auth = FALSE;
user = pr_table_get(session.notes, "mod_auth.orig-user", NULL);
session.user = user;
switch (proxy_role) {
case PROXY_ROLE_FORWARD:
proxy_auth = proxy_forward_use_proxy_auth();
break;
case PROXY_ROLE_REVERSE:
proxy_auth = proxy_reverse_use_proxy_auth();
break;
}
/* Unless proxy auth happened, we set the session groups to null,
* to avoid unexpected behavior when looking up e.g. sections.
*/
if (proxy_auth == FALSE) {
if (session.group != NULL) {
pr_trace_msg(trace_channel, 9,
"clearing unauthenticated primary group name '%s' for user '%s'",
session.group, session.user);
session.group = NULL;
}
if (session.groups != NULL) {
if (session.groups->nelts > 0) {
register unsigned int i;
pr_trace_msg(trace_channel, 9,
"clearing %d unauthenticated additional group %s for user '%s':",
session.groups->nelts,
session.groups->nelts != 1 ? "names" : "name", session.user);
for (i = 0; i < session.groups->nelts; i++) {
pr_trace_msg(trace_channel, 9,
" clearing additional group name '%s'",
((char **) session.groups->elts)[i]);
}
}
session.groups = NULL;
}
}
/* XXX Do we need to set other login-related fields here? E.g.
* session.uid, session.gid, etc?
*/
fixup_dirs(main_server, CF_DEFER);
if (proxy_role == PROXY_ROLE_FORWARD) {
proxy_restrict_session();
}
} else {
proxy_login_failed();
return PR_ERROR(cmd);
}
return PR_HANDLED(cmd);
}
MODRET proxy_type(cmd_rec *cmd, struct proxy_session *proxy_sess) {
int res, xerrno;
pr_response_t *resp;
unsigned int resp_nlines = 0;
res = proxy_ftp_ctrl_send_cmd(cmd->tmp_pool, proxy_sess->backend_ctrl_conn,
cmd);
if (res < 0) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error sending %s to backend: %s", (char *) cmd->argv[0],
strerror(xerrno));
pr_response_add_err(R_500, _("%s: %s"), (char *) cmd->argv[0],
strerror(xerrno));
pr_response_flush(&resp_err_list);
errno = xerrno;
return PR_ERROR(cmd);
}
resp = proxy_ftp_ctrl_recv_resp(cmd->tmp_pool, proxy_sess->backend_ctrl_conn,
&resp_nlines, 0);
if (resp == NULL) {
xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error receiving %s response from backend: %s", (char *) cmd->argv[0],
strerror(xerrno));
pr_response_add_err(R_500, _("%s: %s"), (char *) cmd->argv[0],
strerror(xerrno));
pr_response_flush(&resp_err_list);
errno = xerrno;
return PR_ERROR(cmd);
}
if (resp->num[0] == '2') {
char *type;
/* This code is duplicated from mod_xfer.c#xfer_type(). Would be nice
* to factor it out somewhere reusable, i.e. some pr_str_ function.
*/
type = pstrdup(cmd->tmp_pool, cmd->argv[1]);
type[0] = toupper(type[0]);
if (strcmp(type, "A") == 0 ||
(cmd->argc == 3 &&
strcmp(type, "L") == 0 &&
strcmp(cmd->argv[2], "7") == 0)) {
/* TYPE A(SCII) or TYPE L 7. */
session.sf_flags |= SF_ASCII;
} else if (strcmp(type, "I") == 0 ||
(cmd->argc == 3 &&
strcmp(type, "L") == 0 &&
strcmp(cmd->argv[2], "8") == 0)) {
/* TYPE I(MAGE) or TYPE L 8. */
session.sf_flags &= (SF_ALL^(SF_ASCII|SF_ASCII_OVERRIDE));
}
}
res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool, proxy_sess->frontend_ctrl_conn,
resp, resp_nlines);
if (res < 0) {
xerrno = errno;
pr_response_block(TRUE);
errno = xerrno;
return PR_ERROR(cmd);
}
return PR_HANDLED(cmd);
}
static int proxy_get_cmd_group(cmd_rec *cmd) {
cmdtable *cmdtab;
int idx;
unsigned int h;
idx = cmd->stash_index;
h = cmd->stash_hash;
cmdtab = pr_stash_get_symbol2(PR_SYM_CMD, cmd->argv[0], NULL, &idx, &h);
while (cmdtab != NULL) {
pr_signals_handle();
if (cmdtab->group == NULL ||
cmdtab->cmd_type != CMD) {
cmdtab = pr_stash_get_symbol2(PR_SYM_CMD, cmd->argv[0], cmdtab, &idx, &h);
continue;
}
cmd->group = pstrdup(cmd->pool, cmdtab->group);
return 0;
}
/* Note that some commands legitimately have no group (G_NONE is NULL), thus
* the absense of a group could simply indicate G_NONE.
*/
if (cmd->group == NULL) {
pr_trace_msg(trace_channel, 15,
"found group 'NONE' for command '%s'", (char *) cmd->argv[0]);
}
return 0;
}
static int proxy_have_limit(cmd_rec *cmd, const char **resp_code) {
int res;
/* Some commands get a free pass. */
switch (cmd->cmd_id) {
case PR_CMD_ACCT_ID:
case PR_CMD_EPRT_ID:
case PR_CMD_EPSV_ID:
case PR_CMD_FEAT_ID:
case PR_CMD_PASS_ID:
case PR_CMD_PASV_ID:
case PR_CMD_PORT_ID:
case PR_CMD_QUIT_ID:
case PR_CMD_SYST_ID:
case PR_CMD_USER_ID:
return 0;
default:
break;
}
/* Note: since we use a PRE_CMD ANY handler here, the core code does NOT
* actually look up the specific records for this command. This means that
* that the command's command group may not be known. But to honor any
* group-based sections, we need to look up the command group.
*/
if (cmd->group == NULL) {
if (proxy_get_cmd_group(cmd) < 0) {
pr_trace_msg(trace_channel, 5,
"error finding group for command '%s': %s", (char *) cmd->argv[0],
strerror(errno));
}
}
res = dir_check(cmd->tmp_pool, cmd, cmd->group, session.cwd, NULL);
if (res == 0) {
/* The appropriate response code depends on the command, unfortunately.
* See RFC 959, Section 5.4 for the gory details.
*/
switch (cmd->cmd_id) {
case PR_CMD_ALLO_ID:
case PR_CMD_MODE_ID:
case PR_CMD_REST_ID:
case PR_CMD_STRU_ID:
case PR_CMD_TYPE_ID:
*resp_code = R_501;
break;
default:
*resp_code = R_550;
break;
}
errno = EPERM;
return -1;
}
return 0;
}
MODRET proxy_any(cmd_rec *cmd) {
int block_responses = TRUE;
struct proxy_session *proxy_sess;
modret_t *mr = NULL;
const char *resp_code = R_550;
if (proxy_engine == FALSE) {
return PR_DECLINED(cmd);
}
/* Honor any sections for this comand. */
if (proxy_have_limit(cmd, &resp_code) < 0) {
int xerrno = errno;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"%s denied by configuration", (char *) cmd->argv[0]);
pr_response_add_err(resp_code, "%s: %s", (char *) cmd->argv[0],
strerror(xerrno));
pr_cmd_set_errno(cmd, xerrno);
errno = xerrno;
return PR_ERROR(cmd);
}
proxy_sess = (struct proxy_session *) pr_table_get(session.notes,
"mod_proxy.proxy-session", NULL);
if (proxy_sess == NULL) {
/* Unlikely to occur. */
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "%s",
"missing proxy session unexpectedly");
return PR_DECLINED(cmd);
}
/* Backend servers can send "asynchronous" messages to us; we need to check
* for them.
*/
if (proxy_sess->backend_ctrl_conn != NULL) {
if (proxy_ftp_ctrl_handle_async(cmd->tmp_pool,
proxy_sess->backend_ctrl_conn, proxy_sess->frontend_ctrl_conn, 0) < 0) {
int xerrno = errno;
pr_trace_msg(trace_channel, 7,
"error checking for async messages from the backend server: %s",
strerror(xerrno));
if (xerrno == ECONNRESET ||
xerrno == ECONNABORTED ||
xerrno == ENOENT ||
xerrno == EPIPE) {
pr_session_disconnect(&proxy_module,
PR_SESS_DISCONNECT_BY_APPLICATION, "Backend control connection lost");
}
}
}
pr_response_block(FALSE);
pr_timer_reset(PR_TIMER_IDLE, ANY_MODULE);
/* Commands related to logins and data transfers are handled separately. */
switch (cmd->cmd_id) {
case PR_CMD_USER_ID:
mr = proxy_user(cmd, proxy_sess, &block_responses);
if (block_responses) {
pr_response_block(TRUE);
}
return mr;
case PR_CMD_PASS_ID:
mr = proxy_pass(cmd, proxy_sess, &block_responses);
if (block_responses) {
pr_response_block(TRUE);
}
return mr;
case PR_CMD_EPRT_ID:
if (proxy_sess_state & PROXY_SESS_STATE_CONNECTED) {
if (proxy_opts & PROXY_OPT_USE_DIRECT_DATA_TRANSFERS) {
/* For direct data transfers, we proxy EPRT commands directly to the
* backend server.
*/
break;
}
proxy_session_reset_dataxfer(proxy_sess);
mr = proxy_eprt(cmd, proxy_sess);
pr_response_block(TRUE);
return mr;
} else {
pr_response_send(R_530, _("Access denied"));
return PR_ERROR(cmd);
}
break;
case PR_CMD_EPSV_ID:
if (proxy_sess_state & PROXY_SESS_STATE_CONNECTED) {
if (proxy_opts & PROXY_OPT_USE_DIRECT_DATA_TRANSFERS) {
/* For direct data transfers, we proxy EPSV commands directly to the
* backend server.
*/
break;
}
proxy_session_reset_dataxfer(proxy_sess);
mr = proxy_epsv(cmd, proxy_sess);
pr_response_block(TRUE);
return mr;
} else {
pr_response_send(R_530, _("Access denied"));
return PR_ERROR(cmd);
}
break;
case PR_CMD_PASV_ID:
if (proxy_sess_state & PROXY_SESS_STATE_CONNECTED) {
if (proxy_opts & PROXY_OPT_USE_DIRECT_DATA_TRANSFERS) {
/* For direct data transfers, we proxy PASV commands directly to the
* backend server.
*/
break;
}
proxy_session_reset_dataxfer(proxy_sess);
mr = proxy_pasv(cmd, proxy_sess);
pr_response_block(TRUE);
return mr;
} else {
pr_response_send(R_530, _("Access denied"));
return PR_ERROR(cmd);
}
break;
case PR_CMD_PORT_ID:
if (proxy_sess_state & PROXY_SESS_STATE_CONNECTED) {
if (proxy_opts & PROXY_OPT_USE_DIRECT_DATA_TRANSFERS) {
/* For direct data transfers, we proxy PORT commands directly to the
* backend server.
*/
break;
}
proxy_session_reset_dataxfer(proxy_sess);
mr = proxy_port(cmd, proxy_sess);
pr_response_block(TRUE);
return mr;
} else {
pr_response_send(R_530, _("Access denied"));
return PR_ERROR(cmd);
}
break;
case PR_CMD_TYPE_ID:
if (proxy_sess_state & PROXY_SESS_STATE_CONNECTED) {
/* Used for setting the ASCII/binary session flag properly, e.g. for
* TransferLogs.
*/
mr = proxy_type(cmd, proxy_sess);
pr_response_block(TRUE);
return mr;
}
break;
case PR_CMD_LIST_ID:
case PR_CMD_MLSD_ID:
case PR_CMD_NLST_ID:
if (proxy_sess_state & PROXY_SESS_STATE_CONNECTED) {
if (session.xfer.p != NULL) {
destroy_pool(session.xfer.p);
}
memset(&session.xfer, 0, sizeof(session.xfer));
session.xfer.p = make_sub_pool(session.pool);
pr_pool_tag(session.xfer.p, "proxy session xfer pool");
session.xfer.direction = PR_NETIO_IO_WR;
if (proxy_opts & PROXY_OPT_USE_DIRECT_DATA_TRANSFERS) {
/* For direct data transfers, we proxy data transfer commands
* directly to the backend server. Since data transfer commands
* involve two responsese (the initial 1xx, then the closing 2xx),
* we need to handle them more carefully.
*/
mr = proxy_data_cmd(cmd, proxy_sess);
} else {
mr = proxy_directory_data(proxy_sess, cmd);
}
pr_response_block(TRUE);
return mr;
} else {
pr_response_send(R_530, _("Access denied"));
return PR_ERROR(cmd);
}
break;
case PR_CMD_APPE_ID:
case PR_CMD_RETR_ID:
case PR_CMD_STOR_ID:
case PR_CMD_STOU_ID:
if (proxy_sess_state & PROXY_SESS_STATE_CONNECTED) {
/* In addition to the same setup as for directory listings, we also
* track more things, for supporting e.g. TransferLog.
*/
if (session.xfer.p != NULL) {
destroy_pool(session.xfer.p);
}
memset(&session.xfer, 0, sizeof(session.xfer));
if (pr_cmd_cmp(cmd, PR_CMD_RETR_ID) == 0) {
session.xfer.direction = PR_NETIO_IO_RD;
} else {
session.xfer.direction = PR_NETIO_IO_WR;
}
session.xfer.p = make_sub_pool(session.pool);
pr_pool_tag(session.xfer.p, "proxy session xfer pool");
gettimeofday(&session.xfer.start_time, NULL);
if (proxy_opts & PROXY_OPT_USE_DIRECT_DATA_TRANSFERS) {
/* For direct data transfers, we proxy data transfer commands
* directly to the backend server. Since data transfer commands
* involve two responsese (the initial 1xx, then the closing 2xx),
* we need to handle them more carefully.
*/
mr = proxy_data_cmd(cmd, proxy_sess);
} else {
mr = proxy_data(proxy_sess, cmd);
}
pr_response_block(TRUE);
return mr;
} else {
pr_response_send(R_530, _("Access denied"));
return PR_ERROR(cmd);
}
break;
case PR_CMD_FEAT_ID:
if (proxy_role == PROXY_ROLE_REVERSE) {
/* In reverse proxy mode, we do not want to necessarily leak the
* capabilities of the selected backend server to the client. Or
* do we?
*/
if (proxy_sess_state & PROXY_SESS_STATE_CONNECTED) {
if (proxy_opts & PROXY_OPT_SHOW_FEATURES) {
mr = proxy_feat(cmd, proxy_sess);
return mr;
}
}
return PR_DECLINED(cmd);
} else {
if (proxy_sess_state & PROXY_SESS_STATE_CONNECTED) {
mr = proxy_feat(cmd, proxy_sess);
return mr;
}
}
break;
case PR_CMD_OPTS_ID:
if (cmd->argc == 3 &&
strcasecmp(cmd->argv[1], C_MLST) == 0 &&
proxy_sess->dirlist_policy == PROXY_SESS_DIRECTORY_LIST_POLICY_LIST) {
(void) proxy_ftp_facts_parse_opts(pstrdup(cmd->tmp_pool, cmd->argv[2]));
}
break;
case PR_CMD_HOST_ID:
/* TODO is there any value in handling the HOST command locally?
* Answer: yes! Consider the reverse proxy case + mod_autohost!
* Thus we DO want to return DECLINED here, BUT we ALSO need to implement
* the event listener for resetting the forward/reverse (but not tls)
* APIs.
*/
return PR_DECLINED(cmd);
/* Directory changing commands not allowed locally. */
case PR_CMD_CDUP_ID:
case PR_CMD_CWD_ID:
case PR_CMD_MKD_ID:
case PR_CMD_PWD_ID:
case PR_CMD_RMD_ID:
case PR_CMD_XCUP_ID:
case PR_CMD_XCWD_ID:
case PR_CMD_XMKD_ID:
case PR_CMD_XPWD_ID:
case PR_CMD_XRMD_ID:
if ((proxy_sess_state & PROXY_SESS_STATE_PROXY_AUTHENTICATED) &&
!(proxy_sess_state & PROXY_SESS_STATE_CONNECTED)) {
pr_response_send(R_530, _("Access denied"));
return PR_ERROR(cmd);
}
break;
/* RFC 2228 commands */
case PR_CMD_ADAT_ID:
case PR_CMD_AUTH_ID:
case PR_CMD_CCC_ID:
case PR_CMD_CONF_ID:
case PR_CMD_ENC_ID:
case PR_CMD_MIC_ID:
case PR_CMD_PBSZ_ID:
return PR_DECLINED(cmd);
case PR_CMD_PROT_ID:
/* For a TLS transfer protection policy of "client" (0), we handle the
* PROT command by proxying it to the backend server. For any other
* policy, though, we decline to handle the command from the client
* (and let it dispatch to mod_tls); mod_proxy will send a different
* PROT command to the backend, based on the policy.
*/
if (proxy_tls_xfer_prot_policy != PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_CLIENT) {
return PR_DECLINED(cmd);
}
break;
case PR_CMD_ABOR_ID:
mr = proxy_abort(cmd, proxy_sess, NULL);
if ((proxy_sess->frontend_sess_flags & SF_XFER) ||
(proxy_sess->backend_sess_flags & SF_XFER)) {
pr_trace_msg(trace_channel, 19, "received ABOR on frontend connection, "
"closing frontend data connection");
if (session.d != NULL) {
pr_inet_close(session.pool, proxy_sess->frontend_data_conn);
proxy_sess->frontend_data_conn = session.d = NULL;
}
proxy_sess->frontend_sess_flags &= ~SF_XFER;
proxy_sess->backend_sess_flags &= ~SF_XFER;
}
return mr;
}
/* If we are not connected to a backend server, then don't try to proxy
* the command.
*/
if (!(proxy_sess_state & PROXY_SESS_STATE_CONNECTED)) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"declining to proxy %s command: not connected to backend server",
(char *) cmd->argv[0]);
return PR_DECLINED(cmd);
}
/* XXX Should any other commands, like TYPE or SYST, also be allowed through
* to the backend server, prior to authentication?
*/
if (pr_cmd_cmp(cmd, PR_CMD_QUIT_ID) != 0) {
/* If we have connected to a backend server, but we have NOT authenticated
* to that backend server, then reject all commands as "out of sequence"
* errors (i.e. malicious or misinformed clients).
*/
if (!(proxy_sess_state & PROXY_SESS_STATE_BACKEND_AUTHENTICATED)) {
pr_response_add_err(R_530, _("Please login with USER and PASS"));
return PR_ERROR(cmd);
}
}
return proxy_cmd(cmd, proxy_sess, NULL);
}
MODRET proxy_post_prot(cmd_rec *cmd) {
if (proxy_engine == FALSE) {
return PR_DECLINED(cmd);
}
switch (proxy_tls_xfer_prot_policy) {
case PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_CLEAR:
proxy_tls_set_data_prot(FALSE);
break;
case PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_REQUIRED:
proxy_tls_set_data_prot(TRUE);
break;
case PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_CLIENT:
if (strcasecmp(cmd->arg, "P") == 0) {
proxy_tls_set_data_prot(TRUE);
} else if (strcasecmp(cmd->arg, "C") == 0) {
proxy_tls_set_data_prot(FALSE);
}
break;
}
return PR_DECLINED(cmd);
}
/* Event handlers
*/
static void proxy_ctrl_read_ev(const void *event_data, void *user_data) {
switch (proxy_role) {
case PROXY_ROLE_REVERSE:
if (proxy_sess_state & PROXY_SESS_STATE_CONNECTED) {
proxy_restrict_session();
pr_event_unregister(&proxy_module, "mod_proxy.ctrl-read",
proxy_ctrl_read_ev);
}
break;
case PROXY_ROLE_FORWARD:
/* We don't really need this event listener for forward proxying. */
pr_event_unregister(&proxy_module, "mod_proxy.ctrl-read",
proxy_ctrl_read_ev);
break;
}
}
static void proxy_exit_ev(const void *event_data, void *user_data) {
struct proxy_session *proxy_sess;
proxy_sess = (struct proxy_session *) pr_table_get(session.notes,
"mod_proxy.proxy-session", NULL);
if (proxy_sess != NULL) {
/* proxy_sess->frontend_ctrl_conn is session.c; let the core engine
* close that connection. If we try to close it here via pr_inet_close(),
* we risk segfaults due to double-free of the memory, stale pointers, etc.
*/
if (proxy_sess->frontend_ctrl_conn != NULL) {
proxy_sess->frontend_ctrl_conn = NULL;
}
if (proxy_sess->frontend_data_conn != NULL) {
/* Note: if session.d is NULL, ASSUME that the core API's session
* cleanup already closed that connection, and so doing it here
* would be redundant (and worse, an attempted double-free);
* the frontend_data_conn and session.d are the same connection.
*/
if (session.d != NULL) {
pr_inet_close(proxy_sess->pool, proxy_sess->frontend_data_conn);
proxy_sess->frontend_data_conn = session.d = NULL;
}
}
if (proxy_sess->backend_ctrl_conn != NULL) {
proxy_inet_close(proxy_sess->pool, proxy_sess->backend_ctrl_conn);
pr_inet_close(proxy_sess->pool, proxy_sess->backend_ctrl_conn);
proxy_sess->backend_ctrl_conn = NULL;
}
if (proxy_sess->backend_data_conn != NULL) {
proxy_inet_close(proxy_sess->pool, proxy_sess->backend_data_conn);
pr_inet_close(proxy_sess->pool, proxy_sess->backend_data_conn);
proxy_sess->backend_data_conn = NULL;
}
pr_table_remove(session.notes, "mod_proxy.proxy-session", NULL);
}
switch (proxy_role) {
case PROXY_ROLE_REVERSE:
proxy_reverse_sess_exit(session.pool);
break;
case PROXY_ROLE_FORWARD:
break;
}
if (proxy_logfd >= 0) {
(void) close(proxy_logfd);
proxy_logfd = -1;
}
}
#if defined(PR_SHARED_MODULE)
static void proxy_mod_unload_ev(const void *event_data, void *user_data) {
if (strcmp((const char *) event_data, "mod_proxy.c") != 0) {
return;
}
/* Unregister ourselves from all events. */
pr_event_unregister(&proxy_module, NULL, NULL);
destroy_pool(proxy_pool);
proxy_pool = NULL;
(void) close(proxy_logfd);
proxy_logfd = -1;
}
#endif
static void proxy_postparse_ev(const void *event_data, void *user_data) {
int engine = FALSE;
config_rec *c;
c = find_config(main_server->conf, CONF_PARAM, "ProxyEngine", FALSE);
if (c != NULL) {
engine = *((int *) c->argv[0]);
} else {
server_rec *s;
for (s = (server_rec *) server_list->xas_list; s; s = s->next) {
c = find_config(s->conf, CONF_PARAM, "ProxyEngine", FALSE);
if (c != NULL) {
engine = *((int *) c->argv[0]);
}
if (engine) {
break;
}
}
}
if (engine == FALSE) {
return;
}
c = find_config(main_server->conf, CONF_PARAM, "ProxyTables", FALSE);
if (c == NULL) {
/* No ProxyTables configured, mod_proxy cannot run. */
pr_log_pri(PR_LOG_WARNING, MOD_PROXY_VERSION
": missing required ProxyTables directive, failing to start up");
pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BAD_CONFIG,
"Missing required config");
}
proxy_tables_dir = c->argv[0];
if (proxy_forward_init(proxy_pool, proxy_tables_dir) < 0) {
pr_log_pri(PR_LOG_WARNING, MOD_PROXY_VERSION
": unable to initialize forward proxy, failing to start up: %s",
strerror(errno));
pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BAD_CONFIG,
"Failed forward proxy initialization");
}
if (proxy_reverse_init(proxy_pool, proxy_tables_dir, 0) < 0) {
pr_log_pri(PR_LOG_WARNING, MOD_PROXY_VERSION
": unable to initialize reverse proxy, failing to start up: %s",
strerror(errno));
pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BAD_CONFIG,
"Failed reverse proxy initialization");
}
if (proxy_ssh_init(proxy_pool, proxy_tables_dir, 0) < 0) {
pr_log_pri(PR_LOG_WARNING, MOD_PROXY_VERSION
": unable to initialize SSH support, failing to start up: %s",
strerror(errno));
pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BAD_CONFIG,
"Failed SSH initialization");
}
if (proxy_tls_init(proxy_pool, proxy_tables_dir, 0) < 0) {
pr_log_pri(PR_LOG_WARNING, MOD_PROXY_VERSION
": unable to initialize TLS support, failing to start up: %s",
strerror(errno));
pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BAD_CONFIG,
"Failed TLS initialization");
}
}
static void proxy_restart_ev(const void *event_data, void *user_data) {
(void) proxy_forward_free(proxy_pool);
(void) proxy_reverse_free(proxy_pool);
(void) proxy_ssh_free(proxy_pool);
(void) proxy_tls_free(proxy_pool);
/* Do NOT close the database connection/handle here; we may have session
* processes that have their own handles to that same file.
*/
}
static void proxy_sess_reinit_ev(const void *event_data, void *user_data) {
struct proxy_session *proxy_sess;
int res;
/* A HOST command changed the main_server pointer; reinitialize ourselves. */
pr_event_unregister(&proxy_module, "core.exit", proxy_exit_ev);
pr_event_unregister(&proxy_module, "core.session-reinit",
proxy_sess_reinit_ev);
pr_event_unregister(&proxy_module, "mod_proxy.ctrl-read", proxy_ctrl_read_ev);
pr_event_unregister(&proxy_module, "core.timeout-idle", proxy_timeoutidle_ev);
pr_event_unregister(&proxy_module, "core.timeout-no-transfer",
proxy_timeoutnoxfer_ev);
pr_event_unregister(&proxy_module, "core.timeout-stalled",
proxy_timeoutstalled_ev);
/* Reset static variables, other session state. Note that we explicitly
* do NOT reset the proxy_tables_dir variable; that is set during postparse,
* and affects the entire daemon process.
*/
proxy_sess = (struct proxy_session *) pr_table_get(session.notes,
"mod_proxy.proxy-session", NULL);
if (proxy_sess != NULL) {
proxy_ssh_sess_free(proxy_pool);
proxy_tls_sess_free(proxy_pool);
proxy_reverse_sess_free(proxy_pool, proxy_sess);
proxy_forward_sess_free(proxy_pool, proxy_sess);
(void) pr_table_remove(session.notes, "mod_proxy.proxy-session", NULL);
proxy_session_free(proxy_pool, proxy_sess);
}
(void) close(proxy_logfd);
proxy_logfd = -1;
(void) proxy_db_close(proxy_pool, NULL);
proxy_engine = FALSE;
proxy_opts = 0UL;
proxy_login_attempts = 0;
proxy_role = PROXY_ROLE_REVERSE;
proxy_tls_xfer_prot_policy = PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_REQUIRED;
res = proxy_sess_init();
if (res < 0) {
pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_SESSION_INIT_FAILED,
NULL);
}
}
static void proxy_shutdown_ev(const void *event_data, void *user_data) {
int res;
(void) proxy_forward_free(proxy_pool);
(void) proxy_reverse_free(proxy_pool);
(void) proxy_ssh_free(proxy_pool);
(void) proxy_tls_free(proxy_pool);
res = proxy_db_close(proxy_pool, NULL);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error closing database: %s", strerror(errno));
}
(void) proxy_db_free();
#if defined(HAVE_OSSL_PROVIDER_LOAD_OPENSSL)
if (legacy_provider != NULL) {
OSSL_PROVIDER_unload(legacy_provider);
legacy_provider = NULL;
}
#endif /* HAVE_OSSL_PROVIDER_LOAD_OPENSSL */
destroy_pool(proxy_pool);
proxy_pool = NULL;
if (proxy_logfd >= 0) {
(void) close(proxy_logfd);
proxy_logfd = -1;
}
}
static void unblock_responses(void) {
const struct proxy_session *proxy_sess;
int block_responses = FALSE;
proxy_sess = pr_table_get(session.notes, "mod_proxy.proxy-session", NULL);
if (proxy_sess != NULL &&
proxy_sess->use_ssh == TRUE) {
/* We do not want mod_core's response flushed to the frontend client
* for SSH connections.
*/
block_responses = TRUE;
}
pr_response_block(block_responses);
}
static void proxy_timeoutidle_ev(const void *event_data, void *user_data) {
/* Unblock responses here, so that mod_core's response will be flushed
* out to the frontend client.
*/
unblock_responses();
}
static void proxy_timeoutnoxfer_ev(const void *event_data, void *user_data) {
/* Unblock responses here, so that mod_xfer's response will be flushed
* out to the frontend client.
*/
unblock_responses();
}
static void proxy_timeoutstalled_ev(const void *event_data, void *user_data) {
/* Unblock responses here, so that mod_xfer's response will be flushed
* out to the frontend client.
*/
unblock_responses();
}
/* XXX Do we want to support any Controls/ftpctl actions? */
/* Initialization routines
*/
static int proxy_init(void) {
proxy_pool = make_sub_pool(permanent_pool);
pr_pool_tag(proxy_pool, MOD_PROXY_VERSION);
#if defined(PR_SHARED_MODULE)
pr_event_register(&proxy_module, "core.module-unload", proxy_mod_unload_ev,
NULL);
#endif
pr_event_register(&proxy_module, "core.postparse", proxy_postparse_ev, NULL);
pr_event_register(&proxy_module, "core.restart", proxy_restart_ev, NULL);
pr_event_register(&proxy_module, "core.shutdown", proxy_shutdown_ev, NULL);
#if defined(HAVE_OSSL_PROVIDER_LOAD_OPENSSL)
/* Load the "legacy" OpenSSL algorithm provider, for those SSH algorithms
* that require support of algorithms that OpenSSL deemed "legacy".
*/
legacy_provider = OSSL_PROVIDER_load(NULL, "legacy");
#endif /* HAVE_OSSL_PROVIDER_LOAD_OPENSSL */
if (proxy_db_init(proxy_pool) < 0) {
return -1;
}
return 0;
}
/* Set defaults for directives that mod_proxy should allow (but whose
* values are checked e.g. by PRE_CMD handlers):
*
* AllowOverwrite
* AllowStoreRestart
*
* Unless these directives have already been set, of course.
*/
static void proxy_set_sess_defaults(void) {
config_rec *c;
c = find_config(main_server->conf, CONF_PARAM, "AllowOverwrite", FALSE);
if (c == NULL) {
c = add_config_param_set(&main_server->conf, "AllowOverwrite", 1, NULL);
c->argv[0] = palloc(c->pool, sizeof(unsigned char));
*((unsigned char *) c->argv[0]) = TRUE;
c->flags |= CF_MERGEDOWN;
}
c = find_config(main_server->conf, CONF_PARAM, "AllowStoreRestart", FALSE);
if (c == NULL) {
c = add_config_param_set(&main_server->conf, "AllowStoreRestart", 1, NULL);
c->argv[0] = palloc(c->pool, sizeof(unsigned char));
*((unsigned char *) c->argv[0]) = TRUE;
c->flags |= CF_MERGEDOWN;
}
}
static int proxy_sess_init(void) {
config_rec *c;
int res;
struct proxy_session *proxy_sess;
const char *sess_dir = NULL;
/* We have to register our HOST handler here, even if ProxyEngine is off,
* as the current vhost may be disabled BUT the requested vhost may be
* enabled.
*/
pr_event_register(&proxy_module, "core.session-reinit",
proxy_sess_reinit_ev, NULL);
c = find_config(main_server->conf, CONF_PARAM, "ProxyEngine", FALSE);
if (c != NULL) {
proxy_engine = *((int *) c->argv[0]);
}
if (proxy_engine == FALSE) {
return 0;
}
pr_event_register(&proxy_module, "core.exit", proxy_exit_ev, NULL);
pr_event_register(&proxy_module, "mod_proxy.ctrl-read", proxy_ctrl_read_ev,
NULL);
/* Install event handlers for timeouts, so that we can properly close
* the connections on either side.
*/
pr_event_register(&proxy_module, "core.timeout-idle",
proxy_timeoutidle_ev, NULL);
pr_event_register(&proxy_module, "core.timeout-no-transfer",
proxy_timeoutnoxfer_ev, NULL);
pr_event_register(&proxy_module, "core.timeout-stalled",
proxy_timeoutstalled_ev, NULL);
c = find_config(main_server->conf, CONF_PARAM, "ProxyLog", FALSE);
if (c != NULL) {
char *logname;
logname = c->argv[0];
if (strncasecmp(logname, "none", 5) != 0) {
int xerrno;
pr_signals_block();
PRIVS_ROOT
res = pr_log_openfile(logname, &proxy_logfd, PR_LOG_SYSTEM_MODE);
xerrno = errno;
PRIVS_RELINQUISH
pr_signals_unblock();
if (res < 0) {
if (res == -1) {
pr_log_pri(PR_LOG_NOTICE, MOD_PROXY_VERSION
": notice: unable to open ProxyLog '%s': %s", logname,
strerror(xerrno));
} else if (res == PR_LOG_WRITABLE_DIR) {
pr_log_pri(PR_LOG_NOTICE, MOD_PROXY_VERSION
": notice: unable to open ProxyLog '%s': parent directory is "
"world-writable", logname);
} else if (res == PR_LOG_SYMLINK) {
pr_log_pri(PR_LOG_NOTICE, MOD_PROXY_VERSION
": notice: unable to open ProxyLog '%s': cannot log to a symlink",
logname);
}
}
}
}
if (proxy_pool == NULL) {
proxy_pool = make_sub_pool(session.pool);
pr_pool_tag(proxy_pool, MOD_PROXY_VERSION " Session Pool");
}
c = find_config(main_server->conf, CONF_PARAM, "ProxyOptions", FALSE);
while (c != NULL) {
unsigned long opts;
pr_signals_handle();
opts = *((unsigned long *) c->argv[0]);
proxy_opts |= opts;
c = find_config_next(c, c->next, CONF_PARAM, "ProxyOptions", FALSE);
}
c = find_config(main_server->conf, CONF_PARAM, "ProxyRole", FALSE);
if (c != NULL) {
proxy_role = *((int *) c->argv[0]);
}
proxy_random_init();
/* Set defaults for directives that mod_proxy should allow. */
proxy_set_sess_defaults();
/* Allocate our own session structure, for tracking proxy-specific
* fields. Use the session.notes table for stashing/retrieving it as
* needed.
*/
proxy_sess = (struct proxy_session *) proxy_session_alloc(proxy_pool);
res = pr_table_add(session.notes, "mod_proxy.proxy-session", proxy_sess,
sizeof(struct proxy_session));
if (res < 0 &&
errno == ENOSPC) {
int nents, nmaxents;
nents = pr_table_count(session.notes);
nmaxents = nents * 2;
/* Attempt to handle the unusual case where the table is full, since
* we really need this note.
*/
pr_trace_msg(trace_channel, 1,
"session notes table is full (%u), increasing entry limit to %u",
nents, nmaxents);
if (pr_table_ctl(session.notes, PR_TABLE_CTL_SET_MAX_ENTS,
&nmaxents) == 0) {
res = pr_table_add(session.notes, "mod_proxy.proxy-session", proxy_sess,
sizeof(struct proxy_session));
} else {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error increasing session notes max entries: %s", strerror(errno));
res = -1;
errno = ENOSPC;
}
}
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error stashing proxy session note: %s", strerror(errno));
/* This is a fatal error; mod_proxy won't function without this note. */
errno = EPERM;
return -1;
}
/* Provide default note values. */
(void) pr_table_add_dup(session.notes, "mod_proxy.backend-port", "0", 0);
c = find_config(main_server->conf, CONF_PARAM, "ProxySourceAddress", FALSE);
if (c != NULL) {
proxy_sess->src_addr = c->argv[0];
}
c = find_config(main_server->conf, CONF_PARAM, "ProxyDataTransferPolicy",
FALSE);
if (c != NULL) {
proxy_sess->dataxfer_policy = *((int *) c->argv[0]);
}
c = find_config(main_server->conf, CONF_PARAM, "ProxyDirectoryListPolicy",
FALSE);
if (c != NULL) {
proxy_sess->dirlist_policy = *((int *) c->argv[0]);
proxy_sess->dirlist_opts = *((unsigned long *) c->argv[1]);
}
c = find_config(main_server->conf, CONF_PARAM, "ProxyTimeoutConnect", FALSE);
if (c != NULL) {
proxy_sess->connect_timeout = *((int *) c->argv[0]);
} else {
proxy_sess->connect_timeout = PROXY_CONNECT_DEFAULT_TIMEOUT;
}
c = find_config(main_server->conf, CONF_PARAM, "ProxyTimeoutLinger", FALSE);
if (c != NULL) {
proxy_sess->linger_timeout = *((int *) c->argv[0]);
} else {
proxy_sess->linger_timeout = PROXY_LINGER_DEFAULT_TIMEOUT;
}
c = find_config(main_server->conf, CONF_PARAM,
"ProxyTLSTransferProtectionPolicy", FALSE);
if (c != NULL) {
proxy_tls_xfer_prot_policy = *((int *) c->argv[0]);
switch (proxy_tls_xfer_prot_policy) {
case PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_CLIENT:
case PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_REQUIRED:
proxy_tls_set_data_prot(TRUE);
break;
case PROXY_FTP_SESS_TLS_XFER_PROTECTION_POLICY_CLEAR:
proxy_tls_set_data_prot(FALSE);
break;
default:
/* ignore */
break;
}
}
/* Every proxy session starts off in the ProxyTables/empty/ directory. */
sess_dir = pdircat(proxy_pool, proxy_tables_dir, "empty", NULL);
if (pr_fsio_chdir_canon(sess_dir, TRUE) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error setting session directory to '%s': %s", sess_dir, strerror(errno));
}
/* Close any database handle inherited from our parent, and open a new
* one, per SQLite3 recommendation.
*/
(void) proxy_db_close(proxy_pool, NULL);
if (proxy_ssh_sess_init(proxy_pool, proxy_sess, 0) < 0) {
pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION,
"Unable to initialize SSH API");
}
if (proxy_tls_sess_init(proxy_pool, proxy_sess, 0) < 0) {
pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION,
"Unable to initialize TLS API");
}
switch (proxy_role) {
case PROXY_ROLE_REVERSE:
if (proxy_reverse_sess_init(proxy_pool, proxy_tables_dir,
proxy_sess, 0) < 0) {
pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION,
"Unable to initialize reverse proxy API");
}
set_auth_check(proxy_reverse_have_authenticated);
break;
case PROXY_ROLE_FORWARD:
if (proxy_forward_sess_init(proxy_pool, proxy_tables_dir,
proxy_sess) < 0) {
pr_session_disconnect(&proxy_module, PR_SESS_DISCONNECT_BY_APPLICATION,
"Unable to initialize forward proxy API");
}
/* XXX TODO:
* DisplayConnect
*/
set_auth_check(proxy_forward_have_authenticated);
break;
}
/* XXX set protocol? What about ssh2 proxying? How to interact
* with mod_sftp, which doesn't have the same pipeline of request
* handling? (Need to add PRE_REQ handling in mod_sftp, to support proxying.)
*
* pr_session_set_protocol("proxy"); ?
*
* If we do this, we should also add separate "proxy" rows to the DelayTable.
*/
/* We have to use our own command event loop, since we will also need to
* watch any data transfer connections with the backend server, in addition
* to the client control connection.
*/
pr_cmd_set_handler(proxy_cmd_loop);
proxy_remove_symbols();
return 0;
}
/* Module API tables
*/
static conftable proxy_conftab[] = {
{ "ProxyDataTransferPolicy", set_proxydataxferpolicy, NULL },
{ "ProxyDatastore", set_proxydatastore, NULL },
{ "ProxyDirectoryListPolicy", set_proxydirlistpolicy, NULL },
{ "ProxyEngine", set_proxyengine, NULL },
{ "ProxyForwardEnabled", set_proxyforwardenabled, NULL },
{ "ProxyForwardMethod", set_proxyforwardmethod, NULL },
{ "ProxyForwardTo", set_proxyforwardto, NULL },
{ "ProxyLog", set_proxylog, NULL },
{ "ProxyOptions", set_proxyoptions, NULL },
{ "ProxyRetryCount", set_proxyretrycount, NULL },
{ "ProxyReverseConnectPolicy",set_proxyreverseconnectpolicy, NULL },
{ "ProxyReverseServers", set_proxyreverseservers, NULL },
{ "ProxyRole", set_proxyrole, NULL },
{ "ProxySourceAddress", set_proxysourceaddress, NULL },
{ "ProxyTables", set_proxytables, NULL },
{ "ProxyTimeoutConnect", set_proxytimeoutconnect, NULL },
{ "ProxyTimeoutLinger", set_proxytimeoutlinger, NULL },
/* SSH support */
{ "ProxySFTPCiphers", set_proxysftpciphers, NULL },
{ "ProxySFTPCompression", set_proxysftpcompression, NULL },
{ "ProxySFTPDigests", set_proxysftpdigests, NULL },
{ "ProxySFTPHostKey", set_proxysftphostkey, NULL },
{ "ProxySFTPKeyExchanges", set_proxysftpkeyexchanges, NULL },
{ "ProxySFTPOptions", set_proxysftpoptions, NULL },
{ "ProxySFTPPassPhraseProvider", set_proxysftppassphraseprovider, NULL },
{ "ProxySFTPServerAlive", set_proxysftpserveralive, NULL },
{ "ProxySFTPServerMatch", set_proxysftpservermatch, NULL },
{ "ProxySFTPVerifyServer", set_proxysftpverifyserver, NULL },
/* TLS support */
{ "ProxyTLSCACertificateFile",set_proxytlscacertfile, NULL },
{ "ProxyTLSCACertificatePath",set_proxytlscacertpath, NULL },
{ "ProxyTLSCARevocationFile", set_proxytlscacrlfile, NULL },
{ "ProxyTLSCARevocationPath", set_proxytlscacrlpath, NULL },
{ "ProxyTLSCertificateFile", set_proxytlscertfile, NULL },
{ "ProxyTLSCertificateKeyFile",set_proxytlscertkeyfile, NULL },
{ "ProxyTLSCipherSuite", set_proxytlsciphersuite, NULL },
{ "ProxyTLSEngine", set_proxytlsengine, NULL },
{ "ProxyTLSOptions", set_proxytlsoptions, NULL },
{ "ProxyTLSPreSharedKey", set_proxytlspresharedkey, NULL },
{ "ProxyTLSProtocol", set_proxytlsprotocol, NULL },
{ "ProxyTLSTimeoutHandshake", set_proxytlstimeouthandshake, NULL },
{ "ProxyTLSTransferProtectionPolicy", set_proxytlsxferprotpolicy, NULL },
{ "ProxyTLSVerifyServer", set_proxytlsverifyserver, NULL },
/* Support TransferPriority for proxied connections? */
/* Deliberately ignore/disable HiddenStores in mod_proxy configs */
/* Two timeouts, one for frontend and one for backend? */
{ NULL }
};
static cmdtable proxy_cmdtab[] = {
/* XXX Should this be marked with a CL_ value, for logging? */
{ CMD, C_ANY, G_NONE, proxy_any, FALSE, FALSE },
{ POST_CMD, C_PROT, G_NONE, proxy_post_prot, FALSE, FALSE },
{ 0, NULL }
};
module proxy_module = {
/* Always NULL */
NULL, NULL,
/* Module API version */
0x20,
/* Module name */
"proxy",
/* Module configuration handler table */
proxy_conftab,
/* Module command handler table */
proxy_cmdtab,
/* Module authentication handler table */
NULL,
/* Module initialization */
proxy_init,
/* Session initialization */
proxy_sess_init,
/* Module version */
MOD_PROXY_VERSION
};
proftpd-mod_proxy-0.9.5/mod_proxy.h.in 0000664 0000000 0000000 00000010614 14757370167 0020042 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy
* Copyright (c) 2012-2025 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_PROXY_H
#define MOD_PROXY_H
#include "conf.h"
#include "privs.h"
#include
#if HAVE_SYS_MMAN_H
# include
#endif
/* Define if you have the header. */
#undef HAVE_ZLIB_H
/* Define if you have OpenSSL with crippled AES support. */
#undef HAVE_AES_CRIPPLED_OPENSSL
/* Define if you have OpenSSL with EVP_aes_128_ctr support. */
#undef HAVE_EVP_AES_128_CTR_OPENSSL
/* Define if you have OpenSSL with EVP_aes_192_ctr support. */
#undef HAVE_EVP_AES_192_CTR_OPENSSL
/* Define if you have OpenSSL with EVP_aes_256_ctr support. */
#undef HAVE_EVP_AES_256_CTR_OPENSSL
/* Define if you have OpenSSL with EVP_aes_256_gcm support. */
#undef HAVE_EVP_AES_256_GCM_OPENSSL
/* Define if you have OpenSSL with OSSL_PROVIDER_load support. */
#undef HAVE_OSSL_PROVIDER_LOAD_OPENSSL
/* Define if you have OpenSSL with SHA256 support. */
#undef HAVE_SHA256_OPENSSL
/* Define if you have OpenSSL with SHA512 support. */
#undef HAVE_SHA512_OPENSSL
/* Define if you have OpenSSL with X448 support. */
#undef HAVE_X448_OPENSSL
/* Define if you have the sqlite3.h header. */
#undef HAVE_SQLITE3_H
#if !defined(HAVE_SQLITE3_H)
# error "SQLite library/headers required"
#endif
/* Define if you have the random(3) function. */
#undef HAVE_RANDOM
/* Define if you have the sqlite3_stmt_readonly() function. */
#undef HAVE_SQLITE3_STMT_READONLY
/* Define if you have the sqlite3_trace() function. */
#undef HAVE_SQLITE3_TRACE
/* Define if you have the sqlite3_trace_v2() function. */
#undef HAVE_SQLITE3_TRACE_V2
/* Define if you have the srandom(3) function. */
#undef HAVE_SRANDOM
/* Define if you have the strnstr(3) function. */
#undef HAVE_STRNSTR
#define MOD_PROXY_VERSION "mod_proxy/0.9.5"
/* Make sure the version of proftpd is as necessary. */
#if PROFTPD_VERSION_NUMBER < 0x0001030706
# error "ProFTPD 1.3.7a or later required"
#endif
/* mod_proxy option flags */
#define PROXY_OPT_USE_PROXY_PROTOCOL_V1 0x0001
#define PROXY_OPT_SHOW_FEATURES 0x0002
#define PROXY_OPT_USE_REVERSE_PROXY_AUTH 0x0004
#define PROXY_OPT_USE_DIRECT_DATA_TRANSFERS 0x0008
#define PROXY_OPT_IGNORE_CONFIG_PERMS 0x0010
#define PROXY_OPT_USE_PROXY_PROTOCOL_V2 0x0020
#define PROXY_OPT_USE_PROXY_PROTOCOL_V2_TLVS 0x0040
#define PROXY_OPT_ALLOW_FOREIGN_ADDRESS 0x0080
#define PROXY_OPT_IGNORE_FOREIGN_ADDRESS 0x0100
/* mod_proxy datastores */
#define PROXY_DATASTORE_SQLITE 1
#define PROXY_DATASTORE_REDIS 2
/* Miscellaneous */
extern int proxy_logfd;
extern module proxy_module;
extern pool *proxy_pool;
extern unsigned long proxy_opts;
extern unsigned int proxy_sess_state;
extern int proxy_datastore;
extern void *proxy_datastore_data;
extern size_t proxy_datastore_datasz;
/* mod_proxy session state flags */
#define PROXY_SESS_STATE_PROXY_AUTHENTICATED 0x0001
#define PROXY_SESS_STATE_CONNECTED 0x0002
#define PROXY_SESS_STATE_BACKEND_AUTHENTICATED 0x0004
#define PROXY_SESS_STATE_BACKEND_HAS_CTRL_TLS 0x0008
#define PROXY_SESS_STATE_BACKEND_HAS_DATA_TLS 0x0010
#define PROXY_SESS_STATE_SSH_HAVE_KEX 0x0020
#define PROXY_SESS_STATE_SSH_HAVE_SERVICE 0x0040
#define PROXY_SESS_STATE_SSH_HAVE_AUTH 0x0080
#define PROXY_SESS_STATE_SSH_REKEYING 0x0100
#define PROXY_SESS_STATE_SSH_HAVE_EXT_INFO 0x0200
#ifndef PROXY_DEFAULT_RETRY_COUNT
# define PROXY_DEFAULT_RETRY_COUNT 5
#endif
/* mod_proxy SSH roles */
#define PROXY_SSH_ROLE_SERVER 1
#define PROXY_SSH_ROLE_CLIENT 2
#endif /* MOD_PROXY_H */
proftpd-mod_proxy-0.9.5/mod_proxy.html 0000664 0000000 0000000 00000303266 14757370167 0020162 0 ustar 00root root 0000000 0000000