pax_global_header 0000666 0000000 0000000 00000000064 15207633221 0014513 g ustar 00root root 0000000 0000000 52 comment=68885f7cd474b16eef0b6ea6dfd4df83536b48c7
proftpd-mod_proxy-0.9.7/ 0000775 0000000 0000000 00000000000 15207633221 0015224 5 ustar 00root root 0000000 0000000 proftpd-mod_proxy-0.9.7/.clang-tidy 0000664 0000000 0000000 00000002746 15207633221 0017271 0 ustar 00root root 0000000 0000000 Checks: '
*,
-altera-*,
-android-*,
bugprone-*,
-bugprone-branch-clone,
-bugprone-easily-swappable-parameters,
-bugprone-integer-division,
-bugprone-macro-parentheses,
-bugprone-narrowing-conversions,
cert-*,
-cert-err33-c,
-cert-err34-c,
-cert-msc30-c,
-cert-msc50-cpp,
clang-analyzer-*,
-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,
-clang-analyzer-valist.Uninitialized,
-concurrency-mt-unsafe,
-cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-avoid-non-const-global-variables,
-cppcoreguidelines-init-variables,
-cppcoreguidelines-narrowing-conversions,
-google-readability-casting,
-hicpp-signed-bitwise,
llvm-*,
-llvm-include-order,
-llvmlibc-restrict-system-libc-headers,
misc-*,
-misc-redundant-expression,
-misc-unused-parameters,
modernize-*,
-modernize-avoid-c-arrays,
-modernize-deprecated-headers,
-modernize-loop-convert,
-modernize-use-auto,
-modernize-use-nullptr,
-modernize-use-trailing-return-type,
-modernize-use-using,
performance-*,
portability-*,
readability-*,
-readability-function-cognitive-complexity,
-readability-function-size,
-readability-identifier-length,
-readability-implicit-bool-conversion,
-readability-inconsistent-declaration-parameter-name,
-readability-isolate-declaration,
-readability-magic-numbers,
-readability-simplify-boolean-expr'
proftpd-mod_proxy-0.9.7/.codeql.yml 0000664 0000000 0000000 00000001471 15207633221 0017277 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.7/.gitattributes 0000664 0000000 0000000 00000000062 15207633221 0020115 0 ustar 00root root 0000000 0000000 *.pl linguist-language=C
*.pm linguist-language=C
proftpd-mod_proxy-0.9.7/.github/ 0000775 0000000 0000000 00000000000 15207633221 0016564 5 ustar 00root root 0000000 0000000 proftpd-mod_proxy-0.9.7/.github/workflows/ 0000775 0000000 0000000 00000000000 15207633221 0020621 5 ustar 00root root 0000000 0000000 proftpd-mod_proxy-0.9.7/.github/workflows/ci.yml 0000664 0000000 0000000 00000016277 15207633221 0021754 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:
# Let's try opting into use of Nodejs v24; see:
# https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/
# GitHub runners still causing problems with NodeJS v20; see:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 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@v6
with:
repository: proftpd/proftpd
path: proftpd
- name: Checkout module source code
uses: actions/checkout@v6
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.7/.github/workflows/codeql.yml 0000664 0000000 0000000 00000005501 15207633221 0022614 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
env:
# Let's try opting into use of Nodejs v24; see:
# https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/
# GitHub runners still causing problems with NodeJS v20; see:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
steps:
- name: Checkout ProFTPD
uses: actions/checkout@v6
with:
repository: proftpd/proftpd
- name: Checkout mod_proxy
uses: actions/checkout@v6
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@v4
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@v4
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@v4
with:
checkout_path: contrib/mod_proxy
sarif_file: "sarif-results/${{ matrix.language }}.sarif"
proftpd-mod_proxy-0.9.7/.github/workflows/regressions.yml 0000664 0000000 0000000 00000007121 15207633221 0023710 0 ustar 00root root 0000000 0000000 name: Regression Tests
on:
push:
branches:
- master
paths-ignore:
- '*.html'
- '*.md'
pull_request:
branches:
- master
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
env:
# Let's try opting into use of Nodejs v24; see:
# https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/
# GitHub runners still causing problems with NodeJS v20; see:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 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@v6
with:
repository: proftpd/proftpd
path: proftpd
- name: Checkout mod_proxy_protocol source code
uses: actions/checkout@v6
with:
repository: Castaglia/proftpd-mod_proxy_protocol
path: mod_proxy_protocol
- name: Checkout module source code
uses: actions/checkout@v6
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.7/.gitignore 0000664 0000000 0000000 00000000232 15207633221 0017211 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.7/Makefile.in 0000664 0000000 0000000 00000010614 15207633221 0017273 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/pkcs11.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/provider.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/poly1305.o \
lib/proxy/ssh/service.o \
lib/proxy/ssh/session.o \
lib/proxy/ssh/sntrup761.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/pkcs11.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/provider.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/poly1305.lo \
lib/proxy/ssh/service.lo \
lib/proxy/ssh/session.lo \
lib/proxy/ssh/sntrup761.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.7/README.md 0000664 0000000 0000000 00000001551 15207633221 0016505 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.7/cacerts.pem 0000664 0000000 0000000 00000707457 15207633221 0017377 0 ustar 00root root 0000000 0000000 ##
## Bundle of CA Root Certificates
##
## Certificate data from Mozilla as of: Thu Apr 17 19:18:41 2025 GMT
##
## Find updated versions here: https://curl.se/docs/caextract.html
##
## 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.29.
## SHA256: 620fd89c02acb0019f1899dab7907db5d20735904f5a9a0d3a8771a5857ac482
##
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-----
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-----
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-----
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 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-----
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-----
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-----
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-----
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-----
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-----
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-----
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-----
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-----
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 - R5
===========================
-----BEGIN CERTIFICATE-----
MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEkMCIGA1UECxMb
R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD
EwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoXDTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMb
R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD
EwpHbG9iYWxTaWduMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6
SFkc8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8kehOvRnkmS
h5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd
BgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYIKoZIzj0EAwMDaAAwZQIxAOVpEslu28Yx
uglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7
yFz9SO8NdCKoCOJuxUnOxwy8p2Fp8fc74SrL+SvzZpA3
-----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-----
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-----
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-----
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-----
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 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-----
Autoridad de Certificacion Firmaprofesional CIF A62634068
=========================================================
-----BEGIN CERTIFICATE-----
MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCRVMxQjBA
BgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2
MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1MDUxNTIyMDdaMFExCzAJBgNVBAYTAkVTMUIw
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+JIwuwIDAQABo4HvMIHsMB0GA1Ud
DgQWBBRlzeurNR4APn7VdMActHNHDhpkLzASBgNVHRMBAf8ECDAGAQH/AgEBMIGmBgNVHSAEgZ4w
gZswgZgGBFUdIAAwgY8wLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuZmlybWFwcm9mZXNpb25hbC5j
b20vY3BzMFwGCCsGAQUFBwICMFAeTgBQAGEAcwBlAG8AIABkAGUAIABsAGEAIABCAG8AbgBhAG4A
bwB2AGEAIAA0ADcAIABCAGEAcgBjAGUAbABvAG4AYQAgADAAOAAwADEANzAOBgNVHQ8BAf8EBAMC
AQYwDQYJKoZIhvcNAQELBQADggIBAHSHKAIrdx9miWTtj3QuRhy7qPj4Cx2Dtjqn6EWKB7fgPiDL
4QjbEwj4KKE1soCzC1HA01aajTNFSa9J8OA9B3pFE1r/yJfY0xgsfZb43aJlQ3CTkBW6kN/oGbDb
LIpgD7dvlAceHabJhfa9NPhAeGIQcDq+fUs5gakQ1JZBu/hfHAsdCPKxsIl68veg4MSPi3i1O1il
I45PVf42O+AMt8oqMEEgtIDNrvx2ZnOorm7hfNoD6JQg5iKj0B+QXSBTFCZX2lSX3xZEEAEeiGaP
cjiT3SC3NL7X8e5jjkd5KAb881lFJWAiMxujX6i6KtoaPc1A6ozuBRWV1aUsIC+nmCjuRfzxuIgA
LI9C2lHVnOUTaHFFQ4ueCyE8S1wF3BqfmI7avSKecs2tCsvMo2ebKHTEm9caPARYpoKdrcd7b/+A
lun4jWq9GJAd/0kakFI3ky88Al2CdgtR5xbHV/g4+afNmyJU72OwFW1TZQNKXkqgsqeOSQBZONXH
9IBk9W6VULgRfhVwOEqwf9DEMnDAGf/JOC0ULGb0QkTmVXYbgBVX/8Cnp6o5qtjTcNAuuuuUavpf
NIbnYrX9ivAwhZTJryQCL2/W3Wf+47BVTwSYT6RBVuKT0Gro1vP7ZeDOdcQxWQzugsgMYDNKGbqE
ZycPvEJdvSRUDewdcAZfpLz6IHxV
-----END CERTIFICATE-----
vTrus ECC Root CA
=================
-----BEGIN CERTIFICATE-----
MIICDzCCAZWgAwIBAgIUbmq8WapTvpg5Z6LSa6Q75m0c1towCgYIKoZIzj0EAwMwRzELMAkGA1UE
BhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBS
b290IENBMB4XDTE4MDczMTA3MjY0NFoXDTQzMDczMTA3MjY0NFowRzELMAkGA1UEBhMCQ04xHDAa
BgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBSb290IENBMHYw
EAYHKoZIzj0CAQYFK4EEACIDYgAEZVBKrox5lkqqHAjDo6LN/llWQXf9JpRCux3NCNtzslt188+c
ToL0v/hhJoVs1oVbcnDS/dtitN9Ti72xRFhiQgnH+n9bEOf+QP3A2MMrMudwpremIFUde4BdS49n
TPEQo0IwQDAdBgNVHQ4EFgQUmDnNvtiyjPeyq+GtJK97fKHbH88wDwYDVR0TAQH/BAUwAwEB/zAO
BgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwMDaAAwZQIwV53dVvHH4+m4SVBrm2nDb+zDfSXkV5UT
QJtS0zvzQBm8JsctBp61ezaf9SXUY2sAAjEA6dPGnlaaKsyh2j/IZivTWJwghfqrkYpwcBE4YGQL
YgmRWAD5Tfs0aNoJrSEGGJTO
-----END CERTIFICATE-----
vTrus Root CA
=============
-----BEGIN CERTIFICATE-----
MIIFVjCCAz6gAwIBAgIUQ+NxE9izWRRdt86M/TX9b7wFjUUwDQYJKoZIhvcNAQELBQAwQzELMAkG
A1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xFjAUBgNVBAMTDXZUcnVzIFJv
b3QgQ0EwHhcNMTgwNzMxMDcyNDA1WhcNNDMwNzMxMDcyNDA1WjBDMQswCQYDVQQGEwJDTjEcMBoG
A1UEChMTaVRydXNDaGluYSBDby4sTHRkLjEWMBQGA1UEAxMNdlRydXMgUm9vdCBDQTCCAiIwDQYJ
KoZIhvcNAQEBBQADggIPADCCAgoCggIBAL1VfGHTuB0EYgWgrmy3cLRB6ksDXhA/kFocizuwZots
SKYcIrrVQJLuM7IjWcmOvFjai57QGfIvWcaMY1q6n6MLsLOaXLoRuBLpDLvPbmyAhykUAyyNJJrI
ZIO1aqwTLDPxn9wsYTwaP3BVm60AUn/PBLn+NvqcwBauYv6WTEN+VRS+GrPSbcKvdmaVayqwlHeF
XgQPYh1jdfdr58tbmnDsPmcF8P4HCIDPKNsFxhQnL4Z98Cfe/+Z+M0jnCx5Y0ScrUw5XSmXX+6KA
YPxMvDVTAWqXcoKv8R1w6Jz1717CbMdHflqUhSZNO7rrTOiwCcJlwp2dCZtOtZcFrPUGoPc2BX70
kLJrxLT5ZOrpGgrIDajtJ8nU57O5q4IikCc9Kuh8kO+8T/3iCiSn3mUkpF3qwHYw03dQ+A0Em5Q2
AXPKBlim0zvc+gRGE1WKyURHuFE5Gi7oNOJ5y1lKCn+8pu8fA2dqWSslYpPZUxlmPCdiKYZNpGvu
/9ROutW04o5IWgAZCfEF2c6Rsffr6TlP9m8EQ5pV9T4FFL2/s1m02I4zhKOQUqqzApVg+QxMaPnu
1RcN+HFXtSXkKe5lXa/R7jwXC1pDxaWG6iSe4gUH3DRCEpHWOXSuTEGC2/KmSNGzm/MzqvOmwMVO
9fSddmPmAsYiS8GVP1BkLFTltvA8Kc9XAgMBAAGjQjBAMB0GA1UdDgQWBBRUYnBj8XWEQ1iO0RYg
scasGrz2iTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOC
AgEAKbqSSaet8PFww+SX8J+pJdVrnjT+5hpk9jprUrIQeBqfTNqK2uwcN1LgQkv7bHbKJAs5EhWd
nxEt/Hlk3ODg9d3gV8mlsnZwUKT+twpw1aA08XXXTUm6EdGz2OyC/+sOxL9kLX1jbhd47F18iMjr
jld22VkE+rxSH0Ws8HqA7Oxvdq6R2xCOBNyS36D25q5J08FsEhvMKar5CKXiNxTKsbhm7xqC5PD4
8acWabfbqWE8n/Uxy+QARsIvdLGx14HuqCaVvIivTDUHKgLKeBRtRytAVunLKmChZwOgzoy8sHJn
xDHO2zTlJQNgJXtxmOTAGytfdELSS8VZCAeHvsXDf+eW2eHcKJfWjwXj9ZtOyh1QRwVTsMo554Wg
icEFOwE30z9J4nfrI8iIZjs9OXYhRvHsXyO466JmdXTBQPfYaJqT4i2pLr0cox7IdMakLXogqzu4
sEb9b91fUlV1YvCXoHzXOP0l382gmxDPi7g4Xl7FtKYCNqEeXxzP4padKar9mK5S4fNBUvupLnKW
nyfjqnN9+BojZns7q2WwMgFLFT49ok8MKzWixtlnEjUwzXYuFrOZnk1PTi07NEPhmg4NpGaXutIc
SkwsKouLgU9xGqndXHt7CMUADTdA43x7VF8vhV929vensBxXVsFy6K2ir40zSbofitzmdHxghm+H
l3s=
-----END CERTIFICATE-----
ISRG Root X2
============
-----BEGIN CERTIFICATE-----
MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQswCQYDVQQGEwJV
UzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElT
UkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVT
MSkwJwYDVQQKEyBJbnRlcm5ldCBTZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNS
RyBSb290IFgyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0H
ttwW+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9ItgKbppb
d9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
HQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZIzj0EAwMDaAAwZQIwe3lORlCEwkSHRhtF
cP9Ymd70/aTSVaYgLXTWNLxBo1BfASdWtL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5
U6VR5CmD1/iQMVtCnwr1/q4AaOeMSQ+2b1tbFfLn
-----END CERTIFICATE-----
HiPKI Root CA - G1
==================
-----BEGIN CERTIFICATE-----
MIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBPMQswCQYDVQQG
EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xGzAZBgNVBAMMEkhpUEtJ
IFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRaFw0zNzEyMzExNTU5NTlaME8xCzAJBgNVBAYT
AlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEbMBkGA1UEAwwSSGlQS0kg
Um9vdCBDQSAtIEcxMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9B5/UnMyDHPkvRN0
o9QwqNCuS9i233VHZvR85zkEHmpwINJaR3JnVfSl6J3VHiGh8Ge6zCFovkRTv4354twvVcg3Px+k
wJyz5HdcoEb+d/oaoDjq7Zpy3iu9lFc6uux55199QmQ5eiY29yTw1S+6lZgRZq2XNdZ1AYDgr/SE
YYwNHl98h5ZeQa/rh+r4XfEuiAU+TCK72h8q3VJGZDnzQs7ZngyzsHeXZJzA9KMuH5UHsBffMNsA
GJZMoYFL3QRtU6M9/Aes1MU3guvklQgZKILSQjqj2FPseYlgSGDIcpJQ3AOPgz+yQlda22rpEZfd
hSi8MEyr48KxRURHH+CKFgeW0iEPU8DtqX7UTuybCeyvQqww1r/REEXgphaypcXTT3OUM3ECoWqj
1jOXTyFjHluP2cFeRXF3D4FdXyGarYPM+l7WjSNfGz1BryB1ZlpK9p/7qxj3ccC2HTHsOyDry+K4
9a6SsvfhhEvyovKTmiKe0xRvNlS9H15ZFblzqMF8b3ti6RZsR1pl8w4Rm0bZ/W3c1pzAtH2lsN0/
Vm+h+fbkEkj9Bn8SV7apI09bA8PgcSojt/ewsTu8mL3WmKgMa/aOEmem8rJY5AIJEzypuxC00jBF
8ez3ABHfZfjcK0NVvxaXxA/VLGGEqnKG/uY6fsI/fe78LxQ+5oXdUG+3Se0CAwEAAaNCMEAwDwYD
VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ncX+l6o/vY9cdVouslGDDjYr7AwDgYDVR0PAQH/BAQD
AgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBQUfB13HAE4/+qddRxosuej6ip0691x1TPOhwEmSKsxBHi
7zNKpiMdDg1H2DfHb680f0+BazVP6XKlMeJ45/dOlBhbQH3PayFUhuaVevvGyuqcSE5XCV0vrPSl
tJczWNWseanMX/mF+lLFjfiRFOs6DRfQUsJ748JzjkZ4Bjgs6FzaZsT0pPBWGTMpWmWSBUdGSquE
wx4noR8RkpkndZMPvDY7l1ePJlsMu5wP1G4wB9TcXzZoZjmDlicmisjEOf6aIW/Vcobpf2Lll07Q
JNBAsNB1CI69aO4I1258EHBGG3zgiLKecoaZAeO/n0kZtCW+VmWuF2PlHt/o/0elv+EmBYTksMCv
5wiZqAxeJoBF1PhoL5aPruJKHJwWDBNvOIf2u8g0X5IDUXlwpt/L9ZlNec1OvFefQ05rLisY+Gpz
jLrFNe85akEez3GoorKGB1s6yeHvP2UEgEcyRHCVTjFnanRbEEV16rCf0OY1/k6fi8wrkkVbbiVg
hUbN0aqwdmaTd5a+g744tiROJgvM7XpWGuDpWsZkrUx6AEhEL7lAuxM+vhV4nYWBSipX3tUZQ9rb
yltHhoMLP7YNdnhzeSJesYAfz77RP1YQmCuVh6EfnWQUYDksswBVLuT1sw5XxJFBAJw/6KXf6vb/
yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ==
-----END CERTIFICATE-----
GlobalSign ECC Root CA - R4
===========================
-----BEGIN CERTIFICATE-----
MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYDVQQLExtHbG9i
YWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds
b2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgwMTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9i
YWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds
b2JhbFNpZ24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkW
ymOxuYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNVHQ8BAf8E
BAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/+wpu+74zyTyjhNUwCgYI
KoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147bmF0774BxL4YSFlhgjICICadVGNA3jdg
UM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm
-----END CERTIFICATE-----
GTS Root R1
===========
-----BEGIN CERTIFICATE-----
MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQGEwJV
UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg
UjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE
ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0G
CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaM
f/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7wCl7raKb0
xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjwTcLCeoiKu7rPWRnWr4+w
B7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0PfyblqAj+lug8aJRT7oM6iCsVlgmy4HqMLnXW
nOunVmSPlk9orj2XwoSPwLxAwAtcvfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk
9+aCEI3oncKKiPo4Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zq
kUspzBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92wO1A
K/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70paDPvOmbsB4om3xPX
V2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrNVjzRlwW5y0vtOUucxD/SVRNuJLDW
cfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0T
AQH/BAUwAwEB/zAdBgNVHQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQAD
ggIBAJ+qQibbC5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe
QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuyh6f88/qBVRRi
ClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM47HLwEXWdyzRSjeZ2axfG34ar
J45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8JZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYci
NuaCp+0KueIHoI17eko8cdLiA6EfMgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5me
LMFrUKTX5hgUvYU/Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJF
fbdT6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ0E6yove+
7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm2tIMPNuzjsmhDYAPexZ3
FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bbbP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3
gm3c
-----END CERTIFICATE-----
GTS Root R2
===========
-----BEGIN CERTIFICATE-----
MIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQGEwJV
UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg
UjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE
ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0G
CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3Lv
CvptnfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY6Dlo7JUl
e3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAuMC6C/Pq8tBcKSOWIm8Wb
a96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7kRXuJVfeKH2JShBKzwkCX44ofR5GmdFrS
+LFjKBC4swm4VndAoiaYecb+3yXuPuWgf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7M
kogwTZq9TwtImoS1mKPV+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJG
r61K8YzodDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RWIr9q
S34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKaG73VululycslaVNV
J1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCqgc7dGtxRcw1PcOnlthYhGXmy5okL
dWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0T
AQH/BAUwAwEB/zAdBgNVHQ4EFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQAD
ggIBAB/Kzt3HvqGf2SdMC9wXmBFqiN495nFWcrKeGk6c1SuYJF2ba3uwM4IJvd8lRuqYnrYb/oM8
0mJhwQTtzuDFycgTE1XnqGOtjHsB/ncw4c5omwX4Eu55MaBBRTUoCnGkJE+M3DyCB19m3H0Q/gxh
swWV7uGugQ+o+MePTagjAiZrHYNSVc61LwDKgEDg4XSsYPWHgJ2uNmSRXbBoGOqKYcl3qJfEycel
/FVL8/B/uWU9J2jQzGv6U53hkRrJXRqWbTKH7QMgyALOWr7Z6v2yTcQvG99fevX4i8buMTolUVVn
jWQye+mew4K6Ki3pHrTgSAai/GevHyICc/sgCq+dVEuhzf9gR7A/Xe8bVr2XIZYtCtFenTgCR2y5
9PYjJbigapordwj6xLEokCZYCDzifqrXPW+6MYgKBesntaFJ7qBFVHvmJ2WZICGoo7z7GJa7Um8M
7YNRTOlZ4iBgxcJlkoKM8xAfDoqXvneCbT+PHV28SSe9zE8P4c52hgQjxcCMElv924SgJPFI/2R8
0L5cFtHvma3AH/vLrrw4IgYmZNralw4/KBVEqE8AyvCazM90arQ+POuV7LXTWtiBmelDGDfrs7vR
WGJB82bSj6p4lVQgw1oudCvV0b4YacCs1aTPObpRhANl6WLAYv7YTVWW4tAR+kg0Eeye7QUd5MjW
HYbL
-----END CERTIFICATE-----
GTS Root R3
===========
-----BEGIN CERTIFICATE-----
MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJVUzEi
MCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMw
HhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZ
R29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjO
PQIBBgUrgQQAIgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout
736GjOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL24CejQjBA
MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTB8Sa6oC2uhYHP0/Eq
Er24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7VKOQFhG/hMjqb2sXnh5GmCCbn9MN2azT
L818+FsuVbu/3ZL3pAzcMeGiAjEA/JdmZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV
11RZt+cRLInUue4X
-----END CERTIFICATE-----
GTS Root R4
===========
-----BEGIN CERTIFICATE-----
MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJVUzEi
MCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQw
HhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZ
R29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjO
PQIBBgUrgQQAIgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzu
hXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvRHYqjQjBA
MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSATNbrdP9JNqPV2Py1
PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/C
r8deVl5c1RxYIigL9zC2L7F8AjEA8GE8p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh
4rsUecrNIdSUtUlD
-----END CERTIFICATE-----
Telia Root CA v2
================
-----BEGIN CERTIFICATE-----
MIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQxCzAJBgNVBAYT
AkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2
MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1NTRaMEQxCzAJBgNVBAYTAkZJMRowGAYDVQQK
DBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2MjCCAiIwDQYJKoZI
hvcNAQEBBQADggIPADCCAgoCggIBALLQPwe84nvQa5n44ndp586dpAO8gm2h/oFlH0wnrI4AuhZ7
6zBqAMCzdGh+sq/H1WKzej9Qyow2RCRj0jbpDIX2Q3bVTKFgcmfiKDOlyzG4OiIjNLh9vVYiQJ3q
9HsDrWj8soFPmNB06o3lfc1jw6P23pLCWBnglrvFxKk9pXSW/q/5iaq9lRdU2HhE8Qx3FZLgmEKn
pNaqIJLNwaCzlrI6hEKNfdWV5Nbb6WLEWLN5xYzTNTODn3WhUidhOPFZPY5Q4L15POdslv5e2QJl
tI5c0BE0312/UqeBAMN/mUWZFdUXyApT7GPzmX3MaRKGwhfwAZ6/hLzRUssbkmbOpFPlob/E2wnW
5olWK8jjfN7j/4nlNW4o6GwLI1GpJQXrSPjdscr6bAhR77cYbETKJuFzxokGgeWKrLDiKca5JLNr
RBH0pUPCTEPlcDaMtjNXepUugqD0XBCzYYP2AgWGLnwtbNwDRm41k9V6lS/eINhbfpSQBGq6WT0E
BXWdN6IOLj3rwaRSg/7Qa9RmjtzG6RJOHSpXqhC8fF6CfaamyfItufUXJ63RDolUK5X6wK0dmBR4
M0KGCqlztft0DbcbMBnEWg4cJ7faGND/isgFuvGqHKI3t+ZIpEYslOqodmJHixBTB0hXbOKSTbau
BcvcwUpej6w9GU7C7WB1K9vBykLVAgMBAAGjYzBhMB8GA1UdIwQYMBaAFHKs5DN5qkWH9v2sHZ7W
xy+G2CQ5MB0GA1UdDgQWBBRyrOQzeapFh/b9rB2e1scvhtgkOTAOBgNVHQ8BAf8EBAMCAQYwDwYD
VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAoDtZpwmUPjaE0n4vOaWWl/oRrfxn83EJ
8rKJhGdEr7nv7ZbsnGTbMjBvZ5qsfl+yqwE2foH65IRe0qw24GtixX1LDoJt0nZi0f6X+J8wfBj5
tFJ3gh1229MdqfDBmgC9bXXYfef6xzijnHDoRnkDry5023X4blMMA8iZGok1GTzTyVR8qPAs5m4H
eW9q4ebqkYJpCh3DflminmtGFZhb069GHWLIzoBSSRE/yQQSwxN8PzuKlts8oB4KtItUsiRnDe+C
y748fdHif64W1lZYudogsYMVoe+KTTJvQS8TUoKU1xrBeKJR3Stwbbca+few4GeXVtt8YVMJAygC
QMez2P2ccGrGKMOF6eLtGpOg3kuYooQ+BXcBlj37tCAPnHICehIv1aO6UXivKitEZU61/Qrowc15
h2Er3oBXRb9n8ZuRXqWk7FlIEA04x7D6w0RtBPV4UBySllva9bguulvP5fBqnUsvWHMtTy3EHD70
sz+rFQ47GUGKpMFXEmZxTPpT41frYpUJnlTd0cI8Vzy9OK2YZLe4A5pTVmBds9hCG1xLEooc6+t9
xnppxyd/pPiL8uSUZodL6ZQHCRJ5irLrdATczvREWeAWysUsWNc8e89ihmpQfTU2Zqf7N+cox9jQ
raVplI/owd8k+BsHMYeB2F326CjYSlKArBPuUBQemMc=
-----END CERTIFICATE-----
D-TRUST BR Root CA 1 2020
=========================
-----BEGIN CERTIFICATE-----
MIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQswCQYDVQQGEwJE
RTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEJSIFJvb3QgQ0EgMSAy
MDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5NDQ1OVowSDELMAkGA1UEBhMCREUxFTATBgNV
BAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDEgMjAyMDB2MBAG
ByqGSM49AgEGBSuBBAAiA2IABMbLxyjR+4T1mu9CFCDhQ2tuda38KwOE1HaTJddZO0Flax7mNCq7
dPYSzuht56vkPE4/RAiLzRZxy7+SmfSk1zxQVFKQhYN4lGdnoxwJGT11NIXe7WB9xwy0QVK5buXu
QqOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHOREKv/VbNafAkl1bK6CKBrqx9t
MA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6gPKA6hjhodHRwOi8vY3JsLmQtdHJ1c3Qu
bmV0L2NybC9kLXRydXN0X2JyX3Jvb3RfY2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVj
dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwQlIlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxP
PUQtVHJ1c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjOPQQD
AwNpADBmAjEAlJAtE/rhY/hhY+ithXhUkZy4kzg+GkHaQBZTQgjKL47xPoFWwKrY7RjEsK70Pvom
AjEA8yjixtsrmfu3Ubgko6SUeho/5jbiA1czijDLgsfWFBHVdWNbFJWcHwHP2NVypw87
-----END CERTIFICATE-----
D-TRUST EV Root CA 1 2020
=========================
-----BEGIN CERTIFICATE-----
MIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQswCQYDVQQGEwJE
RTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEVWIFJvb3QgQ0EgMSAy
MDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5NTk1OVowSDELMAkGA1UEBhMCREUxFTATBgNV
BAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDEgMjAyMDB2MBAG
ByqGSM49AgEGBSuBBAAiA2IABPEL3YZDIBnfl4XoIkqbz52Yv7QFJsnL46bSj8WeeHsxiamJrSc8
ZRCC/N/DnU7wMyPE0jL1HLDfMxddxfCxivnvubcUyilKwg+pf3VlSSowZ/Rk99Yad9rDwpdhQntJ
raOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFH8QARY3OqQo5FD4pPfsazK2/umL
MA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6gPKA6hjhodHRwOi8vY3JsLmQtdHJ1c3Qu
bmV0L2NybC9kLXRydXN0X2V2X3Jvb3RfY2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVj
dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwRVYlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxP
PUQtVHJ1c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjOPQQD
AwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CAy/m0sRtW9XLS/BnR
AjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJbgfM0agPnIjhQW+0ZT0MW
-----END CERTIFICATE-----
DigiCert TLS ECC P384 Root G5
=============================
-----BEGIN CERTIFICATE-----
MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQswCQYDVQQGEwJV
UzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURpZ2lDZXJ0IFRMUyBFQ0MgUDM4
NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMx
FzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQg
Um9vdCBHNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1Tzvd
lHJS7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp0zVozptj
n4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICISB4CIfBFqMA4GA1UdDwEB
/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQCJao1H5+z8blUD2Wds
Jk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQLgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIx
AJSdYsiJvRmEFOml+wG4DXZDjC5Ty3zfDBeWUA==
-----END CERTIFICATE-----
DigiCert TLS RSA4096 Root G5
============================
-----BEGIN CERTIFICATE-----
MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBNMQswCQYDVQQG
EwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0
MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcNNDYwMTE0MjM1OTU5WjBNMQswCQYDVQQGEwJV
UzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0MDk2
IFJvb3QgRzUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz0PTJeRGd/fxmgefM1eS8
7IE+ajWOLrfn3q/5B03PMJ3qCQuZvWxX2hhKuHisOjmopkisLnLlvevxGs3npAOpPxG02C+JFvuU
AT27L/gTBaF4HI4o4EXgg/RZG5Wzrn4DReW+wkL+7vI8toUTmDKdFqgpwgscONyfMXdcvyej/Ces
tyu9dJsXLfKB2l2w4SMXPohKEiPQ6s+d3gMXsUJKoBZMpG2T6T867jp8nVid9E6P/DsjyG244gXa
zOvswzH016cpVIDPRFtMbzCe88zdH5RDnU1/cHAN1DrRN/BsnZvAFJNY781BOHW8EwOVfH/jXOnV
DdXifBBiqmvwPXbzP6PosMH976pXTayGpxi0KcEsDr9kvimM2AItzVwv8n/vFfQMFawKsPHTDU9q
TXeXAaDxZre3zu/O7Oyldcqs4+Fj97ihBMi8ez9dLRYiVu1ISf6nL3kwJZu6ay0/nTvEF+cdLvvy
z6b84xQslpghjLSR6Rlgg/IwKwZzUNWYOwbpx4oMYIwo+FKbbuH2TbsGJJvXKyY//SovcfXWJL5/
MZ4PbeiPT02jP/816t9JXkGPhvnxd3lLG7SjXi/7RgLQZhNeXoVPzthwiHvOAbWWl9fNff2C+MIk
wcoBOU+NosEUQB+cZtUMCUbW8tDRSHZWOkPLtgoRObqME2wGtZ7P6wIDAQABo0IwQDAdBgNVHQ4E
FgQUUTMc7TZArxfTJc1paPKvTiM+s0EwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8w
DQYJKoZIhvcNAQEMBQADggIBAGCmr1tfV9qJ20tQqcQjNSH/0GEwhJG3PxDPJY7Jv0Y02cEhJhxw
GXIeo8mH/qlDZJY6yFMECrZBu8RHANmfGBg7sg7zNOok992vIGCukihfNudd5N7HPNtQOa27PShN
lnx2xlv0wdsUpasZYgcYQF+Xkdycx6u1UQ3maVNVzDl92sURVXLFO4uJ+DQtpBflF+aZfTCIITfN
MBc9uPK8qHWgQ9w+iUuQrm0D4ByjoJYJu32jtyoQREtGBzRj7TG5BO6jm5qu5jF49OokYTurWGT/
u4cnYiWB39yhL/btp/96j1EuMPikAdKFOV8BmZZvWltwGUb+hmA+rYAQCd05JS9Yf7vSdPD3Rh9G
OUrYU9DzLjtxpdRv/PNn5AeP3SYZ4Y1b+qOTEZvpyDrDVWiakuFSdjjo4bq9+0/V77PnSIMx8IIh
47a+p6tv75/fTM8BuGJqIz3nCU2AG3swpMPdB380vqQmsvZB6Akd4yCYqjdP//fx4ilwMUc/dNAU
FvohigLVigmUdy7yWSiLfFCSCmZ4OIN1xLVaqBHG5cGdZlXPU8Sv13WFqUITVuwhd4GTWgzqltlJ
yqEI8pc7bZsEGCREjnwB8twl2F6GmrE52/WRMmrRpnCKovfepEWFJqgejF0pW8hL2JpqA15w8oVP
bEtoL8pU9ozaMv7Da4M/OMZ+
-----END CERTIFICATE-----
Certainly Root R1
=================
-----BEGIN CERTIFICATE-----
MIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAwPTELMAkGA1UE
BhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2VydGFpbmx5IFJvb3QgUjEwHhcN
MjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9MQswCQYDVQQGEwJVUzESMBAGA1UEChMJQ2Vy
dGFpbmx5MRowGAYDVQQDExFDZXJ0YWlubHkgUm9vdCBSMTCCAiIwDQYJKoZIhvcNAQEBBQADggIP
ADCCAgoCggIBANA21B/q3avk0bbm+yLA3RMNansiExyXPGhjZjKcA7WNpIGD2ngwEc/csiu+kr+O
5MQTvqRoTNoCaBZ0vrLdBORrKt03H2As2/X3oXyVtwxwhi7xOu9S98zTm/mLvg7fMbedaFySpvXl
8wo0tf97ouSHocavFwDvA5HtqRxOcT3Si2yJ9HiG5mpJoM610rCrm/b01C7jcvk2xusVtyWMOvwl
DbMicyF0yEqWYZL1LwsYpfSt4u5BvQF5+paMjRcCMLT5r3gajLQ2EBAHBXDQ9DGQilHFhiZ5shGI
XsXwClTNSaa/ApzSRKft43jvRl5tcdF5cBxGX1HpyTfcX35pe0HfNEXgO4T0oYoKNp43zGJS4YkN
KPl6I7ENPT2a/Z2B7yyQwHtETrtJ4A5KVpK8y7XdeReJkd5hiXSSqOMyhb5OhaRLWcsrxXiOcVTQ
AjeZjOVJ6uBUcqQRBi8LjMFbvrWhsFNunLhgkR9Za/kt9JQKl7XsxXYDVBtlUrpMklZRNaBA2Cnb
rlJ2Oy0wQJuK0EJWtLeIAaSHO1OWzaMWj/Nmqhexx2DgwUMFDO6bW2BvBlyHWyf5QBGenDPBt+U1
VwV/J84XIIwc/PH72jEpSe31C4SnT8H2TsIonPru4K8H+zMReiFPCyEQtkA6qyI6BJyLm4SGcprS
p6XEtHWRqSsjAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
DgQWBBTgqj8ljZ9EXME66C6ud0yEPmcM9DANBgkqhkiG9w0BAQsFAAOCAgEAuVevuBLaV4OPaAsz
HQNTVfSVcOQrPbA56/qJYv331hgELyE03fFo8NWWWt7CgKPBjcZq91l3rhVkz1t5BXdm6ozTaw3d
8VkswTOlMIAVRQdFGjEitpIAq5lNOo93r6kiyi9jyhXWx8bwPWz8HA2YEGGeEaIi1wrykXprOQ4v
MMM2SZ/g6Q8CRFA3lFV96p/2O7qUpUzpvD5RtOjKkjZUbVwlKNrdrRT90+7iIgXr0PK3aBLXWopB
GsaSpVo7Y0VPv+E6dyIvXL9G+VoDhRNCX8reU9ditaY1BMJH/5n9hN9czulegChB8n3nHpDYT3Y+
gjwN/KUD+nsa2UUeYNrEjvn8K8l7lcUq/6qJ34IxD3L/DCfXCh5WAFAeDJDBlrXYFIW7pw0WwfgH
JBu6haEaBQmAupVjyTrsJZ9/nbqkRxWbRHDxakvWOF5D8xh+UG7pWijmZeZ3Gzr9Hb4DJqPb1OG7
fpYnKx3upPvaJVQTA945xsMfTZDsjxtK0hzthZU4UHlG1sGQUDGpXJpuHfUzVounmdLyyCwzk5Iw
x06MZTMQZBf9JBeW0Y3COmor6xOLRPIh80oat3df1+2IpHLlOR+Vnb5nwXARPbv0+Em34yaXOp/S
X3z7wJl8OSngex2/DaeP0ik0biQVy96QXr8axGbqwua6OV+KmalBWQewLK8=
-----END CERTIFICATE-----
Certainly Root E1
=================
-----BEGIN CERTIFICATE-----
MIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQswCQYDVQQGEwJV
UzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlubHkgUm9vdCBFMTAeFw0yMTA0
MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlu
bHkxGjAYBgNVBAMTEUNlcnRhaW5seSBSb290IEUxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3m/4
fxzf7flHh4axpMCK+IKXgOqPyEpeKn2IaKcBYhSRJHpcnqMXfYqGITQYUBsQ3tA3SybHGWCA6TS9
YBk2QNYphwk8kXr2vBMj3VlOBF7PyAIcGFPBMdjaIOlEjeR2o0IwQDAOBgNVHQ8BAf8EBAMCAQYw
DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ygYy2R17ikq6+2uI1g4hevIIgcwCgYIKoZIzj0E
AwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozmut6Dacpps6kFtZaSF4fC0urQe87YQVt8
rgIwRt7qy12a7DLCZRawTDBcMPPaTnOGBtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR
-----END CERTIFICATE-----
Security Communication ECC RootCA1
==================================
-----BEGIN CERTIFICATE-----
MIICODCCAb6gAwIBAgIJANZdm7N4gS7rMAoGCCqGSM49BAMDMGExCzAJBgNVBAYTAkpQMSUwIwYD
VQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYDVQQDEyJTZWN1cml0eSBDb21t
dW5pY2F0aW9uIEVDQyBSb290Q0ExMB4XDTE2MDYxNjA1MTUyOFoXDTM4MDExODA1MTUyOFowYTEL
MAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKzApBgNV
BAMTIlNlY3VyaXR5IENvbW11bmljYXRpb24gRUNDIFJvb3RDQTEwdjAQBgcqhkjOPQIBBgUrgQQA
IgNiAASkpW9gAwPDvTH00xecK4R1rOX9PVdu12O/5gSJko6BnOPpR27KkBLIE+CnnfdldB9sELLo
5OnvbYUymUSxXv3MdhDYW72ixvnWQuRXdtyQwjWpS4g8EkdtXP9JTxpKULGjQjBAMB0GA1UdDgQW
BBSGHOf+LaVKiwj+KBH6vqNm+GBZLzAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAK
BggqhkjOPQQDAwNoADBlAjAVXUI9/Lbu9zuxNuie9sRGKEkz0FhDKmMpzE2xtHqiuQ04pV1IKv3L
snNdo4gIxwwCMQDAqy0Obe0YottT6SXbVQjgUMzfRGEWgqtJsLKB7HOHeLRMsmIbEvoWTSVLY70e
N9k=
-----END CERTIFICATE-----
BJCA Global Root CA1
====================
-----BEGIN CERTIFICATE-----
MIIFdDCCA1ygAwIBAgIQVW9l47TZkGobCdFsPsBsIDANBgkqhkiG9w0BAQsFADBUMQswCQYDVQQG
EwJDTjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRIT1JJVFkxHTAbBgNVBAMMFEJK
Q0EgR2xvYmFsIFJvb3QgQ0ExMB4XDTE5MTIxOTAzMTYxN1oXDTQ0MTIxMjAzMTYxN1owVDELMAkG
A1UEBhMCQ04xJjAkBgNVBAoMHUJFSUpJTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQD
DBRCSkNBIEdsb2JhbCBSb290IENBMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAPFm
CL3ZxRVhy4QEQaVpN3cdwbB7+sN3SJATcmTRuHyQNZ0YeYjjlwE8R4HyDqKYDZ4/N+AZspDyRhyS
sTphzvq3Rp4Dhtczbu33RYx2N95ulpH3134rhxfVizXuhJFyV9xgw8O558dnJCNPYwpj9mZ9S1Wn
P3hkSWkSl+BMDdMJoDIwOvqfwPKcxRIqLhy1BDPapDgRat7GGPZHOiJBhyL8xIkoVNiMpTAK+BcW
yqw3/XmnkRd4OJmtWO2y3syJfQOcs4ll5+M7sSKGjwZteAf9kRJ/sGsciQ35uMt0WwfCyPQ10WRj
eulumijWML3mG90Vr4TqnMfK9Q7q8l0ph49pczm+LiRvRSGsxdRpJQaDrXpIhRMsDQa4bHlW/KNn
MoH1V6XKV0Jp6VwkYe/iMBhORJhVb3rCk9gZtt58R4oRTklH2yiUAguUSiz5EtBP6DF+bHq/pj+b
OT0CFqMYs2esWz8sgytnOYFcuX6U1WTdno9uruh8W7TXakdI136z1C2OVnZOz2nxbkRs1CTqjSSh
GL+9V/6pmTW12xB3uD1IutbB5/EjPtffhZ0nPNRAvQoMvfXnjSXWgXSHRtQpdaJCbPdzied9v3pK
H9MiyRVVz99vfFXQpIsHETdfg6YmV6YBW37+WGgHqel62bno/1Afq8K0wM7o6v0PvY1NuLxxAgMB
AAGjQjBAMB0GA1UdDgQWBBTF7+3M2I0hxkjk49cULqcWk+WYATAPBgNVHRMBAf8EBTADAQH/MA4G
A1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAUoKsITQfI/Ki2Pm4rzc2IInRNwPWaZ+4
YRC6ojGYWUfo0Q0lHhVBDOAqVdVXUsv45Mdpox1NcQJeXyFFYEhcCY5JEMEE3KliawLwQ8hOnThJ
dMkycFRtwUf8jrQ2ntScvd0g1lPJGKm1Vrl2i5VnZu69mP6u775u+2D2/VnGKhs/I0qUJDAnyIm8
60Qkmss9vk/Ves6OF8tiwdneHg56/0OGNFK8YT88X7vZdrRTvJez/opMEi4r89fO4aL/3Xtw+zuh
TaRjAv04l5U/BXCga99igUOLtFkNSoxUnMW7gZ/NfaXvCyUeOiDbHPwfmGcCCtRzRBPbUYQaVQNW
4AB+dAb/OMRyHdOoP2gxXdMJxy6MW2Pg6Nwe0uxhHvLe5e/2mXZgLR6UcnHGCyoyx5JO1UbXHfmp
GQrI+pXObSOYqgs4rZpWDW+N8TEAiMEXnM0ZNjX+VVOg4DwzX5Ze4jLp3zO7Bkqp2IRzznfSxqxx
4VyjHQy7Ct9f4qNx2No3WqB4K/TUfet27fJhcKVlmtOJNBir+3I+17Q9eVzYH6Eze9mCUAyTF6ps
3MKCuwJXNq+YJyo5UOGwifUll35HaBC07HPKs5fRJNz2YqAo07WjuGS3iGJCz51TzZm+ZGiPTx4S
SPfSKcOYKMryMguTjClPPGAyzQWWYezyr/6zcCwupvI=
-----END CERTIFICATE-----
BJCA Global Root CA2
====================
-----BEGIN CERTIFICATE-----
MIICJTCCAaugAwIBAgIQLBcIfWQqwP6FGFkGz7RK6zAKBggqhkjOPQQDAzBUMQswCQYDVQQGEwJD
TjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRIT1JJVFkxHTAbBgNVBAMMFEJKQ0Eg
R2xvYmFsIFJvb3QgQ0EyMB4XDTE5MTIxOTAzMTgyMVoXDTQ0MTIxMjAzMTgyMVowVDELMAkGA1UE
BhMCQ04xJjAkBgNVBAoMHUJFSUpJTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQDDBRC
SkNBIEdsb2JhbCBSb290IENBMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABJ3LgJGNU2e1uVCxA/jl
SR9BIgmwUVJY1is0j8USRhTFiy8shP8sbqjV8QnjAyEUxEM9fMEsxEtqSs3ph+B99iK++kpRuDCK
/eHeGBIK9ke35xe/J4rUQUyWPGCWwf0VHKNCMEAwHQYDVR0OBBYEFNJKsVF/BvDRgh9Obl+rg/xI
1LCRMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gAMGUCMBq8
W9f+qdJUDkpd0m2xQNz0Q9XSSpkZElaA94M04TVOSG0ED1cxMDAtsaqdAzjbBgIxAMvMh1PLet8g
UXOQwKhbYdDFUDn9hf7B43j4ptZLvZuHjw/l1lOWqzzIQNph91Oj9w==
-----END CERTIFICATE-----
Sectigo Public Server Authentication Root E46
=============================================
-----BEGIN CERTIFICATE-----
MIICOjCCAcGgAwIBAgIQQvLM2htpN0RfFf51KBC49DAKBggqhkjOPQQDAzBfMQswCQYDVQQGEwJH
QjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBTZXJ2
ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwHhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5
WjBfMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0
aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUr
gQQAIgNiAAR2+pmpbiDt+dd34wc7qNs9Xzjoq1WmVk/WSOrsfy2qw7LFeeyZYX8QeccCWvkEN/U0
NSt3zn8gj1KjAIns1aeibVvjS5KToID1AZTc8GgHHs3u/iVStSBDHBv+6xnOQ6OjQjBAMB0GA1Ud
DgQWBBTRItpMWfFLXyY4qp3W7usNw/upYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB
/zAKBggqhkjOPQQDAwNnADBkAjAn7qRaqCG76UeXlImldCBteU/IvZNeWBj7LRoAasm4PdCkT0RH
lAFWovgzJQxC36oCMB3q4S6ILuH5px0CMk7yn2xVdOOurvulGu7t0vzCAxHrRVxgED1cf5kDW21U
SAGKcw==
-----END CERTIFICATE-----
Sectigo Public Server Authentication Root R46
=============================================
-----BEGIN CERTIFICATE-----
MIIFijCCA3KgAwIBAgIQdY39i658BwD6qSWn4cetFDANBgkqhkiG9w0BAQwFADBfMQswCQYDVQQG
EwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBT
ZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwHhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1
OTU5WjBfMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1T
ZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwggIiMA0GCSqGSIb3
DQEBAQUAA4ICDwAwggIKAoICAQCTvtU2UnXYASOgHEdCSe5jtrch/cSV1UgrJnwUUxDaef0rty2k
1Cz66jLdScK5vQ9IPXtamFSvnl0xdE8H/FAh3aTPaE8bEmNtJZlMKpnzSDBh+oF8HqcIStw+Kxwf
GExxqjWMrfhu6DtK2eWUAtaJhBOqbchPM8xQljeSM9xfiOefVNlI8JhD1mb9nxc4Q8UBUQvX4yMP
FF1bFOdLvt30yNoDN9HWOaEhUTCDsG3XME6WW5HwcCSrv0WBZEMNvSE6Lzzpng3LILVCJ8zab5vu
ZDCQOc2TZYEhMbUjUDM3IuM47fgxMMxF/mL50V0yeUKH32rMVhlATc6qu/m1dkmU8Sf4kaWD5Qaz
Yw6A3OASVYCmO2a0OYctyPDQ0RTp5A1NDvZdV3LFOxxHVp3i1fuBYYzMTYCQNFu31xR13NgESJ/A
wSiItOkcyqex8Va3e0lMWeUgFaiEAin6OJRpmkkGj80feRQXEgyDet4fsZfu+Zd4KKTIRJLpfSYF
plhym3kT2BFfrsU4YjRosoYwjviQYZ4ybPUHNs2iTG7sijbt8uaZFURww3y8nDnAtOFr94MlI1fZ
EoDlSfB1D++N6xybVCi0ITz8fAr/73trdf+LHaAZBav6+CuBQug4urv7qv094PPK306Xlynt8xhW
6aWWrL3DkJiy4Pmi1KZHQ3xtzwIDAQABo0IwQDAdBgNVHQ4EFgQUVnNYZJX5khqwEioEYnmhQBWI
IUkwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAC9c
mTz8Bl6MlC5w6tIyMY208FHVvArzZJ8HXtXBc2hkeqK5Duj5XYUtqDdFqij0lgVQYKlJfp/imTYp
E0RHap1VIDzYm/EDMrraQKFz6oOht0SmDpkBm+S8f74TlH7Kph52gDY9hAaLMyZlbcp+nv4fjFg4
exqDsQ+8FxG75gbMY/qB8oFM2gsQa6H61SilzwZAFv97fRheORKkU55+MkIQpiGRqRxOF3yEvJ+M
0ejf5lG5Nkc/kLnHvALcWxxPDkjBJYOcCj+esQMzEhonrPcibCTRAUH4WAP+JWgiH5paPHxsnnVI
84HxZmduTILA7rpXDhjvLpr3Etiga+kFpaHpaPi8TD8SHkXoUsCjvxInebnMMTzD9joiFgOgyY9m
pFuiTdaBJQbpdqQACj7LzTWb4OE4y2BThihCQRxEV+ioratF4yUQvNs+ZUH7G6aXD+u5dHn5Hrwd
Vw1Hr8Mvn4dGp+smWg9WY7ViYG4A++MnESLn/pmPNPW56MORcr3Ywx65LvKRRFHQV80MNNVIIb/b
E/FmJUNS0nAiNs2fxBx1IK1jcmMGDw4nztJqDby1ORrp0XZ60Vzk50lJLVU3aPAaOpg+VBeHVOmm
J1CJeyAvP/+/oYtKR5j/K3tJPsMpRmAYQqszKbrAKbkTidOIijlBO8n9pu0f9GBj39ItVQGL
-----END CERTIFICATE-----
SSL.com TLS RSA Root CA 2022
============================
-----BEGIN CERTIFICATE-----
MIIFiTCCA3GgAwIBAgIQb77arXO9CEDii02+1PdbkTANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQG
EwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxTU0wuY29tIFRMUyBSU0Eg
Um9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzQyMloXDTQ2MDgxOTE2MzQyMVowTjELMAkGA1UEBhMC
VVMxGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgUlNBIFJv
b3QgQ0EgMjAyMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANCkCXJPQIgSYT41I57u
9nTPL3tYPc48DRAokC+X94xI2KDYJbFMsBFMF3NQ0CJKY7uB0ylu1bUJPiYYf7ISf5OYt6/wNr/y
7hienDtSxUcZXXTzZGbVXcdotL8bHAajvI9AI7YexoS9UcQbOcGV0insS657Lb85/bRi3pZ7Qcac
oOAGcvvwB5cJOYF0r/c0WRFXCsJbwST0MXMwgsadugL3PnxEX4MN8/HdIGkWCVDi1FW24IBydm5M
R7d1VVm0U3TZlMZBrViKMWYPHqIbKUBOL9975hYsLfy/7PO0+r4Y9ptJ1O4Fbtk085zx7AGL0SDG
D6C1vBdOSHtRwvzpXGk3R2azaPgVKPC506QVzFpPulJwoxJF3ca6TvvC0PeoUidtbnm1jPx7jMEW
TO6Af77wdr5BUxIzrlo4QqvXDz5BjXYHMtWrifZOZ9mxQnUjbvPNQrL8VfVThxc7wDNY8VLS+YCk
8OjwO4s4zKTGkH8PnP2L0aPP2oOnaclQNtVcBdIKQXTbYxE3waWglksejBYSd66UNHsef8JmAOSq
g+qKkK3ONkRN0VHpvB/zagX9wHQfJRlAUW7qglFA35u5CCoGAtUjHBPW6dvbxrB6y3snm/vg1UYk
7RBLY0ulBY+6uB0rpvqR4pJSvezrZ5dtmi2fgTIFZzL7SAg/2SW4BCUvAgMBAAGjYzBhMA8GA1Ud
EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU+y437uOEeicuzRk1sTN8/9REQrkwHQYDVR0OBBYEFPsu
N+7jhHonLs0ZNbEzfP/UREK5MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAjYlt
hEUY8U+zoO9opMAdrDC8Z2awms22qyIZZtM7QbUQnRC6cm4pJCAcAZli05bg4vsMQtfhWsSWTVTN
j8pDU/0quOr4ZcoBwq1gaAafORpR2eCNJvkLTqVTJXojpBzOCBvfR4iyrT7gJ4eLSYwfqUdYe5by
iB0YrrPRpgqU+tvT5TgKa3kSM/tKWTcWQA673vWJDPFs0/dRa1419dvAJuoSc06pkZCmF8NsLzjU
o3KUQyxi4U5cMj29TH0ZR6LDSeeWP4+a0zvkEdiLA9z2tmBVGKaBUfPhqBVq6+AL8BQx1rmMRTqo
ENjwuSfr98t67wVylrXEj5ZzxOhWc5y8aVFjvO9nHEMaX3cZHxj4HCUp+UmZKbaSPaKDN7Egkaib
MOlqbLQjk2UEqxHzDh1TJElTHaE/nUiSEeJ9DU/1172iWD54nR4fK/4huxoTtrEoZP2wAgDHbICi
vRZQIA9ygV/MlP+7mea6kMvq+cYMwq7FGc4zoWtcu358NFcXrfA/rs3qr5nsLFR+jM4uElZI7xc7
P0peYNLcdDa8pUNjyw9bowJWCZ4kLOGGgYz+qxcs+sjiMho6/4UIyYOf8kpIEFR3N+2ivEC+5BB0
9+Rbu7nzifmPQdjH5FCQNYA+HLhNkNPU98OwoX6EyneSMSy4kLGCenROmxMmtNVQZlR4rmA=
-----END CERTIFICATE-----
SSL.com TLS ECC Root CA 2022
============================
-----BEGIN CERTIFICATE-----
MIICOjCCAcCgAwIBAgIQFAP1q/s3ixdAW+JDsqXRxDAKBggqhkjOPQQDAzBOMQswCQYDVQQGEwJV
UzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxTU0wuY29tIFRMUyBFQ0MgUm9v
dCBDQSAyMDIyMB4XDTIyMDgyNTE2MzM0OFoXDTQ2MDgxOTE2MzM0N1owTjELMAkGA1UEBhMCVVMx
GDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgRUNDIFJvb3Qg
Q0EgMjAyMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABEUpNXP6wrgjzhR9qLFNoFs27iosU8NgCTWy
JGYmacCzldZdkkAZDsalE3D07xJRKF3nzL35PIXBz5SQySvOkkJYWWf9lCcQZIxPBLFNSeR7T5v1
5wj4A4j3p8OSSxlUgaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSJjy+j6CugFFR7
81a4Jl9nOAuc0DAdBgNVHQ4EFgQUiY8vo+groBRUe/NWuCZfZzgLnNAwDgYDVR0PAQH/BAQDAgGG
MAoGCCqGSM49BAMDA2gAMGUCMFXjIlbp15IkWE8elDIPDAI2wv2sdDJO4fscgIijzPvX6yv/N33w
7deedWo1dlJF4AIxAMeNb0Igj762TVntd00pxCAgRWSGOlDGxK0tk/UYfXLtqc/ErFc2KAhl3zx5
Zn6g6g==
-----END CERTIFICATE-----
Atos TrustedRoot Root CA ECC TLS 2021
=====================================
-----BEGIN CERTIFICATE-----
MIICFTCCAZugAwIBAgIQPZg7pmY9kGP3fiZXOATvADAKBggqhkjOPQQDAzBMMS4wLAYDVQQDDCVB
dG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgRUNDIFRMUyAyMDIxMQ0wCwYDVQQKDARBdG9zMQswCQYD
VQQGEwJERTAeFw0yMTA0MjIwOTI2MjNaFw00MTA0MTcwOTI2MjJaMEwxLjAsBgNVBAMMJUF0b3Mg
VHJ1c3RlZFJvb3QgUm9vdCBDQSBFQ0MgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYT
AkRFMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEloZYKDcKZ9Cg3iQZGeHkBQcfl+3oZIK59sRxUM6K
DP/XtXa7oWyTbIOiaG6l2b4siJVBzV3dscqDY4PMwL502eCdpO5KTlbgmClBk1IQ1SQ4AjJn8ZQS
b+/Xxd4u/RmAo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR2KCXWfeBmmnoJsmo7jjPX
NtNPojAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIwW5kp85wxtolrbNa9d+F851F+
uDrNozZffPc8dz7kUK2o59JZDCaOMDtuCCrCp1rIAjEAmeMM56PDr9NJLkaCI2ZdyQAUEv049OGY
a3cpetskz2VAv9LcjBHo9H1/IISpQuQo
-----END CERTIFICATE-----
Atos TrustedRoot Root CA RSA TLS 2021
=====================================
-----BEGIN CERTIFICATE-----
MIIFZDCCA0ygAwIBAgIQU9XP5hmTC/srBRLYwiqipDANBgkqhkiG9w0BAQwFADBMMS4wLAYDVQQD
DCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgUlNBIFRMUyAyMDIxMQ0wCwYDVQQKDARBdG9zMQsw
CQYDVQQGEwJERTAeFw0yMTA0MjIwOTIxMTBaFw00MTA0MTcwOTIxMDlaMEwxLjAsBgNVBAMMJUF0
b3MgVHJ1c3RlZFJvb3QgUm9vdCBDQSBSU0EgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNV
BAYTAkRFMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtoAOxHm9BYx9sKOdTSJNy/BB
l01Z4NH+VoyX8te9j2y3I49f1cTYQcvyAh5x5en2XssIKl4w8i1mx4QbZFc4nXUtVsYvYe+W/CBG
vevUez8/fEc4BKkbqlLfEzfTFRVOvV98r61jx3ncCHvVoOX3W3WsgFWZkmGbzSoXfduP9LVq6hdK
ZChmFSlsAvFr1bqjM9xaZ6cF4r9lthawEO3NUDPJcFDsGY6wx/J0W2tExn2WuZgIWWbeKQGb9Cpt
0xU6kGpn8bRrZtkh68rZYnxGEFzedUlnnkL5/nWpo63/dgpnQOPF943HhZpZnmKaau1Fh5hnstVK
PNe0OwANwI8f4UDErmwh3El+fsqyjW22v5MvoVw+j8rtgI5Y4dtXz4U2OLJxpAmMkokIiEjxQGMY
sluMWuPD0xeqqxmjLBvk1cbiZnrXghmmOxYsL3GHX0WelXOTwkKBIROW1527k2gV+p2kHYzygeBY
Br3JtuP2iV2J+axEoctr+hbxx1A9JNr3w+SH1VbxT5Aw+kUJWdo0zuATHAR8ANSbhqRAvNncTFd+
rrcztl524WWLZt+NyteYr842mIycg5kDcPOvdO3GDjbnvezBc6eUWsuSZIKmAMFwoW4sKeFYV+xa
fJlrJaSQOoD0IJ2azsct+bJLKZWD6TWNp0lIpw9MGZHQ9b8Q4HECAwEAAaNCMEAwDwYDVR0TAQH/
BAUwAwEB/zAdBgNVHQ4EFgQUdEmZ0f+0emhFdcN+tNzMzjkz2ggwDgYDVR0PAQH/BAQDAgGGMA0G
CSqGSIb3DQEBDAUAA4ICAQAjQ1MkYlxt/T7Cz1UAbMVWiLkO3TriJQ2VSpfKgInuKs1l+NsW4AmS
4BjHeJi78+xCUvuppILXTdiK/ORO/auQxDh1MoSf/7OwKwIzNsAQkG8dnK/haZPso0UvFJ/1TCpl
Q3IM98P4lYsU84UgYt1UU90s3BiVaU+DR3BAM1h3Egyi61IxHkzJqM7F78PRreBrAwA0JrRUITWX
AdxfG/F851X6LWh3e9NpzNMOa7pNdkTWwhWaJuywxfW70Xp0wmzNxbVe9kzmWy2B27O3Opee7c9G
slA9hGCZcbUztVdF5kJHdWoOsAgMrr3e97sPWD2PAzHoPYJQyi9eDF20l74gNAf0xBLh7tew2Vkt
afcxBPTy+av5EzH4AXcOPUIjJsyacmdRIXrMPIWo6iFqO9taPKU0nprALN+AnCng33eU0aKAQv9q
TFsR0PXNor6uzFFcw9VUewyu1rkGd4Di7wcaaMxZUa1+XGdrudviB0JbuAEFWDlN5LuYo7Ey7Nmj
1m+UI/87tyll5gfp77YZ6ufCOB0yiJA8EytuzO+rdwY0d4RPcuSBhPm5dDTedk+SKlOxJTnbPP/l
PqYO5Wue/9vsL3SD3460s6neFE3/MaNFcyT6lSnMEpcEoji2jbDwN/zIIX8/syQbPYtuzE2wFg2W
HYMfRsCbvUOZ58SWLs5fyQ==
-----END CERTIFICATE-----
TrustAsia Global Root CA G3
===========================
-----BEGIN CERTIFICATE-----
MIIFpTCCA42gAwIBAgIUZPYOZXdhaqs7tOqFhLuxibhxkw8wDQYJKoZIhvcNAQEMBQAwWjELMAkG
A1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xJDAiBgNVBAMM
G1RydXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHMzAeFw0yMTA1MjAwMjEwMTlaFw00NjA1MTkwMjEw
MTlaMFoxCzAJBgNVBAYTAkNOMSUwIwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMu
MSQwIgYDVQQDDBtUcnVzdEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzMwggIiMA0GCSqGSIb3DQEBAQUA
A4ICDwAwggIKAoICAQDAMYJhkuSUGwoqZdC+BqmHO1ES6nBBruL7dOoKjbmzTNyPtxNST1QY4Sxz
lZHFZjtqz6xjbYdT8PfxObegQ2OwxANdV6nnRM7EoYNl9lA+sX4WuDqKAtCWHwDNBSHvBm3dIZwZ
Q0WhxeiAysKtQGIXBsaqvPPW5vxQfmZCHzyLpnl5hkA1nyDvP+uLRx+PjsXUjrYsyUQE49RDdT/V
P68czH5GX6zfZBCK70bwkPAPLfSIC7Epqq+FqklYqL9joDiR5rPmd2jE+SoZhLsO4fWvieylL1Ag
dB4SQXMeJNnKziyhWTXAyB1GJ2Faj/lN03J5Zh6fFZAhLf3ti1ZwA0pJPn9pMRJpxx5cynoTi+jm
9WAPzJMshH/x/Gr8m0ed262IPfN2dTPXS6TIi/n1Q1hPy8gDVI+lhXgEGvNz8teHHUGf59gXzhqc
D0r83ERoVGjiQTz+LISGNzzNPy+i2+f3VANfWdP3kXjHi3dqFuVJhZBFcnAvkV34PmVACxmZySYg
WmjBNb9Pp1Hx2BErW+Canig7CjoKH8GB5S7wprlppYiU5msTf9FkPz2ccEblooV7WIQn3MSAPmea
mseaMQ4w7OYXQJXZRe0Blqq/DPNL0WP3E1jAuPP6Z92bfW1K/zJMtSU7/xxnD4UiWQWRkUF3gdCF
TIcQcf+eQxuulXUtgQIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFEDk5PIj
7zjKsK5Xf/IhMBY027ySMB0GA1UdDgQWBBRA5OTyI+84yrCuV3/yITAWNNu8kjAOBgNVHQ8BAf8E
BAMCAQYwDQYJKoZIhvcNAQEMBQADggIBACY7UeFNOPMyGLS0XuFlXsSUT9SnYaP4wM8zAQLpw6o1
D/GUE3d3NZ4tVlFEbuHGLige/9rsR82XRBf34EzC4Xx8MnpmyFq2XFNFV1pF1AWZLy4jVe5jaN/T
G3inEpQGAHUNcoTpLrxaatXeL1nHo+zSh2bbt1S1JKv0Q3jbSwTEb93mPmY+KfJLaHEih6D4sTNj
duMNhXJEIlU/HHzp/LgV6FL6qj6jITk1dImmasI5+njPtqzn59ZW/yOSLlALqbUHM/Q4X6RJpstl
cHboCoWASzY9M/eVVHUl2qzEc4Jl6VL1XP04lQJqaTDFHApXB64ipCz5xUG3uOyfT0gA+QEEVcys
+TIxxHWVBqB/0Y0n3bOppHKH/lmLmnp0Ft0WpWIp6zqW3IunaFnT63eROfjXy9mPX1onAX1daBli
2MjN9LdyR75bl87yraKZk62Uy5P2EgmVtqvXO9A/EcswFi55gORngS1d7XB4tmBZrOFdRWOPyN9y
aFvqHbgB8X7754qz41SgOAngPN5C8sLtLpvzHzW2NtjjgKGLzZlkD8Kqq7HK9W+eQ42EVJmzbsAS
ZthwEPEGNTNDqJwuuhQxzhB/HIbjj9LV+Hfsm6vxL2PZQl/gZ4FkkfGXL/xuJvYz+NO1+MRiqzFR
JQJ6+N1rZdVtTTDIZbpoFGWsJwt0ivKH
-----END CERTIFICATE-----
TrustAsia Global Root CA G4
===========================
-----BEGIN CERTIFICATE-----
MIICVTCCAdygAwIBAgIUTyNkuI6XY57GU4HBdk7LKnQV1tcwCgYIKoZIzj0EAwMwWjELMAkGA1UE
BhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xJDAiBgNVBAMMG1Ry
dXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHNDAeFw0yMTA1MjAwMjEwMjJaFw00NjA1MTkwMjEwMjJa
MFoxCzAJBgNVBAYTAkNOMSUwIwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQw
IgYDVQQDDBtUcnVzdEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi
AATxs8045CVD5d4ZCbuBeaIVXxVjAd7Cq92zphtnS4CDr5nLrBfbK5bKfFJV4hrhPVbwLxYI+hW8
m7tH5j/uqOFMjPXTNvk4XatwmkcN4oFBButJ+bAp3TPsUKV/eSm4IJijYzBhMA8GA1UdEwEB/wQF
MAMBAf8wHwYDVR0jBBgwFoAUpbtKl86zK3+kMd6Xg1mDpm9xy94wHQYDVR0OBBYEFKW7SpfOsyt/
pDHel4NZg6ZvccveMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjBe8usGzEkxn0AA
bbd+NvBNEU/zy4k6LHiRUKNbwMp1JvK/kF0LgoxgKJ/GcJpo5PECMFxYDlZ2z1jD1xCMuo6u47xk
dUfFVZDj/bpV6wfEU6s3qe4hsiFbYI89MvHVI5TWWA==
-----END CERTIFICATE-----
CommScope Public Trust ECC Root-01
==================================
-----BEGIN CERTIFICATE-----
MIICHTCCAaOgAwIBAgIUQ3CCd89NXTTxyq4yLzf39H91oJ4wCgYIKoZIzj0EAwMwTjELMAkGA1UE
BhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1YmxpYyBUcnVz
dCBFQ0MgUm9vdC0wMTAeFw0yMTA0MjgxNzM1NDNaFw00NjA0MjgxNzM1NDJaME4xCzAJBgNVBAYT
AlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3Qg
RUNDIFJvb3QtMDEwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARLNumuV16ocNfQj3Rid8NeeqrltqLx
eP0CflfdkXmcbLlSiFS8LwS+uM32ENEp7LXQoMPwiXAZu1FlxUOcw5tjnSCDPgYLpkJEhRGnSjot
6dZoL0hOUysHP029uax3OVejQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G
A1UdDgQWBBSOB2LAUN3GGQYARnQE9/OufXVNMDAKBggqhkjOPQQDAwNoADBlAjEAnDPfQeMjqEI2
Jpc1XHvr20v4qotzVRVcrHgpD7oh2MSg2NED3W3ROT3Ek2DS43KyAjB8xX6I01D1HiXo+k515liW
pDVfG2XqYZpwI7UNo5uSUm9poIyNStDuiw7LR47QjRE=
-----END CERTIFICATE-----
CommScope Public Trust ECC Root-02
==================================
-----BEGIN CERTIFICATE-----
MIICHDCCAaOgAwIBAgIUKP2ZYEFHpgE6yhR7H+/5aAiDXX0wCgYIKoZIzj0EAwMwTjELMAkGA1UE
BhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1YmxpYyBUcnVz
dCBFQ0MgUm9vdC0wMjAeFw0yMTA0MjgxNzQ0NTRaFw00NjA0MjgxNzQ0NTNaME4xCzAJBgNVBAYT
AlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3Qg
RUNDIFJvb3QtMDIwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAR4MIHoYx7l63FRD/cHB8o5mXxO1Q/M
MDALj2aTPs+9xYa9+bG3tD60B8jzljHz7aRP+KNOjSkVWLjVb3/ubCK1sK9IRQq9qEmUv4RDsNuE
SgMjGWdqb8FuvAY5N9GIIvejQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G
A1UdDgQWBBTmGHX/72DehKT1RsfeSlXjMjZ59TAKBggqhkjOPQQDAwNnADBkAjAmc0l6tqvmSfR9
Uj/UQQSugEODZXW5hYA4O9Zv5JOGq4/nich/m35rChJVYaoR4HkCMHfoMXGsPHED1oQmHhS48zs7
3u1Z/GtMMH9ZzkXpc2AVmkzw5l4lIhVtwodZ0LKOag==
-----END CERTIFICATE-----
CommScope Public Trust RSA Root-01
==================================
-----BEGIN CERTIFICATE-----
MIIFbDCCA1SgAwIBAgIUPgNJgXUWdDGOTKvVxZAplsU5EN0wDQYJKoZIhvcNAQELBQAwTjELMAkG
A1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1YmxpYyBU
cnVzdCBSU0EgUm9vdC0wMTAeFw0yMTA0MjgxNjQ1NTRaFw00NjA0MjgxNjQ1NTNaME4xCzAJBgNV
BAYTAlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1
c3QgUlNBIFJvb3QtMDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwSGWjDR1C45Ft
nYSkYZYSwu3D2iM0GXb26v1VWvZVAVMP8syMl0+5UMuzAURWlv2bKOx7dAvnQmtVzslhsuitQDy6
uUEKBU8bJoWPQ7VAtYXR1HHcg0Hz9kXHgKKEUJdGzqAMxGBWBB0HW0alDrJLpA6lfO741GIDuZNq
ihS4cPgugkY4Iw50x2tBt9Apo52AsH53k2NC+zSDO3OjWiE260f6GBfZumbCk6SP/F2krfxQapWs
vCQz0b2If4b19bJzKo98rwjyGpg/qYFlP8GMicWWMJoKz/TUyDTtnS+8jTiGU+6Xn6myY5QXjQ/c
Zip8UlF1y5mO6D1cv547KI2DAg+pn3LiLCuz3GaXAEDQpFSOm117RTYm1nJD68/A6g3czhLmfTif
BSeolz7pUcZsBSjBAg/pGG3svZwG1KdJ9FQFa2ww8esD1eo9anbCyxooSU1/ZOD6K9pzg4H/kQO9
lLvkuI6cMmPNn7togbGEW682v3fuHX/3SZtS7NJ3Wn2RnU3COS3kuoL4b/JOHg9O5j9ZpSPcPYeo
KFgo0fEbNttPxP/hjFtyjMcmAyejOQoBqsCyMWCDIqFPEgkBEa801M/XrmLTBQe0MXXgDW1XT2mH
+VepuhX2yFJtocucH+X8eKg1mp9BFM6ltM6UCBwJrVbl2rZJmkrqYxhTnCwuwwIDAQABo0IwQDAP
BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUN12mmnQywsL5x6YVEFm4
5P3luG0wDQYJKoZIhvcNAQELBQADggIBAK+nz97/4L1CjU3lIpbfaOp9TSp90K09FlxD533Ahuh6
NWPxzIHIxgvoLlI1pKZJkGNRrDSsBTtXAOnTYtPZKdVUvhwQkZyybf5Z/Xn36lbQnmhUQo8mUuJM
3y+Xpi/SB5io82BdS5pYV4jvguX6r2yBS5KPQJqTRlnLX3gWsWc+QgvfKNmwrZggvkN80V4aCRck
jXtdlemrwWCrWxhkgPut4AZ9HcpZuPN4KWfGVh2vtrV0KnahP/t1MJ+UXjulYPPLXAziDslg+Mkf
Foom3ecnf+slpoq9uC02EJqxWE2aaE9gVOX2RhOOiKy8IUISrcZKiX2bwdgt6ZYD9KJ0DLwAHb/W
NyVntHKLr4W96ioDj8z7PEQkguIBpQtZtjSNMgsSDesnwv1B10A8ckYpwIzqug/xBpMu95yo9GA+
o/E4Xo4TwbM6l4c/ksp4qRyv0LAbJh6+cOx69TOY6lz/KwsETkPdY34Op054A5U+1C0wlREQKC6/
oAI+/15Z0wUOlV9TRe9rh9VIzRamloPh37MG88EU26fsHItdkJANclHnYfkUyq+Dj7+vsQpZXdxc
1+SWrVtgHdqul7I52Qb1dgAT+GhMIbA1xNxVssnBQVocicCMb3SgazNNtQEo/a2tiRc7ppqEvOuM
6sRxJKi6KfkIsidWNTJf6jn7MZrVGczw
-----END CERTIFICATE-----
CommScope Public Trust RSA Root-02
==================================
-----BEGIN CERTIFICATE-----
MIIFbDCCA1SgAwIBAgIUVBa/O345lXGN0aoApYYNK496BU4wDQYJKoZIhvcNAQELBQAwTjELMAkG
A1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1YmxpYyBU
cnVzdCBSU0EgUm9vdC0wMjAeFw0yMTA0MjgxNzE2NDNaFw00NjA0MjgxNzE2NDJaME4xCzAJBgNV
BAYTAlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1
c3QgUlNBIFJvb3QtMDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDh+g77aAASyE3V
rCLENQE7xVTlWXZjpX/rwcRqmL0yjReA61260WI9JSMZNRTpf4mnG2I81lDnNJUDMrG0kyI9p+Kx
7eZ7Ti6Hmw0zdQreqjXnfuU2mKKuJZ6VszKWpCtYHu8//mI0SFHRtI1CrWDaSWqVcN3SAOLMV2MC
e5bdSZdbkk6V0/nLKR8YSvgBKtJjCW4k6YnS5cciTNxzhkcAqg2Ijq6FfUrpuzNPDlJwnZXjfG2W
Wy09X6GDRl224yW4fKcZgBzqZUPckXk2LHR88mcGyYnJ27/aaL8j7dxrrSiDeS/sOKUNNwFnJ5rp
M9kzXzehxfCrPfp4sOcsn/Y+n2Dg70jpkEUeBVF4GiwSLFworA2iI540jwXmojPOEXcT1A6kHkIf
hs1w/tkuFT0du7jyU1fbzMZ0KZwYszZ1OC4PVKH4kh+Jlk+71O6d6Ts2QrUKOyrUZHk2EOH5kQMr
eyBUzQ0ZGshBMjTRsJnhkB4BQDa1t/qp5Xd1pCKBXbCL5CcSD1SIxtuFdOa3wNemKfrb3vOTlycE
VS8KbzfFPROvCgCpLIscgSjX74Yxqa7ybrjKaixUR9gqiC6vwQcQeKwRoi9C8DfF8rhW3Q5iLc4t
Vn5V8qdE9isy9COoR+jUKgF4z2rDN6ieZdIs5fq6M8EGRPbmz6UNp2YINIos8wIDAQABo0IwQDAP
BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUR9DnsSL/nSz12Vdgs7Gx
cJXvYXowDQYJKoZIhvcNAQELBQADggIBAIZpsU0v6Z9PIpNojuQhmaPORVMbc0RTAIFhzTHjCLqB
KCh6krm2qMhDnscTJk3C2OVVnJJdUNjCK9v+5qiXz1I6JMNlZFxHMaNlNRPDk7n3+VGXu6TwYofF
1gbTl4MgqX67tiHCpQ2EAOHyJxCDut0DgdXdaMNmEMjRdrSzbymeAPnCKfWxkxlSaRosTKCL4BWa
MS/TiJVZbuXEs1DIFAhKm4sTg7GkcrI7djNB3NyqpgdvHSQSn8h2vS/ZjvQs7rfSOBAkNlEv41xd
gSGn2rtO/+YHqP65DSdsu3BaVXoT6fEqSWnHX4dXTEN5bTpl6TBcQe7rd6VzEojov32u5cSoHw2O
HG1QAk8mGEPej1WFsQs3BWDJVTkSBKEqz3EWnzZRSb9wO55nnPt7eck5HHisd5FUmrh1CoFSl+Nm
YWvtPjgelmFV4ZFUjO2MJB+ByRCac5krFk5yAD9UG/iNuovnFNa2RU9g7Jauwy8CTl2dlklyALKr
dVwPaFsdZcJfMw8eD/A7hvWwTruc9+olBdytoptLFwG+Qt81IR2tq670v64fG9PiO/yzcnMcmyiQ
iRM9HcEARwmWmjgb3bHPDcK0RPOWlc4yOo80nOAXx17Org3bhzjlP1v9mxnhMUF6cKojawHhRUzN
lM47ni3niAIi9G7oyOzWPPO5std3eqx7
-----END CERTIFICATE-----
Telekom Security TLS ECC Root 2020
==================================
-----BEGIN CERTIFICATE-----
MIICQjCCAcmgAwIBAgIQNjqWjMlcsljN0AFdxeVXADAKBggqhkjOPQQDAzBjMQswCQYDVQQGEwJE
RTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBHbWJIMSswKQYDVQQDDCJUZWxl
a29tIFNlY3VyaXR5IFRMUyBFQ0MgUm9vdCAyMDIwMB4XDTIwMDgyNTA3NDgyMFoXDTQ1MDgyNTIz
NTk1OVowYzELMAkGA1UEBhMCREUxJzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkg
R21iSDErMCkGA1UEAwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgRUNDIFJvb3QgMjAyMDB2MBAGByqG
SM49AgEGBSuBBAAiA2IABM6//leov9Wq9xCazbzREaK9Z0LMkOsVGJDZos0MKiXrPk/OtdKPD/M1
2kOLAoC+b1EkHQ9rK8qfwm9QMuU3ILYg/4gND21Ju9sGpIeQkpT0CdDPf8iAC8GXs7s1J8nCG6NC
MEAwHQYDVR0OBBYEFONyzG6VmUex5rNhTNHLq+O6zd6fMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P
AQH/BAQDAgEGMAoGCCqGSM49BAMDA2cAMGQCMHVSi7ekEE+uShCLsoRbQuHmKjYC2qBuGT8lv9pZ
Mo7k+5Dck2TOrbRBR2Diz6fLHgIwN0GMZt9Ba9aDAEH9L1r3ULRn0SyocddDypwnJJGDSA3PzfdU
ga/sf+Rn27iQ7t0l
-----END CERTIFICATE-----
Telekom Security TLS RSA Root 2023
==================================
-----BEGIN CERTIFICATE-----
MIIFszCCA5ugAwIBAgIQIZxULej27HF3+k7ow3BXlzANBgkqhkiG9w0BAQwFADBjMQswCQYDVQQG
EwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBHbWJIMSswKQYDVQQDDCJU
ZWxla29tIFNlY3VyaXR5IFRMUyBSU0EgUm9vdCAyMDIzMB4XDTIzMDMyODEyMTY0NVoXDTQ4MDMy
NzIzNTk1OVowYzELMAkGA1UEBhMCREUxJzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJp
dHkgR21iSDErMCkGA1UEAwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgUlNBIFJvb3QgMjAyMzCCAiIw
DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAO01oYGA88tKaVvC+1GDrib94W7zgRJ9cUD/h3VC
KSHtgVIs3xLBGYSJwb3FKNXVS2xE1kzbB5ZKVXrKNoIENqil/Cf2SfHVcp6R+SPWcHu79ZvB7JPP
GeplfohwoHP89v+1VmLhc2o0mD6CuKyVU/QBoCcHcqMAU6DksquDOFczJZSfvkgdmOGjup5czQRx
UX11eKvzWarE4GC+j4NSuHUaQTXtvPM6Y+mpFEXX5lLRbtLevOP1Czvm4MS9Q2QTps70mDdsipWo
l8hHD/BeEIvnHRz+sTugBTNoBUGCwQMrAcjnj02r6LX2zWtEtefdi+zqJbQAIldNsLGyMcEWzv/9
FIS3R/qy8XDe24tsNlikfLMR0cN3f1+2JeANxdKz+bi4d9s3cXFH42AYTyS2dTd4uaNir73Jco4v
zLuu2+QVUhkHM/tqty1LkCiCc/4YizWN26cEar7qwU02OxY2kTLvtkCJkUPg8qKrBC7m8kwOFjQg
rIfBLX7JZkcXFBGk8/ehJImr2BrIoVyxo/eMbcgByU/J7MT8rFEz0ciD0cmfHdRHNCk+y7AO+oML
KFjlKdw/fKifybYKu6boRhYPluV75Gp6SG12mAWl3G0eQh5C2hrgUve1g8Aae3g1LDj1H/1Joy7S
WWO/gLCMk3PLNaaZlSJhZQNg+y+TS/qanIA7AgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAdBgNV
HQ4EFgQUtqeXgj10hZv3PJ+TmpV5dVKMbUcwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS2
p5eCPXSFm/c8n5OalXl1UoxtRzANBgkqhkiG9w0BAQwFAAOCAgEAqMxhpr51nhVQpGv7qHBFfLp+
sVr8WyP6Cnf4mHGCDG3gXkaqk/QeoMPhk9tLrbKmXauw1GLLXrtm9S3ul0A8Yute1hTWjOKWi0Fp
kzXmuZlrYrShF2Y0pmtjxrlO8iLpWA1WQdH6DErwM807u20hOq6OcrXDSvvpfeWxm4bu4uB9tPcy
/SKE8YXJN3nptT+/XOR0so8RYgDdGGah2XsjX/GO1WfoVNpbOms2b/mBsTNHM3dA+VKq3dSDz4V4
mZqTuXNnQkYRIer+CqkbGmVps4+uFrb2S1ayLfmlyOw7YqPta9BO1UAJpB+Y1zqlklkg5LB9zVtz
aL1txKITDmcZuI1CfmwMmm6gJC3VRRvcxAIU/oVbZZfKTpBQCHpCNfnqwmbU+AGuHrS+w6jv/naa
oqYfRvaE7fzbzsQCzndILIyy7MMAo+wsVRjBfhnu4S/yrYObnqsZ38aKL4x35bcF7DvB7L6Gs4a8
wPfc5+pbrrLMtTWGS9DiP7bY+A4A7l3j941Y/8+LN+ljX273CXE2whJdV/LItM3z7gLfEdxquVeE
HVlNjM7IDiPCtyaaEBRx/pOyiriA8A4QntOoUAw3gi/q4Iqd4Sw5/7W0cwDk90imc6y/st53BIe0
o82bNSQ3+pCTE4FCxpgmdTdmQRCsu/WU48IxK63nI1bMNSWSs1A=
-----END CERTIFICATE-----
FIRMAPROFESIONAL CA ROOT-A WEB
==============================
-----BEGIN CERTIFICATE-----
MIICejCCAgCgAwIBAgIQMZch7a+JQn81QYehZ1ZMbTAKBggqhkjOPQQDAzBuMQswCQYDVQQGEwJF
UzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25hbCBTQTEYMBYGA1UEYQwPVkFURVMtQTYyNjM0MDY4
MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFMIENBIFJPT1QtQSBXRUIwHhcNMjIwNDA2MDkwMTM2
WhcNNDcwMzMxMDkwMTM2WjBuMQswCQYDVQQGEwJFUzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25h
bCBTQTEYMBYGA1UEYQwPVkFURVMtQTYyNjM0MDY4MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFM
IENBIFJPT1QtQSBXRUIwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARHU+osEaR3xyrq89Zfe9MEkVz6
iMYiuYMQYneEMy3pA4jU4DP37XcsSmDq5G+tbbT4TIqk5B/K6k84Si6CcyvHZpsKjECcfIr28jlg
st7L7Ljkb+qbXbdTkBgyVcUgt5SjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUk+FD
Y1w8ndYn81LsF7Kpryz3dvgwHQYDVR0OBBYEFJPhQ2NcPJ3WJ/NS7Beyqa8s93b4MA4GA1UdDwEB
/wQEAwIBBjAKBggqhkjOPQQDAwNoADBlAjAdfKR7w4l1M+E7qUW/Runpod3JIha3RxEL2Jq68cgL
cFBTApFwhVmpHqTm6iMxoAACMQD94vizrxa5HnPEluPBMBnYfubDl94cT7iJLzPrSA8Z94dGXSaQ
pYXFuXqUPoeovQA=
-----END CERTIFICATE-----
TWCA CYBER Root CA
==================
-----BEGIN CERTIFICATE-----
MIIFjTCCA3WgAwIBAgIQQAE0jMIAAAAAAAAAATzyxjANBgkqhkiG9w0BAQwFADBQMQswCQYDVQQG
EwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJUV0NB
IENZQkVSIFJvb3QgQ0EwHhcNMjIxMTIyMDY1NDI5WhcNNDcxMTIyMTU1OTU5WjBQMQswCQYDVQQG
EwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJUV0NB
IENZQkVSIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDG+Moe2Qkgfh1s
Ts6P40czRJzHyWmqOlt47nDSkvgEs1JSHWdyKKHfi12VCv7qze33Kc7wb3+szT3vsxxFavcokPFh
V8UMxKNQXd7UtcsZyoC5dc4pztKFIuwCY8xEMCDa6pFbVuYdHNWdZsc/34bKS1PE2Y2yHer43CdT
o0fhYcx9tbD47nORxc5zb87uEB8aBs/pJ2DFTxnk684iJkXXYJndzk834H/nY62wuFm40AZoNWDT
Nq5xQwTxaWV4fPMf88oon1oglWa0zbfuj3ikRRjpJi+NmykosaS3Om251Bw4ckVYsV7r8Cibt4LK
/c/WMw+f+5eesRycnupfXtuq3VTpMCEobY5583WSjCb+3MX2w7DfRFlDo7YDKPYIMKoNM+HvnKkH
IuNZW0CP2oi3aQiotyMuRAlZN1vH4xfyIutuOVLF3lSnmMlLIJXcRolftBL5hSmO68gnFSDAS9TM
fAxsNAwmmyYxpjyn9tnQS6Jk/zuZQXLB4HCX8SS7K8R0IrGsayIyJNN4KsDAoS/xUgXJP+92ZuJF
2A09rZXIx4kmyA+upwMu+8Ff+iDhcK2wZSA3M2Cw1a/XDBzCkHDXShi8fgGwsOsVHkQGzaRP6AzR
wyAQ4VRlnrZR0Bp2a0JaWHY06rc3Ga4udfmW5cFZ95RXKSWNOkyrTZpB0F8mAwIDAQABo2MwYTAO
BgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSdhWEUfMFib5do5E83
QOGt4A1WNzAdBgNVHQ4EFgQUnYVhFHzBYm+XaORPN0DhreANVjcwDQYJKoZIhvcNAQEMBQADggIB
AGSPesRiDrWIzLjHhg6hShbNcAu3p4ULs3a2D6f/CIsLJc+o1IN1KriWiLb73y0ttGlTITVX1olN
c79pj3CjYcya2x6a4CD4bLubIp1dhDGaLIrdaqHXKGnK/nZVekZn68xDiBaiA9a5F/gZbG0jAn/x
X9AKKSM70aoK7akXJlQKTcKlTfjF/biBzysseKNnTKkHmvPfXvt89YnNdJdhEGoHK4Fa0o635yDR
IG4kqIQnoVesqlVYL9zZyvpoBJ7tRCT5dEA7IzOrg1oYJkK2bVS1FmAwbLGg+LhBoF1JSdJlBTrq
/p1hvIbZv97Tujqxf36SNI7JAG7cmL3c7IAFrQI932XtCwP39xaEBDG6k5TY8hL4iuO/Qq+n1M0R
FxbIQh0UqEL20kCGoE8jypZFVmAGzbdVAaYBlGX+bgUJurSkquLvWL69J1bY73NxW0Qz8ppy6rBe
Pm6pUlvscG21h483XjyMnM7k8M4MZ0HMzvaAq07MTFb1wWFZk7Q+ptq4NxKfKjLji7gh7MMrZQzv
It6IKTtM1/r+t+FHvpw+PoP7UV31aPcuIYXcv/Fa4nzXxeSDwWrruoBa3lwtcHb4yOWHh8qgnaHl
IhInD0Q9HWzq1MKLL295q39QpsQZp6F6t5b5wR9iWqJDB0BeJsas7a5wFsWqynKKTbDPAYsDP27X
-----END CERTIFICATE-----
SecureSign Root CA12
====================
-----BEGIN CERTIFICATE-----
MIIDcjCCAlqgAwIBAgIUZvnHwa/swlG07VOX5uaCwysckBYwDQYJKoZIhvcNAQELBQAwUTELMAkG
A1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBMdGQuMR0wGwYDVQQDExRT
ZWN1cmVTaWduIFJvb3QgQ0ExMjAeFw0yMDA0MDgwNTM2NDZaFw00MDA0MDgwNTM2NDZaMFExCzAJ
BgNVBAYTAkpQMSMwIQYDVQQKExpDeWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMU
U2VjdXJlU2lnbiBSb290IENBMTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6OcE3
emhFKxS06+QT61d1I02PJC0W6K6OyX2kVzsqdiUzg2zqMoqUm048luT9Ub+ZyZN+v/mtp7JIKwcc
J/VMvHASd6SFVLX9kHrko+RRWAPNEHl57muTH2SOa2SroxPjcf59q5zdJ1M3s6oYwlkm7Fsf0uZl
fO+TvdhYXAvA42VvPMfKWeP+bl+sg779XSVOKik71gurFzJ4pOE+lEa+Ym6b3kaosRbnhW70CEBF
EaCeVESE99g2zvVQR9wsMJvuwPWW0v4JhscGWa5Pro4RmHvzC1KqYiaqId+OJTN5lxZJjfU+1Uef
NzFJM3IFTQy2VYzxV4+Kh9GtxRESOaCtAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P
AQH/BAQDAgEGMB0GA1UdDgQWBBRXNPN0zwRL1SXm8UC2LEzZLemgrTANBgkqhkiG9w0BAQsFAAOC
AQEAPrvbFxbS8hQBICw4g0utvsqFepq2m2um4fylOqyttCg6r9cBg0krY6LdmmQOmFxv3Y67ilQi
LUoT865AQ9tPkbeGGuwAtEGBpE/6aouIs3YIcipJQMPTw4WJmBClnW8Zt7vPemVV2zfrPIpyMpce
mik+rY3moxtt9XUa5rBouVui7mlHJzWhhpmA8zNL4WukJsPvdFlseqJkth5Ew1DgDzk9qTPxpfPS
vWKErI4cqc1avTc7bgoitPQV55FYxTpE05Uo2cBl6XLK0A+9H7MV2anjpEcJnuDLN/v9vZfVvhga
aaI5gdka9at/yOPiZwud9AzqVN/Ssq+xIvEg37xEHA==
-----END CERTIFICATE-----
SecureSign Root CA14
====================
-----BEGIN CERTIFICATE-----
MIIFcjCCA1qgAwIBAgIUZNtaDCBO6Ncpd8hQJ6JaJ90t8sswDQYJKoZIhvcNAQEMBQAwUTELMAkG
A1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBMdGQuMR0wGwYDVQQDExRT
ZWN1cmVTaWduIFJvb3QgQ0ExNDAeFw0yMDA0MDgwNzA2MTlaFw00NTA0MDgwNzA2MTlaMFExCzAJ
BgNVBAYTAkpQMSMwIQYDVQQKExpDeWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMU
U2VjdXJlU2lnbiBSb290IENBMTQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDF0nqh
1oq/FjHQmNE6lPxauG4iwWL3pwon71D2LrGeaBLwbCRjOfHw3xDG3rdSINVSW0KZnvOgvlIfX8xn
bacuUKLBl422+JX1sLrcneC+y9/3OPJH9aaakpUqYllQC6KxNedlsmGy6pJxaeQp8E+BgQQ8sqVb
1MWoWWd7VRxJq3qdwudzTe/NCcLEVxLbAQ4jeQkHO6Lo/IrPj8BGJJw4J+CDnRugv3gVEOuGTgpa
/d/aLIJ+7sr2KeH6caH3iGicnPCNvg9JkdjqOvn90Ghx2+m1K06Ckm9mH+Dw3EzsytHqunQG+bOE
kJTRX45zGRBdAuVwpcAQ0BB8b8VYSbSwbprafZX1zNoCr7gsfXmPvkPx+SgojQlD+Ajda8iLLCSx
jVIHvXiby8posqTdDEx5YMaZ0ZPxMBoH064iwurO8YQJzOAUbn8/ftKChazcqRZOhaBgy/ac18iz
ju3Gm5h1DVXoX+WViwKkrkMpKBGk5hIwAUt1ax5mnXkvpXYvHUC0bcl9eQjs0Wq2XSqypWa9a4X0
dFbD9ed1Uigspf9mR6XU/v6eVL9lfgHWMI+lNpyiUBzuOIABSMbHdPTGrMNASRZhdCyvjG817XsY
AFs2PJxQDcqSMxDxJklt33UkN4Ii1+iW/RVLApY+B3KVfqs9TC7XyvDf4Fg/LS8EmjijAQIDAQAB
o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUBpOjCl4oaTeq
YR3r6/wtbyPk86AwDQYJKoZIhvcNAQEMBQADggIBAJaAcgkGfpzMkwQWu6A6jZJOtxEaCnFxEM0E
rX+lRVAQZk5KQaID2RFPeje5S+LGjzJmdSX7684/AykmjbgWHfYfM25I5uj4V7Ibed87hwriZLoA
ymzvftAj63iP/2SbNDefNWWipAA9EiOWWF3KY4fGoweITedpdopTzfFP7ELyk+OZpDc8h7hi2/Ds
Hzc/N19DzFGdtfCXwreFamgLRB7lUe6TzktuhsHSDCRZNhqfLJGP4xjblJUK7ZGqDpncllPjYYPG
FrojutzdfhrGe0K22VoF3Jpf1d+42kd92jjbrDnVHmtsKheMYc2xbXIBw8MgAGJoFjHVdqqGuw6q
nsb58Nn4DSEC5MUoFlkRudlpcyqSeLiSV5sI8jrlL5WwWLdrIBRtFO8KvH7YVdiI2i/6GaX7i+B/
OfVyK4XELKzvGUWSTLNhB9xNH27SgRNcmvMSZ4PPmz+Ln52kuaiWA3rF7iDeM9ovnhp6dB7h7sxa
OgTdsxoEqBRjrLdHEoOabPXm6RUVkRqEGQ6UROcSjiVbgGcZ3GOTEAtlLor6CZpO2oYofaphNdgO
pygau1LgePhsumywbrmHXumZNTfxPWQrqaA0k89jL9WB365jJ6UeTo3cKXhZ+PmhIIynJkBugnLN
eLLIjzwec+fBH7/PzqUqm9tEZDKgu39cJRNItX+S
-----END CERTIFICATE-----
SecureSign Root CA15
====================
-----BEGIN CERTIFICATE-----
MIICIzCCAamgAwIBAgIUFhXHw9hJp75pDIqI7fBw+d23PocwCgYIKoZIzj0EAwMwUTELMAkGA1UE
BhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBMdGQuMR0wGwYDVQQDExRTZWN1
cmVTaWduIFJvb3QgQ0ExNTAeFw0yMDA0MDgwODMyNTZaFw00NTA0MDgwODMyNTZaMFExCzAJBgNV
BAYTAkpQMSMwIQYDVQQKExpDeWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2Vj
dXJlU2lnbiBSb290IENBMTUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQLUHSNZDKZmbPSYAi4Io5G
dCx4wCtELW1fHcmuS1Iggz24FG1Th2CeX2yF2wYUleDHKP+dX+Sq8bOLbe1PL0vJSpSRZHX+AezB
2Ot6lHhWGENfa4HL9rzatAy2KZMIaY+jQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD
AgEGMB0GA1UdDgQWBBTrQciu/NWeUUj1vYv0hyCTQSvT9DAKBggqhkjOPQQDAwNoADBlAjEA2S6J
fl5OpBEHvVnCB96rMjhTKkZEBhd6zlHp4P9mLQlO4E/0BdGF9jVg3PVys0Z9AjBEmEYagoUeYWmJ
SwdLZrWeqrqgHkHZAXQ6bkU6iYAZezKYVWOr62Nuk22rGwlgMU4=
-----END CERTIFICATE-----
D-TRUST BR Root CA 2 2023
=========================
-----BEGIN CERTIFICATE-----
MIIFqTCCA5GgAwIBAgIQczswBEhb2U14LnNLyaHcZjANBgkqhkiG9w0BAQ0FADBIMQswCQYDVQQG
EwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEJSIFJvb3QgQ0Eg
MiAyMDIzMB4XDTIzMDUwOTA4NTYzMVoXDTM4MDUwOTA4NTYzMFowSDELMAkGA1UEBhMCREUxFTAT
BgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDIgMjAyMzCC
AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK7/CVmRgApKaOYkP7in5Mg6CjoWzckjYaCT
cfKri3OPoGdlYNJUa2NRb0kz4HIHE304zQaSBylSa053bATTlfrdTIzZXcFhfUvnKLNEgXtRr90z
sWh81k5M/itoucpmacTsXld/9w3HnDY25QdgrMBM6ghs7wZ8T1soegj8k12b9py0i4a6Ibn08OhZ
WiihNIQaJZG2tY/vsvmA+vk9PBFy2OMvhnbFeSzBqZCTRphny4NqoFAjpzv2gTng7fC5v2Xx2Mt6
++9zA84A9H3X4F07ZrjcjrqDy4d2A/wl2ecjbwb9Z/Pg/4S8R7+1FhhGaRTMBffb00msa8yr5LUL
QyReS2tNZ9/WtT5PeB+UcSTq3nD88ZP+npNa5JRal1QMNXtfbO4AHyTsA7oC9Xb0n9Sa7YUsOCIv
x9gvdhFP/Wxc6PWOJ4d/GUohR5AdeY0cW/jPSoXk7bNbjb7EZChdQcRurDhaTyN0dKkSw/bSuREV
MweR2Ds3OmMwBtHFIjYoYiMQ4EbMl6zWK11kJNXuHA7e+whadSr2Y23OC0K+0bpwHJwh5Q8xaRfX
/Aq03u2AnMuStIv13lmiWAmlY0cL4UEyNEHZmrHZqLAbWt4NDfTisl01gLmB1IRpkQLLddCNxbU9
CZEJjxShFHR5PtbJFR2kWVki3PaKRT08EtY+XTIvAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB
/zAdBgNVHQ4EFgQUZ5Dw1t61GNVGKX5cq/ieCLxklRAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRC
MEAwPqA8oDqGOGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfYnJfcm9vdF9jYV8y
XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQA097N3U9swFrktpSHxQCF16+tIFoE9c+CeJyrr
d6kTpGoKWloUMz1oH4Guaf2Mn2VsNELZLdB/eBaxOqwjMa1ef67nriv6uvw8l5VAk1/DLQOj7aRv
U9f6QA4w9QAgLABMjDu0ox+2v5Eyq6+SmNMW5tTRVFxDWy6u71cqqLRvpO8NVhTaIasgdp4D/Ca4
nj8+AybmTNudX0KEPUUDAxxZiMrcLmEkWqTqJwtzEr5SswrPMhfiHocaFpVIbVrg0M8JkiZmkdij
YQ6qgYF/6FKC0ULn4B0Y+qSFNueG4A3rvNTJ1jxD8V1Jbn6Bm2m1iWKPiFLY1/4nwSPFyysCu7Ff
/vtDhQNGvl3GyiEm/9cCnnRK3PgTFbGBVzbLZVzRHTF36SXDw7IyN9XxmAnkbWOACKsGkoHU6XCP
pz+y7YaMgmo1yEJagtFSGkUPFaUA8JR7ZSdXOUPPfH/mvTWze/EZTN46ls/pdu4D58JDUjxqgejB
WoC9EV2Ta/vH5mQ/u2kc6d0li690yVRAysuTEwrt+2aSEcr1wPrYg1UDfNPFIkZ1cGt5SAYqgpq/
5usWDiJFAbzdNpQ0qTUmiteXue4Icr80knCDgKs4qllo3UCkGJCy89UDyibK79XH4I9TjvAA46jt
n/mtd+ArY0+ew+43u3gJhJ65bvspmZDogNOfJA==
-----END CERTIFICATE-----
D-TRUST EV Root CA 2 2023
=========================
-----BEGIN CERTIFICATE-----
MIIFqTCCA5GgAwIBAgIQaSYJfoBLTKCnjHhiU19abzANBgkqhkiG9w0BAQ0FADBIMQswCQYDVQQG
EwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEVWIFJvb3QgQ0Eg
MiAyMDIzMB4XDTIzMDUwOTA5MTAzM1oXDTM4MDUwOTA5MTAzMlowSDELMAkGA1UEBhMCREUxFTAT
BgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDIgMjAyMzCC
AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANiOo4mAC7JXUtypU0w3uX9jFxPvp1sjW2l1
sJkKF8GLxNuo4MwxusLyzV3pt/gdr2rElYfXR8mV2IIEUD2BCP/kPbOx1sWy/YgJ25yE7CUXFId/
MHibaljJtnMoPDT3mfd/06b4HEV8rSyMlD/YZxBTfiLNTiVR8CUkNRFeEMbsh2aJgWi6zCudR3Mf
vc2RpHJqnKIbGKBv7FD0fUDCqDDPvXPIEysQEx6Lmqg6lHPTGGkKSv/BAQP/eX+1SH977ugpbzZM
lWGG2Pmic4ruri+W7mjNPU0oQvlFKzIbRlUWaqZLKfm7lVa/Rh3sHZMdwGWyH6FDrlaeoLGPaxK3
YG14C8qKXO0elg6DpkiVjTujIcSuWMYAsoS0I6SWhjW42J7YrDRJmGOVxcttSEfi8i4YHtAxq910
7PncjLgcjmgjutDzUNzPZY9zOjLHfP7KgiJPvo5iR2blzYfi6NUPGJ/lBHJLRjwQ8kTCZFZxTnXo
nMkmdMV9WdEKWw9t/p51HBjGGjp82A0EzM23RWV6sY+4roRIPrN6TagD4uJ+ARZZaBhDM7DS3LAa
QzXupdqpRlyuhoFBAUp0JuyfBr/CBTdkdXgpaP3F9ev+R/nkhbDhezGdpn9yo7nELC7MmVcOIQxF
AZRl62UJxmMiCzNJkkg8/M3OsD6Onov4/knFNXJHAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB
/zAdBgNVHQ4EFgQUqvyREBuHkV8Wub9PS5FeAByxMoAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRC
MEAwPqA8oDqGOGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfZXZfcm9vdF9jYV8y
XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQCTy6UfmRHsmg1fLBWTxj++EI14QvBukEdHjqOS
Mo1wj/Zbjb6JzkcBahsgIIlbyIIQbODnmaprxiqgYzWRaoUlrRc4pZt+UPJ26oUFKidBK7GB0aL2
QHWpDsvxVUjY7NHss+jOFKE17MJeNRqrphYBBo7q3C+jisosketSjl8MmxfPy3MHGcRqwnNU73xD
UmPBEcrCRbH0O1P1aa4846XerOhUt7KR/aypH/KH5BfGSah82ApB9PI+53c0BFLd6IHyTS9URZ0V
4U/M5d40VxDJI3IXcI1QcB9WbMy5/zpaT2N6w25lBx2Eof+pDGOJbbJAiDnXH3dotfyc1dZnaVuo
dNv8ifYbMvekJKZ2t0dT741Jj6m2g1qllpBFYfXeA08mD6iL8AOWsKwV0HFaanuU5nCT2vFp4LJi
TZ6P/4mdm13NRemUAiKN4DV/6PEEeXFsVIP4M7kFMhtYVRFP0OUnR3Hs7dpn1mKmS00PaaLJvOwi
S5THaJQXfuKOKD62xur1NGyfN4gHONuGcfrNlUhDbqNPgofXNJhuS5N5YHVpD/Aa1VP6IQzCP+k/
HxiMkl14p3ZnGbuy6n/pcAlWVqOwDAstNl7F6cTVg8uGF5csbBNvh1qvSaYd2804BC5f4ko1Di1L
+KIkBI3Y4WNeApI02phhXBxvWHZks/wCuPWdCg==
-----END CERTIFICATE-----
proftpd-mod_proxy-0.9.7/config.guess 0000775 0000000 0000000 00000141422 15207633221 0017550 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.7/config.sub 0000775 0000000 0000000 00000105752 15207633221 0017221 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.7/configure 0000775 0000000 0000000 00000502475 15207633221 0017150 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 EVP_chacha20" >&5
$as_echo_n "checking whether OpenSSL supports EVP_chacha20... " >&6; }
LIBS="-lcrypto $LIBS"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include
int
main ()
{
EVP_CIPHER *cipher;
cipher = EVP_chacha20();
;
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_CHACHA20_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 MLKEM768 algorithm" >&5
$as_echo_n "checking whether OpenSSL supports MLKEM768 algorithm... " >&6; }
LIBS="-lcrypto $LIBS"
if test "$cross_compiling" = yes; then :
{ { $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 test program while cross compiling
See \`config.log' for more details" "$LINENO" 5; }
else
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include
#include
#include
#ifdef HAVE_STDLIB_H
# include
#endif
#ifdef HAVE_STRING_H
# include
#endif
int main(int argc, char *argv[]) {
EVP_PKEY_CTX *pctx;
pctx = EVP_PKEY_CTX_new_from_name(NULL, "ML-KEM-768", NULL);
if (pctx == NULL) {
return 1;
}
EVP_PKEY_CTX_free(pctx);
return 0;
}
_ACEOF
if ac_fn_c_try_run "$LINENO"; then :
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
$as_echo "#define HAVE_MLKEM768_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 *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
conftest.$ac_objext conftest.beam conftest.$ac_ext
fi
{ $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.7/configure.in 0000664 0000000 0000000 00000030671 15207633221 0017544 0 ustar 00root root 0000000 0000000 dnl ProFTPD - mod_proxy
dnl Copyright (c) 2012-2026 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, see .
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 EVP_chacha20])
LIBS="-lcrypto $LIBS"
AC_TRY_LINK(
[
#include
],
[
EVP_CIPHER *cipher;
cipher = EVP_chacha20();
],
[
AC_MSG_RESULT(yes)
AC_DEFINE(HAVE_EVP_CHACHA20_OPENSSL, 1, [OpenSSL supports EVP_chacha20])
LIBS="$saved_libs"
],
[
AC_MSG_RESULT(no)
LIBS="$saved_libs"
]
)
AC_MSG_CHECKING([whether OpenSSL supports MLKEM768 algorithm])
LIBS="-lcrypto $LIBS"
AC_TRY_RUN(
[
#include
#include
#include
#ifdef HAVE_STDLIB_H
# include
#endif
#ifdef HAVE_STRING_H
# include
#endif
int main(int argc, char *argv[]) {
EVP_PKEY_CTX *pctx;
pctx = EVP_PKEY_CTX_new_from_name(NULL, "ML-KEM-768", NULL);
if (pctx == NULL) {
return 1;
}
EVP_PKEY_CTX_free(pctx);
return 0;
}
],
[
AC_MSG_RESULT(yes)
AC_DEFINE(HAVE_MLKEM768_OPENSSL, 1, [OpenSSL supports MLKEM768])
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.7/doc/ 0000775 0000000 0000000 00000000000 15207633221 0015771 5 ustar 00root root 0000000 0000000 proftpd-mod_proxy-0.9.7/doc/NOTES 0000664 0000000 0000000 00000015060 15207633221 0016606 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.7/doc/NOTES.dirlist-parsing 0000664 0000000 0000000 00000001376 15207633221 0021725 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.7/doc/NOTES.dns-srv 0000664 0000000 0000000 00000026724 15207633221 0020212 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, multiple 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.7/doc/NOTES.forward-proxy 0000664 0000000 0000000 00000005255 15207633221 0021435 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.7/doc/NOTES.health-checks 0000664 0000000 0000000 00000024054 15207633221 0021313 0 ustar 00root root 0000000 0000000
Health check (more properly "application server state" checks with applied
interpretation/policy) metrics:
TCP connect to port
connect 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 successful 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 heterogeneous 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.7/doc/NOTES.history 0000664 0000000 0000000 00000001067 15207633221 0020310 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.7/doc/NOTES.load-balancing 0000664 0000000 0000000 00000007631 15207633221 0021445 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.7/doc/NOTES.protocol-translation 0000664 0000000 0000000 00000010273 15207633221 0023003 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.7/doc/NOTES.proxy-datatransfer-policy 0000664 0000000 0000000 00000005662 15207633221 0023746 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.7/doc/NOTES.reverse-proxy 0000664 0000000 0000000 00000007435 15207633221 0021446 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.7/doc/NOTES.selection 0000664 0000000 0000000 00000001310 15207633221 0020563 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.7/doc/NOTES.selection-roundrobin 0000664 0000000 0000000 00000015643 15207633221 0022760 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.7/doc/NOTES.tests 0000664 0000000 0000000 00000000455 15207633221 0017751 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.7/doc/NOTES.tls 0000664 0000000 0000000 00000000105 15207633221 0017401 0 ustar 00root root 0000000 0000000
ProxyTLSOptions
IgnoreFEAT
UseCCC
Passphrase provider support?
proftpd-mod_proxy-0.9.7/doc/NOTES.todo 0000664 0000000 0000000 00000000220 15207633221 0017542 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.7/doc/REFERENCES 0000664 0000000 0000000 00000006526 15207633221 0017346 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.7/include/ 0000775 0000000 0000000 00000000000 15207633221 0016647 5 ustar 00root root 0000000 0000000 proftpd-mod_proxy-0.9.7/include/proxy/ 0000775 0000000 0000000 00000000000 15207633221 0020030 5 ustar 00root root 0000000 0000000 proftpd-mod_proxy-0.9.7/include/proxy/conn.h 0000664 0000000 0000000 00000004726 15207633221 0021147 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.7/include/proxy/db.h 0000664 0000000 0000000 00000006123 15207633221 0020570 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.7/include/proxy/dns.h 0000664 0000000 0000000 00000003526 15207633221 0020773 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.7/include/proxy/forward.h 0000664 0000000 0000000 00000004451 15207633221 0021651 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.7/include/proxy/ftp/ 0000775 0000000 0000000 00000000000 15207633221 0020621 5 ustar 00root root 0000000 0000000 proftpd-mod_proxy-0.9.7/include/proxy/ftp/conn.h 0000664 0000000 0000000 00000002734 15207633221 0021735 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.7/include/proxy/ftp/ctrl.h 0000664 0000000 0000000 00000003377 15207633221 0021750 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.7/include/proxy/ftp/data.h 0000664 0000000 0000000 00000002501 15207633221 0021701 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.7/include/proxy/ftp/dirlist.h 0000664 0000000 0000000 00000004720 15207633221 0022447 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.7/include/proxy/ftp/facts.h 0000664 0000000 0000000 00000003415 15207633221 0022075 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.7/include/proxy/ftp/msg.h 0000664 0000000 0000000 00000003634 15207633221 0021566 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy FTP message API
* 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.
*/
#ifndef MOD_PROXY_FTP_MSG_H
#define MOD_PROXY_FTP_MSG_H
#include "mod_proxy.h"
/* Format a string containing 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 containing 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.7/include/proxy/ftp/sess.h 0000664 0000000 0000000 00000003341 15207633221 0021750 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.7/include/proxy/ftp/xfer.h 0000664 0000000 0000000 00000002615 15207633221 0021742 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.7/include/proxy/inet.h 0000664 0000000 0000000 00000003220 15207633221 0021135 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.7/include/proxy/netio.h 0000664 0000000 0000000 00000004643 15207633221 0021326 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.7/include/proxy/random.h 0000664 0000000 0000000 00000002464 15207633221 0021467 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.7/include/proxy/reverse.h 0000664 0000000 0000000 00000010211 15207633221 0021647 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.7/include/proxy/reverse/ 0000775 0000000 0000000 00000000000 15207633221 0021503 5 ustar 00root root 0000000 0000000 proftpd-mod_proxy-0.9.7/include/proxy/reverse/db.h 0000664 0000000 0000000 00000002446 15207633221 0022247 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.7/include/proxy/reverse/redis.h 0000664 0000000 0000000 00000002525 15207633221 0022766 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.7/include/proxy/session.h 0000664 0000000 0000000 00000005747 15207633221 0021701 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.7/include/proxy/ssh.h 0000664 0000000 0000000 00000005175 15207633221 0021006 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.7/include/proxy/ssh/ 0000775 0000000 0000000 00000000000 15207633221 0020625 5 ustar 00root root 0000000 0000000 proftpd-mod_proxy-0.9.7/include/proxy/ssh/agent.h 0000664 0000000 0000000 00000003115 15207633221 0022074 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH agent API
* Copyright (c) 2021-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_SSH_AGENT_H
#define MOD_PROXY_SSH_AGENT_H
#include "mod_proxy.h"
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 /* MOD_PROXY_SSH_AGENT_H */
proftpd-mod_proxy-0.9.7/include/proxy/ssh/auth.h 0000664 0000000 0000000 00000003226 15207633221 0021742 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH auth API
* Copyright (c) 2021-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_SSH_AUTH_H
#define MOD_PROXY_SSH_AUTH_H
#include "mod_proxy.h"
#include "proxy/session.h"
#include "proxy/ssh/packet.h"
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 /* MOD_PROXY_SSH_AUTH_H */
proftpd-mod_proxy-0.9.7/include/proxy/ssh/bcrypt.h 0000664 0000000 0000000 00000002603 15207633221 0022302 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH bcrypt PBKDF2
* Copyright (c) 2021-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_SSH_BCRYPT_H
#define MOD_PROXY_SSH_BCRYPT_H
#include "mod_proxy.h"
#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 /* MOD_PROXY_SSH_BCRYPT_H */
proftpd-mod_proxy-0.9.7/include/proxy/ssh/cipher.h 0000664 0000000 0000000 00000005620 15207633221 0022253 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH cipher API
* Copyright (c) 2021-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_SSH_CIPHER_H
#define MOD_PROXY_SSH_CIPHER_H
#include "mod_proxy.h"
#include
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);
int proxy_ssh_cipher_is_read_chachapoly(void);
/* 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);
int proxy_ssh_cipher_read_packet_len(struct proxy_ssh_packet *pkt,
unsigned char *data, uint32_t data_len, unsigned char **buf,
uint32_t *buflen, uint32_t *packet_len);
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 /* MOD_PROXY_SSH_CIPHER_H */
proftpd-mod_proxy-0.9.7/include/proxy/ssh/compress.h 0000664 0000000 0000000 00000003330 15207633221 0022630 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH compression API
* Copyright (c) 2021-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_SSH_COMPRESS_H
#define MOD_PROXY_SSH_COMPRESS_H
#include "mod_proxy.h"
#include "proxy/ssh/packet.h"
#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 /* MOD_PROXY_SSH_COMPRESS_H */
proftpd-mod_proxy-0.9.7/include/proxy/ssh/crypto.h 0000664 0000000 0000000 00000003351 15207633221 0022320 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH crypto API
* Copyright (c) 2021-2026 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU 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 exemption, TJ Saunders and other respective copyright holders
* 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"
#include
#if defined(EVP_PKEY_X25519)
# define HAVE_X25519_OPENSSL 1
#endif /* EVP_PKEY_X25519 */
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,
int *free_digest);
void proxy_ssh_crypto_free_digest(const EVP_MD *md);
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 /* MOD_PROXY_SSH_CRYPTO_H */
proftpd-mod_proxy-0.9.7/include/proxy/ssh/db.h 0000664 0000000 0000000 00000002417 15207633221 0021367 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH Database API
* Copyright (c) 2021-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_SSH_DB_H
#define MOD_PROXY_SSH_DB_H
#include "mod_proxy.h"
#include "proxy/ssh.h"
int proxy_ssh_db_as_datastore(struct proxy_ssh_datastore *ds, void *ds_data,
size_t ds_datasz);
#endif /* MOD_PROXY_SSH_DB_H */
proftpd-mod_proxy-0.9.7/include/proxy/ssh/disconnect.h 0000664 0000000 0000000 00000004056 15207633221 0023134 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH disconnect API
* Copyright (c) 2021-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_SSH_DISCONNECT_H
#define MOD_PROXY_SSH_DISCONNECT_H
#include "mod_proxy.h"
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 /* MOD_PROXY_SSH_DISCONNECT_H */
proftpd-mod_proxy-0.9.7/include/proxy/ssh/interop.h 0000664 0000000 0000000 00000007106 15207633221 0022462 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH interop API
* Copyright (c) 2021-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_SSH_INTEROP_H
#define MOD_PROXY_SSH_INTEROP_H
#include "mod_proxy.h"
#include "proxy/session.h"
/* 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 /* MOD_PROXY_SSH_INTEROP_H */
proftpd-mod_proxy-0.9.7/include/proxy/ssh/kex.h 0000664 0000000 0000000 00000003315 15207633221 0021567 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH kex API
* Copyright (c) 2021-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_SSH_KEX_H
#define MOD_PROXY_SSH_KEX_H
#include "mod_proxy.h"
#include "proxy/session.h"
#include "proxy/ssh.h"
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 /* MOD_PROXY_SSH_KEX_H */
proftpd-mod_proxy-0.9.7/include/proxy/ssh/keys.h 0000664 0000000 0000000 00000006122 15207633221 0021752 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH keys API
* Copyright (c) 2021-2026 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU 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 exemption, TJ Saunders and other respective copyright holders
* 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"
#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_compare_keys(pool *p, unsigned char *remote_key_data,
uint32_t remote_key_datalen, unsigned char *local_key_data,
uint32_t local_key_datalen);
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 /* MOD_PROXY_SSH_KEYS_H */
proftpd-mod_proxy-0.9.7/include/proxy/ssh/mac.h 0000664 0000000 0000000 00000004212 15207633221 0021535 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH MAC API
* Copyright (c) 2021-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_SSH_MAC_H
#define MOD_PROXY_SSH_MAC_H
#include "mod_proxy.h"
#include "proxy/ssh/packet.h"
#include
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 /* MOD_PROXY_SSH_MAC_H */
proftpd-mod_proxy-0.9.7/include/proxy/ssh/misc.h 0000664 0000000 0000000 00000002470 15207633221 0021734 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH miscellany API
* Copyright (c) 2021-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_SSH_MISC_H
#define MOD_PROXY_SSH_MISC_H
#include "mod_proxy.h"
int proxy_ssh_misc_namelist_contains(pool *, const char *, const char *);
const char *proxy_ssh_misc_namelist_shared(pool *, const char *, const char *);
#endif /* MOD_PROXY_SSH_MISC_H */
proftpd-mod_proxy-0.9.7/include/proxy/ssh/msg.h 0000664 0000000 0000000 00000006056 15207633221 0021573 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH message API
* Copyright (c) 2021-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_SSH_MSG_H
#define MOD_PROXY_SSH_MSG_H
#include "mod_proxy.h"
#include
#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 /* MOD_PROXY_SSH_MSG_H */
proftpd-mod_proxy-0.9.7/include/proxy/ssh/packet.h 0000664 0000000 0000000 00000013121 15207633221 0022243 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH packet API
* Copyright (c) 2021-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_SSH_PACKET_H
#define MOD_PROXY_SSH_PACKET_H
#include "mod_proxy.h"
#include "proxy/session.h"
/* 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 /* MOD_PROXY_SSH_PACKET_H */
proftpd-mod_proxy-0.9.7/include/proxy/ssh/poly1305.h 0000664 0000000 0000000 00000001354 15207633221 0022275 0 ustar 00root root 0000000 0000000 /*
* Public Domain poly1305 from Andrew Moon
* poly1305-donna-unrolled.c from https://github.com/floodyberry/poly1305-donna
*/
#ifndef POLY1305_H
#define POLY1305_H
#include "mod_proxy.h"
#if defined(HAVE_EVP_CHACHA20_OPENSSL) && \
!defined(HAVE_BROKEN_CHACHA20)
#include
#define POLY1305_KEYLEN 32
#define POLY1305_TAGLEN 16
void poly1305_auth(u_char out[POLY1305_TAGLEN], const u_char *m, size_t inlen,
const u_char key[POLY1305_KEYLEN])
__attribute__((__bounded__(__minbytes__, 1, POLY1305_TAGLEN)))
__attribute__((__bounded__(__buffer__, 2, 3)))
__attribute__((__bounded__(__minbytes__, 4, POLY1305_KEYLEN)));
#endif /* HAVE_EVP_CHACHA20_OPENSSL and !HAVE_BROKEN_CHACHA20 */
#endif /* POLY1305_H */
proftpd-mod_proxy-0.9.7/include/proxy/ssh/provider.h 0000664 0000000 0000000 00000002244 15207633221 0022632 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy OpenSSL provider
* Copyright (c) 2026 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU 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 exemption, TJ Saunders and other respective copyright holders
* 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_PROVIDER_H
#define MOD_PROXY_SSH_PROVIDER_H
#include "mod_proxy.h"
int proxy_ssh_provider_init(void);
void proxy_ssh_provider_free(void);
#endif /* MOD_PROXY_SSH_PROVIDER_H */
proftpd-mod_proxy-0.9.7/include/proxy/ssh/redis.h 0000664 0000000 0000000 00000002423 15207633221 0022105 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.7/include/proxy/ssh/service.h 0000664 0000000 0000000 00000002466 15207633221 0022446 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH service API
* Copyright (c) 2021-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_SSH_SERVICE_H
#define MOD_PROXY_SSH_SERVICE_H
#include "mod_proxy.h"
#include "proxy/session.h"
#include "proxy/ssh/packet.h"
int proxy_ssh_service_handle(struct proxy_ssh_packet *,
const struct proxy_session *);
#endif /* MOD_PROXY_SSH_SERVICE_H */
proftpd-mod_proxy-0.9.7/include/proxy/ssh/session.h 0000664 0000000 0000000 00000002651 15207633221 0022465 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH session API
* Copyright (c) 2021-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_SSH_SESSION_H
#define MOD_PROXY_SSH_SESSION_H
#include "mod_proxy.h"
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 /* MOD_PROXY_SSH_SESSION_H */
proftpd-mod_proxy-0.9.7/include/proxy/ssh/sntrup761.h 0000664 0000000 0000000 00000002457 15207633221 0022577 0 ustar 00root root 0000000 0000000 #ifndef MOD_PROXY_SSH_SNTRUP761_H
#define MOD_PROXY_SSH_SNTRUP761_H
#include "mod_proxy.h"
typedef int8_t crypto_int8;
typedef uint8_t crypto_uint8;
typedef int16_t crypto_int16;
typedef uint16_t crypto_uint16;
typedef int32_t crypto_int32;
typedef uint32_t crypto_uint32;
typedef int64_t crypto_int64;
typedef uint64_t crypto_uint64;
#if defined(PR_USE_SODIUM)
/* Use Sodium's randombytes() implementation, if present. */
# include
# else
/* Use OpenSSL for random bytes. */
#define randombytes(buf, buf_len) RAND_bytes((buf), (buf_len))
#endif /* PR_USE_SODIUM */
#if !defined(PR_USE_SODIUM)
# include
# include
static inline int crypto_hash_sha512(unsigned char *out,
const unsigned char *in, unsigned long long inlen) {
if (EVP_Digest(in, inlen, out, NULL, EVP_sha512(), NULL) == 0) {
return -1;
}
return 0;
}
#endif /* PR_USE_SODIUM */
#define sntrup761_PUBLICKEYBYTES 1158
#define sntrup761_SECRETKEYBYTES 1763
#define sntrup761_CIPHERTEXTBYTES 1039
#define sntrup761_BYTES 32
int sntrup761_enc(unsigned char *cstr, unsigned char *k,
const unsigned char *pk);
int sntrup761_dec(unsigned char *k, const unsigned char *cstr,
const unsigned char *sk);
int sntrup761_keypair(unsigned char *pk, unsigned char *sk);
#endif /* MOD_PROXY_SSH_SNTRUP761_H */
proftpd-mod_proxy-0.9.7/include/proxy/ssh/ssh2.h 0000664 0000000 0000000 00000010615 15207633221 0021660 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.7/include/proxy/ssh/umac.h 0000664 0000000 0000000 00000004220 15207633221 0021721 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.7/include/proxy/ssh/utf8.h 0000664 0000000 0000000 00000002711 15207633221 0021665 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH UTF8 API
* Copyright (c) 2021-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_SSH_UTF8_H
#define MOD_PROXY_SSH_UTF8_H
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 /* MOD_PROXY_SSH_UTF8_H */
proftpd-mod_proxy-0.9.7/include/proxy/str.h 0000664 0000000 0000000 00000002303 15207633221 0021007 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.7/include/proxy/tls.h 0000664 0000000 0000000 00000010432 15207633221 0021003 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy TLS API
* Copyright (c) 2015-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_TLS_H
#define MOD_PROXY_TLS_H
#include "mod_proxy.h"
#include "proxy/session.h"
#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 */
/* 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 {
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);
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.7/include/proxy/tls/ 0000775 0000000 0000000 00000000000 15207633221 0020632 5 ustar 00root root 0000000 0000000 proftpd-mod_proxy-0.9.7/include/proxy/tls/db.h 0000664 0000000 0000000 00000002412 15207633221 0021367 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.7/include/proxy/tls/pkcs11.h 0000664 0000000 0000000 00000002374 15207633221 0022113 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy TLS PKCS11 API
* Copyright (c) 2026 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU 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 exemption, TJ Saunders and other respective copyright holders
* 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_PKCS11_H
#define MOD_PROXY_TLS_PKCS11_H
#include "mod_proxy.h"
#include "proxy/tls.h"
int proxy_tls_pkcs11_supported(void);
int proxy_tls_pkcs11_uri(const char *text);
EVP_PKEY *proxy_tls_pkcs11_get_private_key(const char *text);
#endif /* MOD_PROXY_TLS_PKCS11_H */
proftpd-mod_proxy-0.9.7/include/proxy/tls/redis.h 0000664 0000000 0000000 00000002423 15207633221 0022112 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.7/include/proxy/uri.h 0000664 0000000 0000000 00000002407 15207633221 0021003 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.7/install-sh 0000775 0000000 0000000 00000032464 15207633221 0017241 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.7/lib/ 0000775 0000000 0000000 00000000000 15207633221 0015772 5 ustar 00root root 0000000 0000000 proftpd-mod_proxy-0.9.7/lib/proxy/ 0000775 0000000 0000000 00000000000 15207633221 0017153 5 ustar 00root root 0000000 0000000 proftpd-mod_proxy-0.9.7/lib/proxy/conn.c 0000664 0000000 0000000 00000152350 15207633221 0020262 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");
/* Make sure that TCP_NODELAY is enabled (i.e. Nagle is disabled) by default
* for our control connections.
*/
(void) pr_inet_set_proto_nodelay(ctrl_conn->pool, ctrl_conn, 1);
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.7/lib/proxy/db.c 0000664 0000000 0000000 00000071261 15207633221 0017713 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy database implementation
* Copyright (c) 2015-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
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;
default:
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.7/lib/proxy/dns.c 0000664 0000000 0000000 00000043202 15207633221 0020104 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy DNS resolution
* Copyright (c) 2020-2026 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU 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 exemption, TJ Saunders and other respective copyright holders
* 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;
if (target_len == 0 ||
target_len > NS_MAXDNAME) {
pr_trace_msg(trace_channel, 3,
"invalid SRV target length %lu for record #%u, skipping",
(unsigned long) target_len, i + 1);
continue;
}
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.7/lib/proxy/forward.c 0000664 0000000 0000000 00000066714 15207633221 0021001 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.7/lib/proxy/ftp/ 0000775 0000000 0000000 00000000000 15207633221 0017744 5 ustar 00root root 0000000 0000000 proftpd-mod_proxy-0.9.7/lib/proxy/ftp/conn.c 0000664 0000000 0000000 00000025256 15207633221 0021057 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);
/* Make sure that TCP_NODELAY is enabled (i.e. Nagle is disabled) by default
* for our data connections.
*/
(void) pr_inet_set_proto_nodelay(conn->pool, conn, 1);
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);
/* Make sure that TCP_NODELAY is enabled (i.e. Nagle is disabled) by default
* for our data connections.
*/
(void) pr_inet_set_proto_nodelay(opened->pool, opened, 1);
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.7/lib/proxy/ftp/ctrl.c 0000664 0000000 0000000 00000040004 15207633221 0021052 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy FTP control conn routines
* 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/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 hyphen. 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.7/lib/proxy/ftp/data.c 0000664 0000000 0000000 00000011170 15207633221 0021021 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.7/lib/proxy/ftp/dirlist.c 0000664 0000000 0000000 00000104212 15207633221 0021562 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.7/lib/proxy/ftp/facts.c 0000664 0000000 0000000 00000006602 15207633221 0021214 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.7/lib/proxy/ftp/msg.c 0000664 0000000 0000000 00000027333 15207633221 0020706 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy FTP message 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/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;
}
default:
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.7/lib/proxy/ftp/sess.c 0000664 0000000 0000000 00000041511 15207633221 0021067 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy FTP session 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 "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;
default:
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.7/lib/proxy/ftp/xfer.c 0000664 0000000 0000000 00000047057 15207633221 0021071 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.7/lib/proxy/inet.c 0000664 0000000 0000000 00000011651 15207633221 0020262 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.7/lib/proxy/netio.c 0000664 0000000 0000000 00000020731 15207633221 0020440 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.7/lib/proxy/random.c 0000664 0000000 0000000 00000003634 15207633221 0020605 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.7/lib/proxy/reverse.c 0000664 0000000 0000000 00000150173 15207633221 0021001 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy reverse proxy 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 "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;
}
if (strcasecmp(policy, "RoundRobin") == 0) {
return PROXY_REVERSE_CONNECT_POLICY_ROUND_ROBIN;
}
if (strcasecmp(policy, "Shuffle") == 0) {
return PROXY_REVERSE_CONNECT_POLICY_SHUFFLE;
}
if (strcasecmp(policy, "LeastConns") == 0) {
return PROXY_REVERSE_CONNECT_POLICY_LEAST_CONNS;
}
if (strcasecmp(policy, "PerUser") == 0) {
return PROXY_REVERSE_CONNECT_POLICY_PER_USER;
}
if (strcasecmp(policy, "PerGroup") == 0) {
return PROXY_REVERSE_CONNECT_POLICY_PER_GROUP;
}
if (strcasecmp(policy, "PerHost") == 0) {
return PROXY_REVERSE_CONNECT_POLICY_PER_HOST;
}
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.7/lib/proxy/reverse/ 0000775 0000000 0000000 00000000000 15207633221 0020626 5 ustar 00root root 0000000 0000000 proftpd-mod_proxy-0.9.7/lib/proxy/reverse/db.c 0000664 0000000 0000000 00000155443 15207633221 0021373 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.7/lib/proxy/reverse/redis.c 0000664 0000000 0000000 00000077325 15207633221 0022116 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.7/lib/proxy/session.c 0000664 0000000 0000000 00000021415 15207633221 0021005 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.7/lib/proxy/ssh.c 0000664 0000000 0000000 00000067505 15207633221 0020131 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH implementation
* Copyright (c) 2021-2026 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU 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 exemption, TJ Saunders and other respective copyright holders
* 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/provider.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"
#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 *event_data, void *user_data);
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);
}
int proxy_ssh_init(pool *p, const char *tables_path, int flags) {
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();
return 0;
}
int proxy_ssh_free(pool *p) {
if (p == NULL) {
errno = EINVAL;
return -1;
}
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_provider_free();
proxy_ssh_crypto_free(0);
return 0;
}
int proxy_ssh_sess_init(pool *p, struct proxy_session *proxy_sess, int flags) {
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;
}
return 0;
}
int proxy_ssh_sess_free(pool *p) {
if (p == NULL) {
errno = EINVAL;
return -1;
}
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);
return 0;
}
proftpd-mod_proxy-0.9.7/lib/proxy/ssh/ 0000775 0000000 0000000 00000000000 15207633221 0017750 5 ustar 00root root 0000000 0000000 proftpd-mod_proxy-0.9.7/lib/proxy/ssh/agent.c 0000664 0000000 0000000 00000025761 15207633221 0021225 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH agent support
* Copyright (c) 2021-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/ssh/agent.h"
#include "proxy/ssh/msg.h"
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;
default:
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;
}
proftpd-mod_proxy-0.9.7/lib/proxy/ssh/auth.c 0000664 0000000 0000000 00000107420 15207633221 0021061 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH user authentication
* Copyright (c) 2021-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/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"
/* 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;
}
(void) len;
} 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;
}
proftpd-mod_proxy-0.9.7/lib/proxy/ssh/bcrypt.c 0000664 0000000 0000000 00000014720 15207633221 0021423 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"
#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 modified
* 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;
}
proftpd-mod_proxy-0.9.7/lib/proxy/ssh/cipher.c 0000664 0000000 0000000 00000141466 15207633221 0021402 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH ciphers
* Copyright (c) 2021-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/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"
#include "proxy/ssh/poly1305.h"
#include
struct proxy_ssh_cipher {
pool *pool;
const char *algo;
unsigned int algo_type;
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;
};
#define PROXY_SSH_CIPHER_ALGO_NONE 1
#define PROXY_SSH_CIPHER_ALGO_GCM 2
#define PROXY_SSH_CIPHER_ALGO_CHACHA 3
/* 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, 0, NULL, NULL, 0, NULL, 0, 0, 0 },
{ NULL, NULL, 0, NULL, NULL, 0, NULL, 0, 0, 0 }
};
static EVP_CIPHER_CTX *read_ctxs[2];
#if defined(HAVE_EVP_CHACHA20_OPENSSL) && \
!defined(HAVE_BROKEN_CHACHA20)
static EVP_CIPHER_CTX *read_header_ctxs[2] = { NULL, NULL };
#endif /* HAVE_EVP_CHACHA20_OPENSSL and !HAVE_BROKEN_CHACHA20 */
static struct proxy_ssh_cipher write_ciphers[2] = {
{ NULL, NULL, 0, NULL, NULL, 0, NULL, 0, 0, 0 },
{ NULL, NULL, 0, NULL, NULL, 0, NULL, 0, 0, 0 }
};
static EVP_CIPHER_CTX *write_ctxs[2];
#if defined(HAVE_EVP_CHACHA20_OPENSSL) && \
!defined(HAVE_BROKEN_CHACHA20)
static EVP_CIPHER_CTX *write_header_ctxs[2] = { NULL, NULL };
#endif /* HAVE_EVP_CHACHA20_OPENSSL and !HAVE_BROKEN_CHACHA20 */
#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 *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 unsigned int get_algo_type(const char *algo) {
unsigned int algo_type = 0;
const char *gcm_suffix = "-gcm@openssh.com";
if (strcmp(algo, "none") == 0) {
algo_type = PROXY_SSH_CIPHER_ALGO_NONE;
} else if (pr_strnrstr(algo, strlen(algo), gcm_suffix,
strlen(gcm_suffix), 0) == TRUE) {
algo_type = PROXY_SSH_CIPHER_ALGO_GCM;
} else if (strcmp(algo, "chacha20-poly1305@openssh.com") == 0) {
algo_type = PROXY_SSH_CIPHER_ALGO_CHACHA;
}
return algo_type;
}
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 (cipher->algo_type == PROXY_SSH_CIPHER_ALGO_NONE) {
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 (cipher->algo_type == PROXY_SSH_CIPHER_ALGO_NONE) {
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 of %lu bytes)",
(unsigned long) key_len, (unsigned long) key_sz);
/* 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;
}
#if defined(HAVE_EVP_CHACHA20_OPENSSL) && \
!defined(HAVE_BROKEN_CHACHA20)
/* Note that the given poly_key buffer MUST be POLY1305_KEYLEN in size. */
static int compute_chachapoly_key(struct proxy_ssh_packet *pkt,
EVP_CIPHER_CTX *pctx, unsigned char *poly_key) {
unsigned char seqno_buf[16], *seqno_ptr;
uint32_t seqno_len;
/* Initialize our IV for the ChaCha cipher. */
memset(seqno_buf, 0, sizeof(seqno_buf));
seqno_ptr = seqno_buf + 8;
seqno_len = 8;
proxy_ssh_msg_write_long(&seqno_ptr, &seqno_len, pkt->seqno);
if (EVP_CipherInit(pctx, NULL, NULL, seqno_buf, 1) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error initializing ChaChaPoly cipher for encryption: %s",
proxy_ssh_crypto_get_errors());
return -1;
}
memset(poly_key, 0, POLY1305_KEYLEN);
if (EVP_Cipher(pctx, poly_key, poly_key, POLY1305_KEYLEN) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error computing ChaChaPoly packet key: %s",
proxy_ssh_crypto_get_errors());
return -1;
}
return 0;
}
#endif /* HAVE_EVP_CHACHA20_OPENSSL and !HAVE_BROKEN_CHACHA20 */
#if !defined(HAVE_TIMINGSAFE_BCMP)
static int timingsafe_bcmp(const void *b1, const void *b2, size_t n) {
const unsigned char *p1 = b1, *p2 = b2;
int ret = 0;
for (; n > 0; n--) {
ret |= *p1++ ^ *p2++;
}
return (ret != 0);
}
#endif /* HAVE_TIMINGSAFE_BCMP */
/* 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;
}
}
int proxy_ssh_cipher_is_read_chachapoly(void) {
if (read_ciphers[read_cipher_idx].key != NULL &&
read_ciphers[read_cipher_idx].algo_type == PROXY_SSH_CIPHER_ALGO_CHACHA) {
return TRUE;
}
return FALSE;
}
const char *proxy_ssh_cipher_get_read_algo(void) {
if (read_ciphers[read_cipher_idx].key != NULL ||
read_ciphers[read_cipher_idx].algo_type == PROXY_SSH_CIPHER_ALGO_NONE) {
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].algo_type = get_algo_type(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, *hpctx = NULL;
/* Currently unused. */
(void) p;
switch_read_cipher();
cipher = &(read_ciphers[read_cipher_idx]);
pctx = read_ctxs[read_cipher_idx];
if (cipher->algo_type == PROXY_SSH_CIPHER_ALGO_CHACHA) {
#if defined(HAVE_EVP_CHACHA20_OPENSSL) && \
!defined(HAVE_BROKEN_CHACHA20)
hpctx = read_header_ctxs[read_cipher_idx];
#endif /* HAVE_EVP_CHACHA20_OPENSSL and !HAVE_BROKEN_CHACHA20 */
}
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);
if (hpctx != NULL) {
EVP_CIHPER_CTX_init(hpctx);
}
#else
EVP_CIPHER_CTX_reset(pctx);
if (hpctx != NULL) {
EVP_CIPHER_CTX_reset(hpctx);
}
#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;
}
if (hpctx != NULL) {
#if defined(PR_USE_OPENSSL_EVP_CIPHERINIT_EX)
if (EVP_CipherInit_ex(hpctx, cipher->cipher, NULL, NULL, NULL, 0) != 1) {
#else
if (EVP_CipherInit(hpctx, cipher->cipher, NULL, NULL, 0) != 1) {
#endif /* PR_USE_OPENSSL_EVP_CIPHERINIT_EX */
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error initializing %s cipher for header decryption: %s", cipher->algo,
proxy_ssh_crypto_get_errors());
return -1;
}
}
auth_len = (int) cipher->auth_len;
if (auth_len > 0) {
if (cipher->algo_type == PROXY_SSH_CIPHER_ALGO_GCM) {
#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 &&
cipher->algo_type != PROXY_SSH_CIPHER_ALGO_CHACHA) {
/* Skip setting our custom key length for ChaChaPoly, since the custom
* key length is used for two different ChaChaPoly cipher instances.
*/
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 (hpctx != NULL) {
/* The ChaChaPoly header instance uses the "second half" of the computed
* session key, per OpenSSH spec.
*/
#if defined(PR_USE_OPENSSL_EVP_CIPHERINIT_EX)
if (EVP_CipherInit_ex(hpctx, NULL, NULL, cipher->key + 32, NULL, -1) != 1) {
#else
if (EVP_CipherInit(hpctx, NULL, cipher->key + 32, 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 header 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));
}
pr_trace_msg(trace_channel, 19,
"set block size (%d) for %s cipher for decryption",
(int) proxy_ssh_cipher_get_read_block_size(), cipher->algo);
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) {
int res;
struct proxy_ssh_cipher *cipher;
EVP_CIPHER_CTX *pctx;
size_t auth_len = 0, read_blocksz;
uint32_t output_buflen;
unsigned char *ptr = NULL, *buf2 = NULL;
#if defined(HAVE_EVP_CHACHA20_OPENSSL) && \
!defined(HAVE_BROKEN_CHACHA20)
unsigned char chachapoly_key[POLY1305_KEYLEN];
#endif /* HAVE_EVP_CHACHA20_OPENSSL and !HAVE_BROKEN_CHACHA20 */
cipher = &(read_ciphers[read_cipher_idx]);
if (cipher->key == NULL) {
/* We haven't finished NEWKEYS yet, so our cipher isn't keyed. */
*buf = data;
*buflen = data_len;
return 0;
}
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 (*buf == NULL) {
size_t bufsz;
/* Allocate a buffer that's large enough. */
bufsz = (data_len + read_blocksz - 1);
ptr = buf2 = palloc(pkt->pool, bufsz);
} else {
ptr = buf2 = *buf;
}
if (pkt->packet_len == 0) {
if (auth_len > 0) {
if (cipher->algo_type == PROXY_SSH_CIPHER_ALGO_GCM) {
#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 = palloc(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 (cipher->algo_type != PROXY_SSH_CIPHER_ALGO_CHACHA) {
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 (cipher->algo_type == PROXY_SSH_CIPHER_ALGO_GCM) {
#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;
} else if (cipher->algo_type == PROXY_SSH_CIPHER_ALGO_CHACHA) {
#if defined(HAVE_EVP_CHACHA20_OPENSSL) && \
!defined(HAVE_BROKEN_CHACHA20)
unsigned char chachapoly_tag[POLY1305_TAGLEN];
pool *tag_pool;
unsigned char *tag_buf;
size_t tag_bufsz;
if (compute_chachapoly_key(pkt, pctx, chachapoly_key) < 0) {
return -1;
}
/* Here we want to compute our Poly1305 tag over the combination
* of the encrypted packet length (pkt->aad) and the encrypted
* payload (buf).
*
* Thus we need to assemble that here.
*/
tag_pool = make_sub_pool(pkt->pool);
tag_bufsz = pkt->aad_len + data_len;
tag_buf = palloc(tag_pool, tag_bufsz);
memcpy(tag_buf, pkt->aad, pkt->aad_len);
memcpy(tag_buf + pkt->aad_len, data, data_len);
poly1305_auth(chachapoly_tag, tag_buf, tag_bufsz, chachapoly_key);
destroy_pool(tag_pool);
/* Our ChaChaPoly tag is stored as the MAC, NOT in the given network
* data (which is just the payload).
*/
if (timingsafe_bcmp(chachapoly_tag, pkt->mac,
sizeof(chachapoly_tag)) != 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error verifying %s authentication tag from client: "
"Mismatched tags", cipher->algo);
errno = EIO;
return -1;
}
#endif /* HAVE_EVP_CHACHA20_OPENSSL and !HAVE_BROKEN_CHACHA20 */
}
}
if (cipher->algo_type == PROXY_SSH_CIPHER_ALGO_CHACHA) {
unsigned char seqno_buf[16], *seqno_ptr;
uint32_t seqno_len;
memset(seqno_buf, 0, sizeof(seqno_buf));
seqno_buf[0] = 1;
seqno_ptr = seqno_buf + 8;
seqno_len = 8;
proxy_ssh_msg_write_long(&seqno_ptr, &seqno_len, pkt->seqno);
if (EVP_CipherInit(pctx, NULL, NULL, seqno_buf, 1) != 1) {
(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;
}
}
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 (cipher->algo_type != PROXY_SSH_CIPHER_ALGO_CHACHA) {
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;
}
int proxy_ssh_cipher_read_packet_len(struct proxy_ssh_packet *pkt,
unsigned char *data, uint32_t data_len, unsigned char **buf,
uint32_t *buflen, uint32_t *packet_len) {
int res;
struct proxy_ssh_cipher *cipher;
uint32_t pkt_len = 0;
cipher = &(read_ciphers[read_cipher_idx]);
if (cipher->key == NULL) {
/* We haven't finished NEWKEYS setup yet, so packet length is in
* plaintext.
*/
*buf = data;
*buflen = data_len;
memmove(&pkt_len, *buf, sizeof(uint32_t));
*packet_len = ntohl(pkt_len);
*buf += sizeof(uint32_t);
*buflen -= sizeof(uint32_t);
return 0;
}
if (cipher->algo_type == PROXY_SSH_CIPHER_ALGO_CHACHA) {
#if defined(HAVE_EVP_CHACHA20_OPENSSL) && \
!defined(HAVE_BROKEN_CHACHA20)
unsigned char seqno_buf[16], *seqno_ptr;
uint32_t seqno_len;
EVP_CIPHER_CTX *hpctx;
hpctx = read_header_ctxs[read_cipher_idx];
/* Initialize our IV for the ChaChaPoly header. Note that the packet
* sequence number must be encoded according to the SSH spec.
*/
memset(seqno_buf, 0, sizeof(seqno_buf));
seqno_ptr = seqno_buf + 8;
seqno_len = 8;
proxy_ssh_msg_write_long(&seqno_ptr, &seqno_len, pkt->seqno);
if (EVP_CipherInit(hpctx, NULL, NULL, seqno_buf, 0) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error initializing %s cipher for packet length decryption: %s",
cipher->algo, proxy_ssh_crypto_get_errors());
return -1;
}
if (EVP_Cipher(hpctx, (unsigned char *) &pkt_len, data,
sizeof(pkt_len)) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error decrypting %s packet length from client: %s", cipher->algo,
proxy_ssh_crypto_get_errors());
return -1;
}
/* We need to save these encrypted header bytes for later. */
pkt->aad = palloc(pkt->pool, pkt->aad_len);
memcpy(pkt->aad, data, pkt->aad_len);
*packet_len = ntohl(pkt_len);
/* No leftover network bytes for later processing; we used them all. */
*buf = NULL;
*buflen = 0;
return 0;
#endif /* HAVE_EVP_CHACHA20_OPENSSL and !HAVE_BROKEN_CHACHA20 */
}
res = proxy_ssh_cipher_read_data(pkt, data, data_len, buf, buflen);
if (res < 0) {
return -1;
}
memmove(&pkt_len, *buf, sizeof(uint32_t));
*packet_len = ntohl(pkt_len);
*buf += sizeof(uint32_t);
*buflen -= sizeof(uint32_t);
return 0;
}
const char *proxy_ssh_cipher_get_write_algo(void) {
if (write_ciphers[write_cipher_idx].key != NULL ||
write_ciphers[write_cipher_idx].algo_type == PROXY_SSH_CIPHER_ALGO_NONE) {
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].algo_type = get_algo_type(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, *hpctx = NULL;
/* Currently unused. */
(void) p;
switch_write_cipher();
cipher = &(write_ciphers[write_cipher_idx]);
pctx = write_ctxs[write_cipher_idx];
if (cipher->algo_type == PROXY_SSH_CIPHER_ALGO_CHACHA) {
#if defined(HAVE_EVP_CHACHA20_OPENSSL) && \
!defined(HAVE_BROKEN_CHACHA20)
hpctx = write_header_ctxs[write_cipher_idx];
#endif /* HAVE_EVP_CHACHA20_OPENSSL and !HAVE_BROKEN_CHACHA20 */
}
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);
if (hpctx != NULL) {
EVP_CIPIHER_CTX_init(hpctx);
}
#else
EVP_CIPHER_CTX_reset(pctx);
if (hpctx != NULL) {
EVP_CIPHER_CTX_reset(hpctx);
}
#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;
}
if (hpctx != NULL) {
#if defined(PR_USE_OPENSSL_EVP_CIPHERINIT_EX)
if (EVP_CipherInit_ex(hpctx, cipher->cipher, NULL, NULL, NULL, 1) != 1) {
#else
if (EVP_CipherInit(hpctx, cipher->cipher, NULL, NULL, 1) != 1) {
#endif /* PR_USE_OPENSSL_EVP_CIPHERINIT_EX */
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error initializing %s cipher for header encryption: %s", cipher->algo,
proxy_ssh_crypto_get_errors());
return -1;
}
}
auth_len = (int) cipher->auth_len;
if (auth_len > 0) {
if (cipher->algo_type == PROXY_SSH_CIPHER_ALGO_GCM) {
#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 &&
cipher->algo_type != PROXY_SSH_CIPHER_ALGO_CHACHA) {
/* Skip setting our custom key length for ChaChaPoly, since the custom
* key length is used for two different ChaChaPoly cipher instances.
*/
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 (hpctx != NULL) {
#if defined(PR_USE_OPENSSL_EVP_CIPHERINIT_EX)
if (EVP_CipherInit_ex(hpctx, NULL, NULL, cipher->key + 32, NULL, -1) != 1) {
#else
if (EVP_CipherInit(hpctx, NULL, cipher->key + 32, 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 header 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) {
int res;
struct proxy_ssh_cipher *cipher;
EVP_CIPHER_CTX *pctx;
size_t auth_len = 0;
#if defined(HAVE_EVP_CHACHA20_OPENSSL) && \
!defined(HAVE_BROKEN_CHACHA20)
unsigned char chachapoly_key[POLY1305_KEYLEN];
#endif /* HAVE_EVP_CHACHA20_OPENSSL and !HAVE_BROKEN_CHACHA20 */
unsigned char *data, *ptr;
uint32_t datalen, datasz, 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) {
*buflen = 0;
return 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. However, it IS encrypted for ChaChaPoly.
*/
if (cipher->algo_type != PROXY_SSH_CIPHER_ALGO_CHACHA) {
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 (cipher->algo_type == PROXY_SSH_CIPHER_ALGO_GCM) {
#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
} else if (cipher->algo_type == PROXY_SSH_CIPHER_ALGO_CHACHA) {
#if defined(HAVE_EVP_CHACHA20_OPENSSL) && \
!defined(HAVE_BROKEN_CHACHA20)
if (compute_chachapoly_key(pkt, pctx, chachapoly_key) < 0) {
return -1;
}
#endif /* HAVE_EVP_CHACHA20_OPENSSL and !HAVE_BROKEN_CHACHA20 */
}
}
if (pkt->aad_len > 0 &&
pkt->aad == NULL) {
uint32_t packet_len;
packet_len = htonl(pkt->packet_len);
pkt->aad = palloc(pkt->pool, pkt->aad_len);
memcpy(pkt->aad, &packet_len, pkt->aad_len);
if (auth_len > 0) {
if (cipher->algo_type == PROXY_SSH_CIPHER_ALGO_CHACHA) {
#if defined(HAVE_EVP_CHACHA20_OPENSSL) && \
!defined(HAVE_BROKEN_CHACHA20)
unsigned char seqno_buf[16], *seqno_ptr;
uint32_t seqno_len;
EVP_CIPHER_CTX *hpctx;
hpctx = write_header_ctxs[write_cipher_idx];
memset(seqno_buf, 0, sizeof(seqno_buf));
seqno_ptr = seqno_buf + 8;
seqno_len = 8;
proxy_ssh_msg_write_long(&seqno_ptr, &seqno_len, pkt->seqno);
if (EVP_CipherInit(hpctx, NULL, NULL, seqno_buf, 1) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error initializing %s cipher for packet length encryption: %s",
cipher->algo, proxy_ssh_crypto_get_errors());
return -1;
}
if (EVP_Cipher(hpctx, pkt->aad, (unsigned char *) &packet_len,
pkt->aad_len) < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error encrypting %s packet length for client: %s", cipher->algo,
proxy_ssh_crypto_get_errors());
return -1;
}
#endif /* HAVE_EVP_CHACHA20_OPENSSL and !HAVE_BROKEN_CHACHA20 */
} else {
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);
if (cipher->algo_type == PROXY_SSH_CIPHER_ALGO_CHACHA) {
unsigned char seqno_buf[16], *seqno_ptr;
uint32_t seqno_len;
memset(seqno_buf, 0, sizeof(seqno_buf));
seqno_buf[0] = 1;
seqno_ptr = seqno_buf + 8;
seqno_len = 8;
proxy_ssh_msg_write_long(&seqno_ptr, &seqno_len, pkt->seqno);
if (EVP_CipherInit(pctx, NULL, NULL, seqno_buf, 1) != 1) {
(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;
}
}
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 (cipher->algo_type != PROXY_SSH_CIPHER_ALGO_CHACHA) {
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 = palloc(pkt->pool, tag_datalen);
if (cipher->algo_type == PROXY_SSH_CIPHER_ALGO_GCM) {
#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
} else if (cipher->algo_type == PROXY_SSH_CIPHER_ALGO_CHACHA) {
#if defined(HAVE_EVP_CHACHA20_OPENSSL) && \
!defined(HAVE_BROKEN_CHACHA20)
pool *tag_pool;
unsigned char *tag_buf;
size_t tag_bufsz;
/* Here we want to compute our Poly1305 tag over the combination
* of the encrypted packet length (pkt->aad) and the encrypted
* payload (buf).
*
* Thus we need to assemble that here.
*/
tag_pool = make_sub_pool(pkt->pool);
tag_bufsz = pkt->aad_len + *buflen;
tag_buf = palloc(tag_pool, tag_bufsz);
memcpy(tag_buf, pkt->aad, pkt->aad_len);
memcpy(tag_buf + pkt->aad_len, buf, *buflen);
poly1305_auth(tag_data, tag_buf, tag_bufsz, chachapoly_key);
destroy_pool(tag_pool);
#endif /* HAVE_EVP_CHACHA20_OPENSSL and !HAVE_BROKEN_CHACHA20 */
}
pkt->mac_len = tag_datalen;
pkt->mac = tag_data;
}
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();
# if defined(HAVE_EVP_CHACHA20_OPENSSL) && \
!defined(HAVE_BROKEN_CHACHA20)
read_header_ctxs[0] = EVP_CIPHER_CTX_new();
read_header_ctxs[1] = EVP_CIPHER_CTX_new();
write_header_ctxs[0] = EVP_CIPHER_CTX_new();
write_header_ctxs[1] = EVP_CIPHER_CTX_new();
# endif /* HAVE_EVP_CHACHA20_OPENSSL and !HAVE_BROKEN_CHACHA20 */
#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]);
# if defined(HAVE_EVP_CHACHA20_OPENSSL) && \
!defined(HAVE_BROKEN_CHACHA20)
if (read_header_ctxs[0] != NULL) {
EVP_CIPHER_CTX_free(read_header_ctxs[0]);
}
if (read_header_ctxs[1] != NULL) {
EVP_CIPHER_CTX_free(read_header_ctxs[1]);
}
if (write_header_ctxs[0] != NULL) {
EVP_CIPHER_CTX_free(write_header_ctxs[0]);
}
if (write_header_ctxs[1] != NULL) {
EVP_CIPHER_CTX_free(write_header_ctxs[1]);
}
# endif /* HAVE_EVP_CHACHA20_OPENSSL and !HAVE_BROKEN_CHACHA20 */
#endif /* OpenSSL-1.0.0 and later */
return 0;
}
proftpd-mod_proxy-0.9.7/lib/proxy/ssh/compress.c 0000664 0000000 0000000 00000034337 15207633221 0021761 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH compression
* Copyright (c) 2021-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/ssh/msg.h"
#include "proxy/ssh/packet.h"
#include "proxy/ssh/crypto.h"
#include "proxy/ssh/compress.h"
#if defined(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 */
proftpd-mod_proxy-0.9.7/lib/proxy/ssh/crypto.c 0000664 0000000 0000000 00000123077 15207633221 0021446 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH crypto
* Copyright (c) 2021-2026 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU 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 exemption, TJ Saunders and other respective copyright holders
* 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"
#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.
*/
#if defined(HAVE_EVP_CHACHA20_OPENSSL) && \
!defined(HAVE_BROKEN_CHACHA20)
{ "chacha20-poly1305@openssh.com", "chacha20", 16, 0, EVP_chacha20, TRUE, TRUE },
#endif /* HAVE_EVP_CHACHA20_OPENSSL and !HAVE_BROKEN_CHACHA20 */
{ "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 */
#if OPENSSL_VERSION_NUMBER >= 0x40000000L && !defined(HAVE_LIBRESSL)
/* We'll use the Provider interface for UMAC digests in this case. */
#else
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;
}
#endif /* OpenSSL before 4.x */
static const EVP_MD *get_umac64_digest(int *free_md) {
EVP_MD *md = NULL;
*free_md = FALSE;
#if OPENSSL_VERSION_NUMBER >= 0x40000000L && !defined(HAVE_LIBRESSL)
md = EVP_MD_fetch(NULL, "umac64", NULL);
if (md == NULL) {
pr_trace_msg(trace_channel, 4, "error fetching 'umac64' EVP_MD: %s",
proxy_ssh_crypto_get_errors());
} else {
*free_md = TRUE;
}
#else
# 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 */
#endif /* OpenSSL before 4.x */
return md;
}
static const EVP_MD *get_umac128_digest(int *free_md) {
EVP_MD *md;
*free_md = FALSE;
#if OPENSSL_VERSION_NUMBER >= 0x40000000L && !defined(HAVE_LIBRESSL)
md = EVP_MD_fetch(NULL, "umac128", NULL);
if (md == NULL) {
pr_trace_msg(trace_channel, 4, "error fetching 'umac64' EVP_MD: %s",
proxy_ssh_crypto_get_errors());
} else {
*free_md = TRUE;
}
#else
# 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 */
#endif /* OpenSSL before 4.x */
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) {
/* 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;
} else if (strcmp(name, "chacha20-poly1305@openssh.com") == 0) {
*key_len = 64;
} else {
*key_len = 0;
}
}
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;
}
void proxy_ssh_crypto_free_digest(const EVP_MD *md) {
#if (OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(HAVE_LIBRESSL)) || \
(defined(HAVE_LIBRESSL) && LIBRESSL_VERSION_NUMBER >= 0x3080000L)
EVP_MD_free((EVP_MD *) md);
#else
/* Avoid compiler warnings. */
(void) md;
#endif /* OpenSSL-3.x/LibreSSL-3.8.x and later */
}
const EVP_MD *proxy_ssh_crypto_get_digest(const char *name, uint32_t *mac_len,
int *free_md) {
register unsigned int i;
if (name == NULL) {
errno = EINVAL;
return NULL;
}
*free_md = FALSE;
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(free_md);
} else if (strcmp(name, "umac-128@openssh.com") == 0 ||
strcmp(name, "umac-128-etm@openssh.com") == 0) {
digest = get_umac128_digest(free_md);
#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 */
}
}
proftpd-mod_proxy-0.9.7/lib/proxy/ssh/db.c 0000664 0000000 0000000 00000030173 15207633221 0020505 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH database implementation
* Copyright (c) 2021-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/ssh.h"
#include "proxy/ssh/db.h"
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;
}
proftpd-mod_proxy-0.9.7/lib/proxy/ssh/disconnect.c 0000664 0000000 0000000 00000011776 15207633221 0022261 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH disconnects
* Copyright (c) 2021-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/ssh/ssh2.h"
#include "proxy/ssh/msg.h"
#include "proxy/ssh/packet.h"
#include "proxy/ssh/disconnect.h"
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 */
}
proftpd-mod_proxy-0.9.7/lib/proxy/ssh/interop.c 0000664 0000000 0000000 00000023206 15207633221 0021577 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH interoperability
* Copyright (c) 2021-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/ssh/ssh2.h"
#include "proxy/ssh/disconnect.h"
#include "proxy/ssh/interop.h"
/* 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;
}
proftpd-mod_proxy-0.9.7/lib/proxy/ssh/kex.c 0000664 0000000 0000000 00000604420 15207633221 0020711 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH key exchange (kex)
* Copyright (c) 2021-2026 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU 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 exemption, TJ Saunders and other respective copyright holders
* 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/sntrup761.h"
#include "proxy/ssh/misc.h"
#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_MLKEM768_OPENSSL) && defined(HAVE_SHA256_OPENSSL)
# include
#endif /* HAVE_MLKEM768_OPENSSL and HAVE_SHA256_OPENSSL */
#if defined(HAVE_X25519_OPENSSL)
# define X25519_KEYLEN 32
#endif /* HAVE_X25519_OPENSSL */
#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 MLKEM768? */
int use_mlkem768;
/* Using sntrup761? */
int use_sntrup761;
/* Using extension negotiations? */
int use_ext_info;
/* For generating the session ID */
DH *dh;
const BIGNUM *e;
const EVP_MD *hash;
const BIGNUM *k;
/* Some key exchanges encode K as something other than an mpint. */
unsigned char *kdata;
uint32_t kdatalen;
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 */
#if defined(HAVE_MLKEM768_OPENSSL) && defined(HAVE_SHA256_OPENSSL)
EVP_PKEY *client_mlkem768;
#endif /* HAVE_MLKEM768_OPENSSL and HAVE_SHA256_OPENSSL */
#if defined(HAVE_X25519_OPENSSL)
unsigned char *client_sntrup761_priv_key;
unsigned char *client_sntrup761_pub_key;
unsigned char *client_x25519_priv_key;
unsigned char *client_x25519_pub_key;
unsigned char *server_x25519_pub_key;
#endif /* HAVE_X25519_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 *p,
struct proxy_ssh_kex *kex, conn_t *conn, int disconnect_code,
char *found_msg_type, unsigned int ntypes, ...);
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, free_digest = FALSE;
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, &free_digest);
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);
}
if (free_digest == TRUE) {
proxy_ssh_crypto_free_digest(digest);
}
}
algo = kex->session_names->s2c_mac_algo;
digest = proxy_ssh_crypto_get_digest(algo, NULL, &free_digest);
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);
}
if (free_digest == TRUE) {
proxy_ssh_crypto_free_digest(digest);
}
}
/* 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 */
#if defined(HAVE_X25519_OPENSSL)
/* Note: This is an OpenSSL-based implementation of the similarly named
* 'generate_curve25519_keys' function, which is Sodium-based.
*/
static int generate_x25519_keys(pool *p, 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_X25519, NULL);
if (pctx == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error initializing context for X25519 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 X25519 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 X25519 shared key: %s", proxy_ssh_crypto_get_errors());
EVP_PKEY_CTX_free(pctx);
return -1;
}
key_len = X25519_KEYLEN;
*priv_key = pcalloc(p, key_len);
if (EVP_PKEY_get_raw_private_key(pkey, *priv_key, &key_len) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error obtaining X25519 private key: %s", proxy_ssh_crypto_get_errors());
EVP_PKEY_CTX_free(pctx);
EVP_PKEY_free(pkey);
return -1;
}
key_len = X25519_KEYLEN;
if (EVP_PKEY_get_raw_public_key(pkey, pub_key, &key_len) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error obtaining X25519 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;
}
/* Note: This is an OpenSSL-based implementation of the similarly named
* 'get_curve25519_shared_key' function, which is Sodium-based.
*/
static int get_x25519_shared_key(unsigned char *shared_key,
unsigned char *client_x25519, unsigned char *server_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_X25519, NULL, server_key,
X25519_KEYLEN);
if (server_pkey == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error initializing X25519 server key: %s",
proxy_ssh_crypto_get_errors());
return -1;
}
client_pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_X25519, NULL,
client_x25519, X25519_KEYLEN);
if (client_pkey == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error initializing X25519 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 X25519 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 X25519 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 X25519 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 = X25519_KEYLEN;
if (EVP_PKEY_derive(pctx, shared_key, &shared_keylen) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error generating X25519 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 != X25519_KEYLEN) {
pr_trace_msg(trace_channel, 1,
"generated X25519 shared key length (%lu bytes) is not as expected "
"(%lu bytes)", (unsigned long) shared_keylen,
(unsigned long) X25519_KEYLEN);
}
EVP_PKEY_CTX_free(pctx);
EVP_PKEY_free(server_pkey);
EVP_PKEY_free(client_pkey);
return X25519_KEYLEN;
}
#endif /* HAVE_X25519_OPENSSL */
#if defined(HAVE_MLKEM768_OPENSSL) && defined(HAVE_SHA256_OPENSSL)
static int create_mlkem768(struct proxy_ssh_kex *kex) {
EVP_PKEY_CTX *pctx;
EVP_PKEY *pkey;
pctx = EVP_PKEY_CTX_new_id(NID_ML_KEM_768, NULL);
if (pctx == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error initializing context for MLKEM768 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 MLKEM768 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 MLKEM768 key: %s", proxy_ssh_crypto_get_errors());
EVP_PKEY_CTX_free(pctx);
return -1;
}
kex->client_mlkem768 = pkey;
kex->client_x25519_priv_key = palloc(kex_pool, X25519_KEYLEN);
kex->client_x25519_pub_key = palloc(kex_pool, X25519_KEYLEN);
return generate_x25519_keys(kex_pool, &(kex->client_x25519_priv_key),
kex->client_x25519_pub_key);
}
#endif /* HAVE_MLKEM768_OPENSSL and HAVE_SHA512_OPENSSL */
#if defined(HAVE_X25519_OPENSSL) && defined(HAVE_SHA512_OPENSSL)
static int create_sntrup761(struct proxy_ssh_kex *kex) {
kex->client_x25519_priv_key = palloc(kex_pool, X25519_KEYLEN);
kex->client_x25519_pub_key = palloc(kex_pool, X25519_KEYLEN);
if (generate_x25519_keys(kex_pool, &(kex->client_x25519_priv_key),
kex->client_x25519_pub_key) < 0) {
return -1;
}
kex->client_sntrup761_priv_key = palloc(kex_pool, sntrup761_SECRETKEYBYTES);
kex->client_sntrup761_pub_key = palloc(kex_pool, sntrup761_PUBLICKEYBYTES);
return sntrup761_keypair(kex->client_sntrup761_pub_key,
kex->client_sntrup761_priv_key);
}
#endif /* HAVE_X25519_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_MLKEM768_OPENSSL) && defined(HAVE_SHA256_OPENSSL)
"mlkem768x25519-sha256",
#endif /* HAVE_MLKEM768_OPENSSL and HAVE_SHA256_OPENSSL */
#if defined(HAVE_X448_OPENSSL) && defined(HAVE_SHA512_OPENSSL)
#if defined(HAVE_X25519_OPENSSL) && defined(HAVE_SHA512_OPENSSL)
"sntrup761x25519-sha512",
"sntrup761x25519-sha512@openssh.com",
#endif /* HAVE_X25519_OPENSSL and 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 defined(HAVE_MLKEM768_OPENSSL) && defined(HAVE_SHA256_OPENSSL)
if (kex->client_mlkem768 != NULL) {
EVP_PKEY_free(kex->client_mlkem768);
kex->client_mlkem768 = NULL;
}
#endif /* HAVE_MLKEM768_OPENSSL and HAVE_SHA256_OPENSSL */
#if defined(HAVE_X25519_OPENSSL)
if (kex->client_sntrup761_priv_key != NULL) {
pr_memscrub(kex->client_sntrup761_priv_key, sntrup761_SECRETKEYBYTES);
kex->client_sntrup761_priv_key = NULL;
}
if (kex->client_sntrup761_pub_key != NULL) {
pr_memscrub(kex->client_sntrup761_pub_key, sntrup761_PUBLICKEYBYTES);
kex->client_sntrup761_pub_key = NULL;
}
if (kex->client_x25519_priv_key != NULL) {
pr_memscrub(kex->client_x25519_priv_key, X25519_KEYLEN);
kex->client_x25519_priv_key = NULL;
}
if (kex->client_x25519_pub_key != NULL) {
pr_memscrub(kex->client_x25519_pub_key, X25519_KEYLEN);
kex->client_x25519_pub_key = NULL;
}
if (kex->server_x25519_pub_key != NULL) {
pr_memscrub(kex->server_x25519_pub_key, X25519_KEYLEN);
kex->server_x25519_pub_key = NULL;
}
#endif /* HAVE_X25519_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 defined(HAVE_MLKEM768_OPENSSL) && defined(HAVE_SHA256_OPENSSL)
if (strcmp(algo, "mlkem768x25519-sha256") == 0) {
if (create_mlkem768(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_mlkem768 = TRUE;
return 0;
}
#endif /* HAVE_MLKEM768_OPENSSL and HAVE_SHA256_OPENSSL */
#if defined(HAVE_X25519_OPENSSL) && defined(HAVE_SHA512_OPENSSL)
if (strcmp(algo, "sntrup761x25519-sha512") == 0) {
if (create_sntrup761(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_sntrup761 = TRUE;
return 0;
}
#endif /* HAVE_X25519_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. */
if (kex->k != NULL) {
klen = proxy_ssh_msg_write_mpint(&buf, &buflen, kex->k);
} else {
klen = proxy_ssh_msg_write_data(&buf, &buflen, kex->kdata, kex->kdatalen,
TRUE);
}
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 */
#if defined(HAVE_MLKEM768_OPENSSL) && defined(HAVE_SHA256_OPENSSL)
static int write_mlkem768_init(struct proxy_ssh_packet *pkt,
struct proxy_ssh_kex *kex) {
unsigned char *buf, *ptr, *key;
uint32_t buflen, bufsz, len = 0;
size_t key_len = 0;
/* In our MLKEM768 ECDH_INIT, we send our concatened mlkem768 public key,
* and our client x25519 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);
key_len = OSSL_ML_KEM_768_PUBLIC_KEY_BYTES;
key = palloc(pkt->pool, key_len);
if (EVP_PKEY_get_raw_public_key(kex->client_mlkem768, key, &key_len) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error obtaining client MLKEM768 public key: %s",
proxy_ssh_crypto_get_errors());
return -1;
}
len += proxy_ssh_msg_write_int(&buf, &buflen,
OSSL_ML_KEM_768_PUBLIC_KEY_BYTES + X25519_KEYLEN);
len += proxy_ssh_msg_write_data(&buf, &buflen, key, key_len, FALSE);
len += proxy_ssh_msg_write_data(&buf, &buflen, kex->client_x25519_pub_key,
X25519_KEYLEN, FALSE);
pkt->payload = ptr;
pkt->payload_len = len;
return 0;
}
static int get_mlkem768_shared_key(pool *p, EVP_PKEY *client_mlkem768,
unsigned char **mlkem_key, size_t *mlkem_keylen,
unsigned char *ciphertext, size_t ciphertext_len) {
EVP_PKEY_CTX *pctx = NULL;
pctx = EVP_PKEY_CTX_new_from_pkey(NULL, client_mlkem768, NULL);
if (pctx == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error initializing context from client MLKEM768 public key: %s",
proxy_ssh_crypto_get_errors());
return -1;
}
if (EVP_PKEY_decapsulate_init(pctx, NULL) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error preparing context for MLKEM768 decapsulation: %s",
proxy_ssh_crypto_get_errors());
EVP_PKEY_CTX_free(pctx);
return -1;
}
if (EVP_PKEY_decapsulate(pctx, NULL, mlkem_keylen, ciphertext,
ciphertext_len) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to discover MLKEM768 decapsulation size: %s",
proxy_ssh_crypto_get_errors());
EVP_PKEY_CTX_free(pctx);
return -1;
}
*mlkem_key = palloc(p, *mlkem_keylen);
if (EVP_PKEY_decapsulate(pctx, *mlkem_key, mlkem_keylen, ciphertext,
ciphertext_len) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error performing MLKEM768 decapsulation: %s",
proxy_ssh_crypto_get_errors());
EVP_PKEY_CTX_free(pctx);
return -1;
}
EVP_PKEY_CTX_free(pctx);
return *mlkem_keylen;
}
static const unsigned char *calculate_mlkem768_h(struct proxy_ssh_kex *kex,
pool *p, const unsigned char *hostkey_data, uint32_t hostkey_datalen,
const unsigned char *kdata, uint32_t kdatalen,
EVP_PKEY *client_mlkem768, unsigned char *client_x25519,
unsigned char *ciphertext, uint32_t ciphertext_len,
unsigned char *server_x25519, uint32_t *hlen) {
unsigned char *buf, *ptr, *mlkem768;
uint32_t buflen, bufsz, len = 0;
size_t mlkem768_len;
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.
* 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);
mlkem768_len = OSSL_ML_KEM_768_PUBLIC_KEY_BYTES;
mlkem768 = palloc(p, mlkem768_len);
if (EVP_PKEY_get_raw_public_key(client_mlkem768, mlkem768,
&mlkem768_len) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error obtaining client MLKEM768 public key: %s",
proxy_ssh_crypto_get_errors());
return NULL;
}
len += proxy_ssh_msg_write_int(&buf, &buflen, mlkem768_len + X25519_KEYLEN);
len += proxy_ssh_msg_write_data(&buf, &buflen, mlkem768, mlkem768_len, FALSE);
len += proxy_ssh_msg_write_data(&buf, &buflen, client_x25519, X25519_KEYLEN,
FALSE);
len += proxy_ssh_msg_write_int(&buf, &buflen, ciphertext_len + X25519_KEYLEN);
len += proxy_ssh_msg_write_data(&buf, &buflen, ciphertext, ciphertext_len,
FALSE);
len += proxy_ssh_msg_write_data(&buf, &buflen, server_x25519, X25519_KEYLEN,
FALSE);
/* Shared secret */
len += proxy_ssh_msg_write_data(&buf, &buflen, kdata, kdatalen, TRUE);
if (digest_data(kex, ptr, len, hlen) < 0) {
pr_memscrub(ptr, bufsz);
return NULL;
}
pr_memscrub(ptr, bufsz);
return kex_digest_buf;
}
static int read_mlkem768_reply(struct proxy_ssh_packet *pkt,
struct proxy_ssh_kex *kex) {
EVP_MD_CTX *pctx;
const unsigned char *h;
unsigned char zero_x25519[X25519_KEYLEN];
unsigned char *buf, *server_hostkey_data = NULL, *sig = NULL;
uint32_t buflen, server_hostkey_datalen = 0, siglen = 0, hlen = 0;
unsigned char *x25519_key, *mlkem_key = NULL, *ciphertext = NULL;
size_t mlkem_keylen = 0, expected_len = 0;
unsigned char *buf2, *ptr2, *kdata = NULL;
uint32_t buflen2, bufsz2, ciphertext_len = 0, reply_len = 0, kdatalen = 0;
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_x25519_priv_key, X25519_KEYLEN);
kex->client_x25519_priv_key = NULL;
EVP_PKEY_free(kex->client_mlkem768);
kex->client_mlkem768 = NULL;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error handling server host key: %s", strerror(xerrno));
errno = xerrno;
return -1;
}
ciphertext_len = OSSL_ML_KEM_768_CIPHERTEXT_BYTES;
expected_len = ciphertext_len + X25519_KEYLEN;
proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &reply_len);
if (reply_len != expected_len) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"rejecting invalid length (%lu %s, wanted %lu) of server reply bytes",
(unsigned long) reply_len, reply_len != 1 ? "bytes" : "byte",
(unsigned long) expected_len);
pr_memscrub(kex->client_x25519_priv_key, X25519_KEYLEN);
kex->client_x25519_priv_key = NULL;
EVP_PKEY_free(kex->client_mlkem768);
kex->client_mlkem768 = NULL;
return -1;
}
proxy_ssh_msg_read_data(pkt->pool, &buf, &buflen, ciphertext_len,
&ciphertext);
if (ciphertext == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error reading ECDH_REPLY: %s", strerror(errno));
pr_memscrub(kex->client_x25519_priv_key, X25519_KEYLEN);
kex->client_x25519_priv_key = NULL;
EVP_PKEY_free(kex->client_mlkem768);
kex->client_mlkem768 = NULL;
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, X25519_KEYLEN,
&(kex->server_x25519_pub_key));
if (kex->server_x25519_pub_key == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error reading ECDH_REPLY: %s", strerror(errno));
pr_memscrub(kex->client_x25519_priv_key, X25519_KEYLEN);
kex->client_x25519_priv_key = NULL;
EVP_PKEY_free(kex->client_mlkem768);
kex->client_mlkem768 = NULL;
return -1;
}
/* Watch for all-zero public keys, and reject them. */
memset(zero_x25519, '\0', sizeof(zero_x25519));
if (memcmp(kex->server_x25519_pub_key, zero_x25519, X25519_KEYLEN) == 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"rejecting invalid (all-zero) server X25519 public key");
pr_memscrub(kex->client_x25519_priv_key, X25519_KEYLEN);
kex->client_x25519_priv_key = NULL;
EVP_PKEY_free(kex->client_mlkem768);
kex->client_mlkem768 = NULL;
return -1;
}
/* Note that this X25519 shared key is not needed for long, hence why
* we can use this packet-specific pool.
*/
x25519_key = palloc(pkt->pool, X25519_KEYLEN);
pr_trace_msg(trace_channel, 12, "computing X25519 key");
res = get_x25519_shared_key((unsigned char *) x25519_key,
kex->server_x25519_pub_key, kex->client_x25519_priv_key);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error computing X25519 shared secret: %s", strerror(errno));
pr_memscrub(kex->client_x25519_priv_key, X25519_KEYLEN);
kex->client_x25519_priv_key = NULL;
EVP_PKEY_free(kex->client_mlkem768);
kex->client_mlkem768 = NULL;
return -1;
}
pr_trace_msg(trace_channel, 12, "computing MLKEM768 key");
res = get_mlkem768_shared_key(pkt->pool, kex->client_mlkem768, &mlkem_key,
&mlkem_keylen, ciphertext, ciphertext_len);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error computing MLKEM768 shared secret: %s", strerror(errno));
pr_memscrub(kex->client_x25519_priv_key, X25519_KEYLEN);
kex->client_x25519_priv_key = NULL;
EVP_PKEY_free(kex->client_mlkem768);
kex->client_mlkem768 = NULL;
return -1;
}
bufsz2 = buflen2 = mlkem_keylen + X25519_KEYLEN;
ptr2 = buf2 = palloc(pkt->pool, bufsz2);
/* Per the PQ key exchange draft, here we do:
*
* K = HASH(K_PQ || K_CL)
*
* where K_PQ is the MLKEM768 shared key, and K_CL is our shared X25519 key.
*
* Note that for this concatenation, we only want the bytes, and not the
* length prefixes, hence the last parameter being FALSE.
*/
proxy_ssh_msg_write_data(&buf2, &buflen2, mlkem_key, mlkem_keylen, FALSE);
proxy_ssh_msg_write_data(&buf2, &buflen2, x25519_key, X25519_KEYLEN, FALSE);
pctx = EVP_MD_CTX_new();
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());
pr_memscrub(kex->client_x25519_priv_key, X25519_KEYLEN);
kex->client_x25519_priv_key = NULL;
EVP_PKEY_free(kex->client_mlkem768);
kex->client_mlkem768 = NULL;
pr_memscrub(ptr2, bufsz2);
EVP_MD_CTX_free(pctx);
return -1;
}
if (EVP_DigestUpdate(pctx, ptr2, (bufsz2 - buflen2)) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error updating message digest: %s", proxy_ssh_crypto_get_errors());
pr_memscrub(kex->client_x25519_priv_key, X25519_KEYLEN);
kex->client_x25519_priv_key = NULL;
EVP_PKEY_free(kex->client_mlkem768);
kex->client_mlkem768 = NULL;
pr_memscrub(ptr2, bufsz2);
EVP_MD_CTX_free(pctx);
return -1;
}
kdatalen = EVP_MAX_MD_SIZE;
kdata = palloc(pkt->pool, kdatalen);
if (EVP_DigestFinal(pctx, kdata, &kdatalen) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error finalizing message digest: %s", proxy_ssh_crypto_get_errors());
pr_memscrub(kex->client_x25519_priv_key, X25519_KEYLEN);
kex->client_x25519_priv_key = NULL;
EVP_PKEY_free(kex->client_mlkem768);
kex->client_mlkem768 = NULL;
pr_memscrub(ptr2, bufsz2);
EVP_MD_CTX_free(pctx);
return -1;
}
EVP_MD_CTX_free(pctx);
pr_memscrub(ptr2, bufsz2);
kex->kdatalen = kdatalen;
kex->kdata = palloc(kex->pool, kex->kdatalen);
memcpy(kex->kdata, kdata, kex->kdatalen);
pr_memscrub(kdata, kdatalen);
/* Calculate H */
h = calculate_mlkem768_h(kex, pkt->pool, server_hostkey_data,
server_hostkey_datalen, kex->kdata, kex->kdatalen, kex->client_mlkem768,
kex->client_x25519_pub_key, ciphertext, ciphertext_len,
kex->server_x25519_pub_key, &hlen);
if (h == NULL) {
pr_memscrub(kex->client_x25519_priv_key, X25519_KEYLEN);
kex->client_x25519_priv_key = NULL;
EVP_PKEY_free(kex->client_mlkem768);
kex->client_mlkem768 = NULL;
pr_memscrub((char *) kex->kdata, kex->kdatalen);
kex->kdata = NULL;
kex->kdatalen = 0;
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) {
pr_memscrub(kex->client_x25519_priv_key, X25519_KEYLEN);
kex->client_x25519_priv_key = NULL;
EVP_PKEY_free(kex->client_mlkem768);
kex->client_mlkem768 = NULL;
pr_memscrub((char *) kex->kdata, kex->kdatalen);
kex->kdata = NULL;
kex->kdatalen = 0;
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 keys. */
pr_memscrub(kex->client_x25519_priv_key, X25519_KEYLEN);
kex->client_x25519_priv_key = NULL;
EVP_PKEY_free(kex->client_mlkem768);
kex->client_mlkem768 = NULL;
return 0;
}
static int handle_kex_mlkem768(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_mlkem768_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_mlkem768_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_MLKEM768_OPENSSL and HAVE_SHA256_OPENSSL */
#if defined(HAVE_X25519_OPENSSL) && defined(HAVE_SHA512_OPENSSL)
static int write_sntrup761_init(struct proxy_ssh_packet *pkt,
struct proxy_ssh_kex *kex) {
unsigned char *buf, *ptr;
uint32_t buflen, bufsz, len = 0;
/* In our sntrup761 ECDH_INIT, we send our concatened sntrup761 public key,
* and our client x25519 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_int(&buf, &buflen,
sntrup761_PUBLICKEYBYTES + X25519_KEYLEN);
len += proxy_ssh_msg_write_data(&buf, &buflen, kex->client_sntrup761_pub_key,
sntrup761_PUBLICKEYBYTES, FALSE);
len += proxy_ssh_msg_write_data(&buf, &buflen, kex->client_x25519_pub_key,
X25519_KEYLEN, FALSE);
pkt->payload = ptr;
pkt->payload_len = len;
return 0;
}
static int get_sntrup761_shared_key(pool *p, unsigned char *client_sntrup761,
unsigned char **sntrup_key, size_t *sntrup_keylen,
unsigned char *ciphertext, size_t ciphertext_len) {
*sntrup_keylen = sntrup761_BYTES;
*sntrup_key = palloc(p, *sntrup_keylen);
sntrup761_dec(*sntrup_key, ciphertext, client_sntrup761);
return *sntrup_keylen;
}
static const unsigned char *calculate_sntrup761_h(struct proxy_ssh_kex *kex,
pool *p, const unsigned char *hostkey_data, uint32_t hostkey_datalen,
const unsigned char *kdata, uint32_t kdatalen,
unsigned char *client_sntrup761, unsigned char *client_x25519,
unsigned char *ciphertext, uint32_t ciphertext_len,
unsigned char *server_x25519, uint32_t *hlen) {
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.
* 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);
len += proxy_ssh_msg_write_int(&buf, &buflen,
sntrup761_PUBLICKEYBYTES + X25519_KEYLEN);
len += proxy_ssh_msg_write_data(&buf, &buflen, client_sntrup761,
sntrup761_PUBLICKEYBYTES, FALSE);
len += proxy_ssh_msg_write_data(&buf, &buflen, client_x25519, X25519_KEYLEN,
FALSE);
len += proxy_ssh_msg_write_int(&buf, &buflen, ciphertext_len + X25519_KEYLEN);
len += proxy_ssh_msg_write_data(&buf, &buflen, ciphertext, ciphertext_len,
FALSE);
len += proxy_ssh_msg_write_data(&buf, &buflen, server_x25519, X25519_KEYLEN,
FALSE);
/* Shared secret */
len += proxy_ssh_msg_write_data(&buf, &buflen, kdata, kdatalen, TRUE);
if (digest_data(kex, ptr, len, hlen) < 0) {
pr_memscrub(ptr, bufsz);
return NULL;
}
pr_memscrub(ptr, bufsz);
return kex_digest_buf;
}
static int read_sntrup761_reply(struct proxy_ssh_packet *pkt,
struct proxy_ssh_kex *kex) {
EVP_MD_CTX *pctx;
const unsigned char *h;
unsigned char zero_x25519[X25519_KEYLEN];
unsigned char *buf, *server_hostkey_data = NULL, *sig = NULL;
uint32_t buflen, server_hostkey_datalen = 0, siglen = 0, hlen = 0;
unsigned char *x25519_key, *sntrup_key = NULL, *ciphertext = NULL;
size_t sntrup_keylen = 0, expected_len = 0;
unsigned char *buf2, *ptr2, *kdata = NULL;
uint32_t buflen2, bufsz2, ciphertext_len = 0, reply_len = 0, kdatalen = 0;
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_sntrup761_priv_key, sntrup761_SECRETKEYBYTES);
kex->client_sntrup761_priv_key = NULL;
pr_memscrub(kex->client_x25519_priv_key, X25519_KEYLEN);
kex->client_x25519_priv_key = NULL;
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error handling server host key: %s", strerror(xerrno));
errno = xerrno;
return -1;
}
ciphertext_len = sntrup761_CIPHERTEXTBYTES;
expected_len = ciphertext_len + X25519_KEYLEN;
proxy_ssh_msg_read_int(pkt->pool, &buf, &buflen, &reply_len);
if (reply_len != expected_len) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"rejecting invalid length (%lu %s, wanted %lu) of server reply bytes",
(unsigned long) reply_len, reply_len != 1 ? "bytes" : "byte",
(unsigned long) expected_len);
pr_memscrub(kex->client_sntrup761_priv_key, sntrup761_SECRETKEYBYTES);
kex->client_sntrup761_priv_key = NULL;
pr_memscrub(kex->client_x25519_priv_key, X25519_KEYLEN);
kex->client_x25519_priv_key = NULL;
return -1;
}
proxy_ssh_msg_read_data(pkt->pool, &buf, &buflen, ciphertext_len,
&ciphertext);
if (ciphertext == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error reading ECDH_REPLY: %s", strerror(errno));
pr_memscrub(kex->client_sntrup761_priv_key, sntrup761_SECRETKEYBYTES);
kex->client_sntrup761_priv_key = NULL;
pr_memscrub(kex->client_x25519_priv_key, X25519_KEYLEN);
kex->client_x25519_priv_key = NULL;
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, X25519_KEYLEN,
&(kex->server_x25519_pub_key));
if (kex->server_x25519_pub_key == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error reading ECDH_REPLY: %s", strerror(errno));
pr_memscrub(kex->client_sntrup761_priv_key, sntrup761_SECRETKEYBYTES);
kex->client_sntrup761_priv_key = NULL;
pr_memscrub(kex->client_x25519_priv_key, X25519_KEYLEN);
kex->client_x25519_priv_key = NULL;
return -1;
}
/* Watch for all-zero public keys, and reject them. */
memset(zero_x25519, '\0', sizeof(zero_x25519));
if (memcmp(kex->server_x25519_pub_key, zero_x25519, X25519_KEYLEN) == 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"rejecting invalid (all-zero) server X25519 public key");
pr_memscrub(kex->client_sntrup761_priv_key, sntrup761_SECRETKEYBYTES);
kex->client_sntrup761_priv_key = NULL;
pr_memscrub(kex->client_x25519_priv_key, X25519_KEYLEN);
kex->client_x25519_priv_key = NULL;
return -1;
}
/* Note that this X25519 shared key is not needed for long, hence why
* we can use this packet-specific pool.
*/
x25519_key = palloc(pkt->pool, X25519_KEYLEN);
pr_trace_msg(trace_channel, 12, "computing X25519 key");
res = get_x25519_shared_key((unsigned char *) x25519_key,
kex->server_x25519_pub_key, kex->client_x25519_priv_key);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error computing X25519 shared secret: %s", strerror(errno));
pr_memscrub(kex->client_sntrup761_priv_key, sntrup761_SECRETKEYBYTES);
kex->client_sntrup761_priv_key = NULL;
pr_memscrub(kex->client_x25519_priv_key, X25519_KEYLEN);
kex->client_x25519_priv_key = NULL;
return -1;
}
pr_trace_msg(trace_channel, 12, "computing SNTRUP761 key");
res = get_sntrup761_shared_key(pkt->pool, kex->client_sntrup761_priv_key,
&sntrup_key, &sntrup_keylen, ciphertext, ciphertext_len);
if (res < 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error computing SNTRUP761 shared secret: %s", strerror(errno));
pr_memscrub(kex->client_sntrup761_priv_key, sntrup761_SECRETKEYBYTES);
kex->client_sntrup761_priv_key = NULL;
pr_memscrub(kex->client_x25519_priv_key, X25519_KEYLEN);
kex->client_x25519_priv_key = NULL;
return -1;
}
bufsz2 = buflen2 = sntrup_keylen + X25519_KEYLEN;
ptr2 = buf2 = palloc(pkt->pool, bufsz2);
/* Per the PQ key exchange draft, here we do:
*
* K = HASH(K_PQ || K_CL)
*
* where K_PQ is the SNTRUP761 shared key, and K_CL is our shared X25519 key.
*
* Note that for this concatenation, we only want the bytes, and not the
* length prefixes, hence the last parameter being FALSE.
*/
proxy_ssh_msg_write_data(&buf2, &buflen2, sntrup_key, sntrup_keylen, FALSE);
proxy_ssh_msg_write_data(&buf2, &buflen2, x25519_key, X25519_KEYLEN, FALSE);
pctx = EVP_MD_CTX_new();
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());
pr_memscrub(kex->client_sntrup761_priv_key, sntrup761_SECRETKEYBYTES);
kex->client_sntrup761_priv_key = NULL;
pr_memscrub(kex->client_x25519_priv_key, X25519_KEYLEN);
kex->client_x25519_priv_key = NULL;
pr_memscrub(ptr2, bufsz2);
EVP_MD_CTX_free(pctx);
return -1;
}
if (EVP_DigestUpdate(pctx, ptr2, (bufsz2 - buflen2)) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error updating message digest: %s", proxy_ssh_crypto_get_errors());
pr_memscrub(kex->client_sntrup761_priv_key, sntrup761_SECRETKEYBYTES);
kex->client_sntrup761_priv_key = NULL;
pr_memscrub(kex->client_x25519_priv_key, X25519_KEYLEN);
kex->client_x25519_priv_key = NULL;
pr_memscrub(ptr2, bufsz2);
EVP_MD_CTX_free(pctx);
return -1;
}
kdatalen = EVP_MAX_MD_SIZE;
kdata = palloc(pkt->pool, kdatalen);
if (EVP_DigestFinal(pctx, kdata, &kdatalen) != 1) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error finalizing message digest: %s", proxy_ssh_crypto_get_errors());
pr_memscrub(kex->client_sntrup761_priv_key, sntrup761_SECRETKEYBYTES);
kex->client_sntrup761_priv_key = NULL;
pr_memscrub(kex->client_x25519_priv_key, X25519_KEYLEN);
kex->client_x25519_priv_key = NULL;
pr_memscrub(ptr2, bufsz2);
EVP_MD_CTX_free(pctx);
return -1;
}
EVP_MD_CTX_free(pctx);
pr_memscrub(ptr2, bufsz2);
kex->kdatalen = kdatalen;
kex->kdata = palloc(kex->pool, kex->kdatalen);
memcpy(kex->kdata, kdata, kex->kdatalen);
pr_memscrub(kdata, kdatalen);
/* Calculate H */
h = calculate_sntrup761_h(kex, pkt->pool, server_hostkey_data,
server_hostkey_datalen, kex->kdata, kex->kdatalen,
kex->client_sntrup761_pub_key, kex->client_x25519_pub_key,
ciphertext, ciphertext_len, kex->server_x25519_pub_key, &hlen);
if (h == NULL) {
pr_memscrub(kex->client_sntrup761_priv_key, sntrup761_SECRETKEYBYTES);
kex->client_sntrup761_priv_key = NULL;
pr_memscrub(kex->client_x25519_priv_key, X25519_KEYLEN);
kex->client_x25519_priv_key = NULL;
pr_memscrub((char *) kex->kdata, kex->kdatalen);
kex->kdata = NULL;
kex->kdatalen = 0;
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) {
pr_memscrub(kex->client_sntrup761_priv_key, sntrup761_SECRETKEYBYTES);
kex->client_sntrup761_priv_key = NULL;
pr_memscrub(kex->client_x25519_priv_key, X25519_KEYLEN);
kex->client_x25519_priv_key = NULL;
pr_memscrub((char *) kex->kdata, kex->kdatalen);
kex->kdata = NULL;
kex->kdatalen = 0;
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 keys. */
pr_memscrub(kex->client_sntrup761_priv_key, sntrup761_SECRETKEYBYTES);
kex->client_sntrup761_priv_key = NULL;
pr_memscrub(kex->client_x25519_priv_key, X25519_KEYLEN);
kex->client_x25519_priv_key = NULL;
return 0;
}
static int handle_kex_sntrup761(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_sntrup761_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_sntrup761_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_X25519_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);
}
if (strcmp(algo, "diffie-hellman-group-exchange-sha1") == 0) {
return handle_kex_dh_gex(kex, conn);
}
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)
if (strcmp(algo, "diffie-hellman-group-exchange-sha256") == 0) {
return handle_kex_dh_gex(kex, conn);
}
if (strcmp(algo, "rsa2048-sha256") == 0) {
return handle_kex_rsa(kex, conn);
}
#endif
#if defined(PR_USE_OPENSSL_ECC)
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)
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)
if (strcmp(algo, "curve448-sha512") == 0) {
return handle_kex_curve448(kex, conn);
}
#endif /* HAVE_X448_OPENSSL and HAVE_SHA512_OPENSSL */
#if defined(HAVE_MLKEM768_OPENSSL) && defined(HAVE_SHA256_OPENSSL)
if (strcmp(algo, "mlkem768x25519-sha256") == 0) {
return handle_kex_mlkem768(kex, conn);
}
#endif /* HAVE_MLKEM768_OPENSSL and HAVE_SHA256_OPENSSL */
#if defined(HAVE_X25519_OPENSSL) && defined(HAVE_SHA512_OPENSSL)
if (strcmp(algo, "sntrup761x25519-sha512") == 0 ||
strcmp(algo, "sntrup761x25519-sha512@openssh.com") == 0) {
return handle_kex_sntrup761(kex, conn);
}
#endif /* HAVE_X25519_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;
}
proftpd-mod_proxy-0.9.7/lib/proxy/ssh/keys.c 0000664 0000000 0000000 00000475547 15207633221 0021115 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH key mgmt (keys)
* Copyright (c) 2021-2026 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see .
*
* As a special exemption, TJ Saunders and other respective copyright holders
* 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_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);
}
}
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) {
#if defined(EVP_PKEY_NONE)
case EVP_PKEY_NONE:
key_desc = "undefined";
break;
#endif /* EVP_PKEY_NONE */
#if defined(EVP_PKEY_RSA)
case EVP_PKEY_RSA:
key_desc = "RSA";
break;
#endif /* EVP_PKEY_RSA */
#if defined(EVP_PKEY_DSA)
case EVP_PKEY_DSA:
key_desc = "DSA";
break;
#endif /* EVP_PKEY_DSA */
#if defined(EVP_PKEY_DH)
case EVP_PKEY_DH:
key_desc = "DH";
break;
#endif /* EVP_PKEY_DH */
#if defined(EVP_PKEY_EC)
case EVP_PKEY_EC:
key_desc = "ECC";
break;
#endif /* EVP_PKEY_EC */
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;
}
#endif /* PR_USE_OPENSSL_ECC */
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;
}
#if defined(PR_USE_OPENSSL_ECC)
/* 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;
default:
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;
(void) 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 comments 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;
(void) len;
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;
(void) len;
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) {
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;
}
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;
}
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;
}
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;
default:
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;
default:
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);
if (rsa == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error obtaining RSA key: %s", proxy_ssh_crypto_get_errors());
errno = EINVAL;
return -1;
}
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);
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 DSA signature: missing signature data");
errno = EINVAL;
return -1;
}
dsa = EVP_PKEY_get1_DSA(pkey);
if (dsa == NULL) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"error obtaining DSA key: %s", proxy_ssh_crypto_get_errors());
errno = EINVAL;
return -1;
}
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) {
if (strcmp(pubkey_algo, sig_type) != 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to verify signed data: signature type '%s' does not match "
"publickey algorithm '%s'", sig_type, pubkey_algo);
errno = EINVAL;
return -1;
}
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) {
if (strcmp(pubkey_algo, sig_type) != 0) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"unable to verify signed data: signature type '%s' does not match "
"publickey algorithm '%s'", sig_type, pubkey_algo);
errno = EINVAL;
return -1;
}
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();
}
proftpd-mod_proxy-0.9.7/lib/proxy/ssh/mac.c 0000664 0000000 0000000 00000100715 15207633221 0020660 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH MACs
* Copyright (c) 2021-2026 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU 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 exemption, TJ Saunders and other respective copyright holders
* 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"
#include
#include
struct proxy_ssh_mac {
pool *pool;
const char *algo;
unsigned int algo_type;
int is_etm;
int free_digest;
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_NONE 1
#define PROXY_SSH_MAC_ALGO_TYPE_HMAC 2
#define PROXY_SSH_MAC_ALGO_TYPE_UMAC64 3
#define PROXY_SSH_MAC_ALGO_TYPE_UMAC128 4
#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, FALSE, NULL, NULL, 0, 0, 0 },
{ NULL, NULL, 0, FALSE, 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, FALSE, NULL, NULL, 0, 0, 0 },
{ NULL, NULL, 0, FALSE, 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 *mac);
#if !defined(HAVE_TIMINGSAFE_BCMP)
static int timingsafe_bcmp(const void *b1, const void *b2, size_t n) {
const unsigned char *p1 = b1, *p2 = b2;
int ret = 0;
for (; n > 0; n--) {
ret |= *p1++ ^ *p2++;
}
return (ret != 0);
}
#endif /* HAVE_TIMINGSAFE_BCMP */
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 != NULL) {
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 != NULL) {
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 (mac->algo_type == PROXY_SSH_MAC_ALGO_TYPE_NONE) {
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 unsigned int get_algo_type(const char *algo) {
unsigned int algo_type = 0;
if (strcmp(algo, "none") == 0) {
algo_type = PROXY_SSH_MAC_ALGO_TYPE_NONE;
} else if (strcmp(algo, "umac-64@openssh.com") == 0 ||
strcmp(algo, "umac-64-etm@openssh.com") == 0) {
algo_type = PROXY_SSH_MAC_ALGO_TYPE_UMAC64;
} else if (strcmp(algo, "umac-128@openssh.com") == 0 ||
strcmp(algo, "umac-128-etm@openssh.com") == 0) {
algo_type = PROXY_SSH_MAC_ALGO_TYPE_UMAC128;
} else {
algo_type = PROXY_SSH_MAC_ALGO_TYPE_HMAC;
}
return algo_type;
}
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 (timingsafe_bcmp(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 (mac->algo_type == PROXY_SSH_MAC_ALGO_TYPE_NONE) {
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 ||
read_macs[read_mac_idx].algo_type == PROXY_SSH_MAC_ALGO_TYPE_NONE) {
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;
if (read_macs[idx].free_digest == TRUE) {
proxy_ssh_crypto_free_digest(read_macs[idx].digest);
read_macs[idx].digest = NULL;
}
break;
case PROXY_SSH_MAC_ALGO_TYPE_UMAC128:
proxy_ssh_umac128_delete(umac_read_ctxs[idx]);
umac_read_ctxs[idx] = NULL;
if (read_macs[idx].free_digest == TRUE) {
proxy_ssh_crypto_free_digest(read_macs[idx].digest);
read_macs[idx].digest = NULL;
}
break;
}
}
read_macs[idx].digest = proxy_ssh_crypto_get_digest(algo, &mac_len,
&(read_macs[idx].free_digest));
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);
read_macs[idx].algo_type = get_algo_type(algo);
if (read_macs[idx].algo_type == PROXY_SSH_MAC_ALGO_TYPE_UMAC64) {
umac_read_ctxs[idx] = proxy_ssh_umac_alloc();
} else if (read_macs[idx].algo_type == PROXY_SSH_MAC_ALGO_TYPE_UMAC128) {
umac_read_ctxs[idx] = proxy_ssh_umac128_alloc();
}
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;
if (write_macs[idx].free_digest == TRUE) {
proxy_ssh_crypto_free_digest(write_macs[idx].digest);
write_macs[idx].digest = NULL;
}
break;
case PROXY_SSH_MAC_ALGO_TYPE_UMAC128:
proxy_ssh_umac128_delete(umac_write_ctxs[idx]);
umac_write_ctxs[idx] = NULL;
if (write_macs[idx].free_digest == TRUE) {
proxy_ssh_crypto_free_digest(write_macs[idx].digest);
write_macs[idx].digest = NULL;
}
break;
}
}
write_macs[idx].digest = proxy_ssh_crypto_get_digest(algo, &mac_len,
&(write_macs[idx].free_digest));
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);
write_macs[idx].algo_type = get_algo_type(algo);
if (write_macs[idx].algo_type == PROXY_SSH_MAC_ALGO_TYPE_UMAC64) {
umac_write_ctxs[idx] = proxy_ssh_umac_alloc();
} else if (write_macs[idx].algo_type == PROXY_SSH_MAC_ALGO_TYPE_UMAC128) {
umac_write_ctxs[idx] = proxy_ssh_umac128_alloc();
}
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;
}
proftpd-mod_proxy-0.9.7/lib/proxy/ssh/misc.c 0000664 0000000 0000000 00000005012 15207633221 0021045 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH miscellany
* Copyright (c) 2021-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/ssh/misc.h"
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;
}
proftpd-mod_proxy-0.9.7/lib/proxy/ssh/msg.c 0000664 0000000 0000000 00000041076 15207633221 0020712 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH message format
* Copyright (c) 2021-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/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_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 */
proftpd-mod_proxy-0.9.7/lib/proxy/ssh/packet.c 0000664 0000000 0000000 00000210276 15207633221 0021373 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH packet IO
* Copyright (c) 2021-2026 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU 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 exemption, TJ Saunders and other respective copyright holders
* 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
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";
/* This is admittedly an arbitrary upper limit on the number of EXT_INFO
* extensions we will handle.
*
* draft-ssh-ext-info-05 currently defines five:
*
* server-sig-algs
* no-flow-control
* accept-channels
* elevation
* delay-compression
*
* And some implementations, like OpenSSH, will have their own namespaced
* extensions.
*/
#define PROXY_SSH_MAX_EXT_INFO_COUNT 32
#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);
}
}
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, int chachapoly) {
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 ||
chachapoly == 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;
res = proxy_ssh_cipher_read_packet_len(pkt, buf, readsz, &ptr, &len,
&packet_len);
if (res < 0) {
return -1;
}
pkt->packet_len = packet_len;
/* 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, int chachapoly) {
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. Similarly
* for ChaChaPoly.
*/
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 && chachapoly == 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 = palloc(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 = palloc(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 ||
chachapoly == 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. Similarly for ChaChaPoly.
*/
if (etm_mac == TRUE ||
chachapoly == 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 = palloc(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;
pkt->mac = NULL;
pkt->mac_len = 0;
pkt->seqno = 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";
default:
break;
}
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;
}
}
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 chachapoly = FALSE, 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).
*
* Note that OpenSSH's ChaChaPoly cipher does encrypt the packet length,
* and uses it as 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);
}
/* OpenSSH's ChaChaPoly acts like an ETM cipher as well. */
chachapoly = proxy_ssh_cipher_is_read_chachapoly();
pkt->seqno = packet_server_seqno;
while (TRUE) {
uint32_t encrypted_datasz, req_blocksz;
pr_signals_handle();
/* This is in a while loop in order to consume any debug/ignore
* messages which the client may send.
*/
buflen = 0;
if (read_packet_len(conn, pkt, buf, &offset, &buflen, bufsz, etm_mac,
chachapoly) < 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 &&
chachapoly == 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);
pkt->payload_len = (pkt->packet_len - pkt->padding_len - 1);
pr_trace_msg(trace_channel, 20, "SSH2 packet payload len = %u bytes",
(unsigned int) 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, chachapoly) < 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;
}
if (chachapoly == TRUE) {
/* The custom authentication tag for ChaChaPoly is 16 bytes. */
pkt->mac_len = 16;
} else {
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 ||
chachapoly == TRUE) {
unsigned char *buf2;
size_t buflen2, bufsz2;
bufsz2 = buflen2 = pkt->mac_len;
buf2 = palloc(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;
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 = palloc(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);
if (pkt->packet_len < (pkt->padding_len + 1)) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"illegal padding length (%u bytes) exceeds packet length "
"(%lu bytes)", (unsigned int) pkt->padding_len,
(unsigned long) pkt->packet_len);
read_packet_discard(conn);
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);
if (pkt->payload_len > 0) {
pkt->payload = palloc(pkt->pool, pkt->payload_len);
memmove(pkt->payload, buf2 + offset, pkt->payload_len);
}
pkt->padding = palloc(pkt->pool, pkt->padding_len);
memmove(pkt->padding, buf2 + offset + pkt->payload_len, pkt->padding_len);
} else {
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;
}
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 (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;
}
/* 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 (except for
* ChaChaPoly).
*/
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).
*
* The OpenSSH ChaChaPoly cipher does encrypt the packet length, on the
* other hand, and uses a separate 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;
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();
}
(void) write_len;
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);
}
(void) len;
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) len;
(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");
if (ext_count > PROXY_SSH_MAX_EXT_INFO_COUNT) {
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
"server sent too many EXT_INFO extensions (%lu, max %lu), ignoring",
(unsigned long) ext_count, (unsigned long) PROXY_SSH_MAX_EXT_INFO_COUNT);
destroy_pool(pkt->pool);
return;
}
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);
(void) 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);
(void) len;
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;
} 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;
}
proftpd-mod_proxy-0.9.7/lib/proxy/ssh/poly1305.c 0000664 0000000 0000000 00000011075 15207633221 0021414 0 ustar 00root root 0000000 0000000 /*
* Public Domain poly1305 from Andrew Moon
* poly1305-donna-unrolled.c from https://github.com/floodyberry/poly1305-donna
*/
#include "mod_proxy.h"
#include "proxy/ssh/poly1305.h"
#if defined(HAVE_EVP_CHACHA20_OPENSSL) && \
!defined(HAVE_BROKEN_CHACHA20)
#define mul32x32_64(a,b) ((uint64_t)(a) * (b))
#define U8TO32_LE(p) \
(((uint32_t)((p)[0])) | \
((uint32_t)((p)[1]) << 8) | \
((uint32_t)((p)[2]) << 16) | \
((uint32_t)((p)[3]) << 24))
#define U32TO8_LE(p, v) \
do { \
(p)[0] = (uint8_t)((v)); \
(p)[1] = (uint8_t)((v) >> 8); \
(p)[2] = (uint8_t)((v) >> 16); \
(p)[3] = (uint8_t)((v) >> 24); \
} while (0)
void
poly1305_auth(unsigned char out[POLY1305_TAGLEN], const unsigned char *m, size_t inlen, const unsigned char key[POLY1305_KEYLEN]) {
uint32_t t0,t1,t2,t3;
uint32_t h0,h1,h2,h3,h4;
uint32_t r0,r1,r2,r3,r4;
uint32_t s1,s2,s3,s4;
uint32_t b, nb;
size_t j;
uint64_t t[5];
uint64_t f0,f1,f2,f3;
uint32_t g0,g1,g2,g3,g4;
uint64_t c;
unsigned char mp[16];
/* clamp key */
t0 = U8TO32_LE(key+0);
t1 = U8TO32_LE(key+4);
t2 = U8TO32_LE(key+8);
t3 = U8TO32_LE(key+12);
/* precompute multipliers */
r0 = t0 & 0x3ffffff; t0 >>= 26; t0 |= t1 << 6;
r1 = t0 & 0x3ffff03; t1 >>= 20; t1 |= t2 << 12;
r2 = t1 & 0x3ffc0ff; t2 >>= 14; t2 |= t3 << 18;
r3 = t2 & 0x3f03fff; t3 >>= 8;
r4 = t3 & 0x00fffff;
s1 = r1 * 5;
s2 = r2 * 5;
s3 = r3 * 5;
s4 = r4 * 5;
/* init state */
h0 = 0;
h1 = 0;
h2 = 0;
h3 = 0;
h4 = 0;
/* full blocks */
if (inlen < 16) goto poly1305_donna_atmost15bytes;
poly1305_donna_16bytes:
m += 16;
inlen -= 16;
t0 = U8TO32_LE(m-16);
t1 = U8TO32_LE(m-12);
t2 = U8TO32_LE(m-8);
t3 = U8TO32_LE(m-4);
h0 += t0 & 0x3ffffff;
h1 += ((((uint64_t)t1 << 32) | t0) >> 26) & 0x3ffffff;
h2 += ((((uint64_t)t2 << 32) | t1) >> 20) & 0x3ffffff;
h3 += ((((uint64_t)t3 << 32) | t2) >> 14) & 0x3ffffff;
h4 += (t3 >> 8) | (1 << 24);
poly1305_donna_mul:
t[0] = mul32x32_64(h0,r0) + mul32x32_64(h1,s4) + mul32x32_64(h2,s3) + mul32x32_64(h3,s2) + mul32x32_64(h4,s1);
t[1] = mul32x32_64(h0,r1) + mul32x32_64(h1,r0) + mul32x32_64(h2,s4) + mul32x32_64(h3,s3) + mul32x32_64(h4,s2);
t[2] = mul32x32_64(h0,r2) + mul32x32_64(h1,r1) + mul32x32_64(h2,r0) + mul32x32_64(h3,s4) + mul32x32_64(h4,s3);
t[3] = mul32x32_64(h0,r3) + mul32x32_64(h1,r2) + mul32x32_64(h2,r1) + mul32x32_64(h3,r0) + mul32x32_64(h4,s4);
t[4] = mul32x32_64(h0,r4) + mul32x32_64(h1,r3) + mul32x32_64(h2,r2) + mul32x32_64(h3,r1) + mul32x32_64(h4,r0);
h0 = (uint32_t)t[0] & 0x3ffffff; c = (t[0] >> 26);
t[1] += c; h1 = (uint32_t)t[1] & 0x3ffffff; b = (uint32_t)(t[1] >> 26);
t[2] += b; h2 = (uint32_t)t[2] & 0x3ffffff; b = (uint32_t)(t[2] >> 26);
t[3] += b; h3 = (uint32_t)t[3] & 0x3ffffff; b = (uint32_t)(t[3] >> 26);
t[4] += b; h4 = (uint32_t)t[4] & 0x3ffffff; b = (uint32_t)(t[4] >> 26);
h0 += b * 5;
if (inlen >= 16) goto poly1305_donna_16bytes;
/* final bytes */
poly1305_donna_atmost15bytes:
if (!inlen) goto poly1305_donna_finish;
for (j = 0; j < inlen; j++) mp[j] = m[j];
mp[j++] = 1;
for (; j < 16; j++) mp[j] = 0;
inlen = 0;
t0 = U8TO32_LE(mp+0);
t1 = U8TO32_LE(mp+4);
t2 = U8TO32_LE(mp+8);
t3 = U8TO32_LE(mp+12);
h0 += t0 & 0x3ffffff;
h1 += ((((uint64_t)t1 << 32) | t0) >> 26) & 0x3ffffff;
h2 += ((((uint64_t)t2 << 32) | t1) >> 20) & 0x3ffffff;
h3 += ((((uint64_t)t3 << 32) | t2) >> 14) & 0x3ffffff;
h4 += (t3 >> 8);
goto poly1305_donna_mul;
poly1305_donna_finish:
b = h0 >> 26; h0 = h0 & 0x3ffffff;
h1 += b; b = h1 >> 26; h1 = h1 & 0x3ffffff;
h2 += b; b = h2 >> 26; h2 = h2 & 0x3ffffff;
h3 += b; b = h3 >> 26; h3 = h3 & 0x3ffffff;
h4 += b; b = h4 >> 26; h4 = h4 & 0x3ffffff;
h0 += b * 5; b = h0 >> 26; h0 = h0 & 0x3ffffff;
h1 += b;
g0 = h0 + 5; b = g0 >> 26; g0 &= 0x3ffffff;
g1 = h1 + b; b = g1 >> 26; g1 &= 0x3ffffff;
g2 = h2 + b; b = g2 >> 26; g2 &= 0x3ffffff;
g3 = h3 + b; b = g3 >> 26; g3 &= 0x3ffffff;
g4 = h4 + b - (1 << 26);
b = (g4 >> 31) - 1;
nb = ~b;
h0 = (h0 & nb) | (g0 & b);
h1 = (h1 & nb) | (g1 & b);
h2 = (h2 & nb) | (g2 & b);
h3 = (h3 & nb) | (g3 & b);
h4 = (h4 & nb) | (g4 & b);
f0 = ((h0 ) | (h1 << 26)) + (uint64_t)U8TO32_LE(&key[16]);
f1 = ((h1 >> 6) | (h2 << 20)) + (uint64_t)U8TO32_LE(&key[20]);
f2 = ((h2 >> 12) | (h3 << 14)) + (uint64_t)U8TO32_LE(&key[24]);
f3 = ((h3 >> 18) | (h4 << 8)) + (uint64_t)U8TO32_LE(&key[28]);
U32TO8_LE(&out[ 0], f0); f1 += (f0 >> 32);
U32TO8_LE(&out[ 4], f1); f2 += (f1 >> 32);
U32TO8_LE(&out[ 8], f2); f3 += (f2 >> 32);
U32TO8_LE(&out[12], f3);
}
#endif /* HAVE_EVP_CHACHA20_OPENSSL and !HAVE_BROKEN_CHACHA20 */
proftpd-mod_proxy-0.9.7/lib/proxy/ssh/provider.c 0000664 0000000 0000000 00000020454 15207633221 0021753 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy OpenSSL provider
* Copyright (c) 2026 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU 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 exemption, TJ Saunders and other respective copyright holders
* 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"
#include "proxy/ssh/provider.h"
/* The mod_sftp module in ProFTPD 1.3.10rc2 also registers a 'umac' provider
* with OpenSSL; see Issue #2120.
*
* So we only need our copy of that provider if using an older version of
* ProFTPD. Otherwise, we can reuse the algorithms provided by mod_sftp's
* provider.
*/
#if OPENSSL_VERSION_NUMBER >= 0x40000000L && !defined(HAVE_LIBRESSL) && \
PROFTPD_VERSION_NUMBER < 0x0001030A02
# include
# include
# include
# include
# include
static OSSL_PROVIDER *umac_provider = NULL;
static const char *trace_channel = "proxy.ssh.provider";
/* Our custom algorithm provider implementation. */
/* UMAC */
typedef struct umac_ctx_st {
struct umac_ctx *umac;
} UMAC_CTX;
static void *umac_ctx_new(void *vctx) {
UMAC_CTX *ctx;
ctx = OPENSSL_zalloc(sizeof(UMAC_CTX));
return ctx;
}
static void umac_ctx_free(void *vctx) {
UMAC_CTX *ctx;
ctx = vctx;
if (ctx->umac != NULL) {
proxy_ssh_umac_delete(ctx->umac);
ctx->umac = NULL;
}
OPENSSL_free(ctx);
}
/* The Provider interface for digests expects an "init" callback, even though
* it is not functionally needed for our situation.
*/
static int umac_md_init(void *vctx) {
(void) vctx;
return 1;
}
static const OSSL_PARAM umac_params[] = {
OSSL_PARAM_size_t(OSSL_DIGEST_PARAM_BLOCK_SIZE, NULL),
OSSL_PARAM_size_t(OSSL_DIGEST_PARAM_SIZE, NULL),
OSSL_PARAM_END
};
/* UMAC64 */
static int umac64_md_update(void *vctx, const unsigned char *data, size_t len) {
UMAC_CTX *ctx;
struct umac_ctx *umac;
ctx = vctx;
umac = ctx->umac;
/* The allocation of the umac_ctx is deliberately delayed until the first
* update, since the computation of keys depends on the initial bytes
* provided.
*/
if (umac == NULL) {
umac = proxy_ssh_umac_new((unsigned char *) data);
if (umac == NULL) {
return 0;
}
ctx->umac = umac;
return 1;
}
return proxy_ssh_umac_update(umac, (unsigned char *) data, (long) len);
}
static int umac64_md_final(void *vctx, unsigned char *out, size_t *out_len,
size_t outsz) {
int res = 1;
struct umac_ctx *ctx;
unsigned char nonce[8];
ctx = vctx;
*out_len = outsz;
if (outsz != 0) {
res = proxy_ssh_umac_final(ctx, out, nonce);
}
return res;
}
static int umac64_get_params(void *provctx, OSSL_PARAM params[]) {
OSSL_PARAM *p;
int ok = 1;
p = OSSL_PARAM_locate(params, OSSL_DIGEST_PARAM_BLOCK_SIZE);
if (p != NULL) {
if (OSSL_PARAM_set_size_t(p, 32) != 1) {
ok = 0;
}
}
if (ok == 1) {
p = OSSL_PARAM_locate(params, OSSL_DIGEST_PARAM_SIZE);
if (p != NULL) {
if (OSSL_PARAM_set_size_t(p, 8) != 1) {
ok = 0;
}
}
}
return ok;
}
static const OSSL_PARAM *umac64_gettable_params(void) {
return umac_params;
}
static const OSSL_DISPATCH umac64_functions[] = {
{ OSSL_FUNC_DIGEST_NEWCTX, (void (*)(void)) umac_ctx_new },
{ OSSL_FUNC_DIGEST_FREECTX, (void (*)(void)) umac_ctx_free },
{ OSSL_FUNC_DIGEST_INIT, (void (*)(void)) umac_md_init },
{ OSSL_FUNC_DIGEST_UPDATE, (void (*)(void)) umac64_md_update },
{ OSSL_FUNC_DIGEST_FINAL, (void (*)(void)) umac64_md_final },
{ OSSL_FUNC_DIGEST_GET_PARAMS, (void (*)(void)) umac64_get_params },
{ OSSL_FUNC_DIGEST_GETTABLE_PARAMS, (void (*)(void)) umac64_gettable_params },
{ 0, NULL }
};
/* UMAC128 */
static int umac128_md_update(void *vctx, const unsigned char *data,
size_t len) {
UMAC_CTX *ctx;
struct umac_ctx *umac;
ctx = vctx;
umac = ctx->umac;
/* The allocation of the umac_ctx is deliberately delayed until the first
* update, since the computation of keys depends on the initial bytes
* provided.
*/
if (umac == NULL) {
umac = proxy_ssh_umac128_new((unsigned char *) data);
if (umac == NULL) {
return 0;
}
ctx->umac = umac;
return 1;
}
return proxy_ssh_umac128_update(umac, (unsigned char *) data, (long) len);
}
static int umac128_md_final(void *vctx, unsigned char *out, size_t *out_len,
size_t outsz) {
int res = 1;
struct umac_ctx *ctx;
unsigned char nonce[8];
ctx = vctx;
*out_len = outsz;
if (outsz != 0) {
res = proxy_ssh_umac128_final(ctx, out, nonce);
}
return res;
}
static int umac128_get_params(void *provctx, OSSL_PARAM params[]) {
OSSL_PARAM *p;
int ok = 1;
p = OSSL_PARAM_locate(params, OSSL_DIGEST_PARAM_BLOCK_SIZE);
if (p != NULL) {
if (OSSL_PARAM_set_size_t(p, 64) != 1) {
ok = 0;
}
}
if (ok == 1) {
p = OSSL_PARAM_locate(params, OSSL_DIGEST_PARAM_SIZE);
if (p != NULL) {
if (OSSL_PARAM_set_size_t(p, 16) != 1) {
ok = 0;
}
}
}
return ok;
}
static const OSSL_PARAM *umac128_gettable_params(void) {
return umac_params;
}
static const OSSL_DISPATCH umac128_functions[] = {
{ OSSL_FUNC_DIGEST_NEWCTX, (void (*)(void)) umac_ctx_new },
{ OSSL_FUNC_DIGEST_FREECTX, (void (*)(void)) umac_ctx_free },
{ OSSL_FUNC_DIGEST_INIT, (void (*)(void)) umac_md_init },
{ OSSL_FUNC_DIGEST_UPDATE, (void (*)(void)) umac128_md_update },
{ OSSL_FUNC_DIGEST_FINAL, (void (*)(void)) umac128_md_final },
{ OSSL_FUNC_DIGEST_GET_PARAMS, (void (*)(void)) umac128_get_params },
{ OSSL_FUNC_DIGEST_GETTABLE_PARAMS, (void (*)(void)) umac128_gettable_params },
{ 0, NULL }
};
static const OSSL_ALGORITHM umac_digests[] = {
{ "umac64", NULL, umac64_functions },
{ "umac128", NULL, umac128_functions },
{ NULL, NULL, NULL }
};
static const OSSL_ALGORITHM *umac_provider_operations(void *provctx,
int operation_id, int *no_cache) {
*no_cache = 0;
if (operation_id == OSSL_OP_DIGEST) {
return umac_digests;
}
return NULL;
}
static const OSSL_DISPATCH umac_provider_functions[] = {
{ OSSL_FUNC_PROVIDER_QUERY_OPERATION, (void (*)(void)) umac_provider_operations },
{ 0, NULL }
};
static int umac_provider_init(const OSSL_CORE_HANDLE *core,
const OSSL_DISPATCH *in, const OSSL_DISPATCH **out, void **provctx) {
*out = umac_provider_functions;
*provctx = (void *) core;
return 1;
}
#endif /* OpenSSL 4.x and later, ProFTPD before 1.3.10rc2 */
int proxy_ssh_provider_init(void) {
#if OPENSSL_VERSION_NUMBER >= 0x40000000L && !defined(HAVE_LIBRESSL) && \
PROFTPD_VERSION_NUMBER < 0x0001030A02
if (OSSL_PROVIDER_add_builtin(NULL, "umac", umac_provider_init) != 1) {
pr_log_debug(DEBUG1, MOD_PROXY_VERSION
": error registering 'umac' OpenSSL provider: %s",
proxy_ssh_crypto_get_errors());
} else {
pr_trace_msg(trace_channel, 9, "%s", "registered 'umac' OpenSSL provider");
}
/* Load our custom OpenSSL algorithm provider. */
umac_provider = OSSL_PROVIDER_load(NULL, "umac");
if (umac_provider == NULL) {
pr_log_pri(PR_LOG_NOTICE, MOD_PROXY_VERSION
": error loading 'umac' OpenSSL provider: %s",
proxy_ssh_crypto_get_errors());
} else {
pr_trace_msg(trace_channel, 9, "%s", "loaded 'umac' OpenSSL provider");
}
#endif /* OpenSSL 4.x and later, ProFTPD before 1.3.10rc2 */
return 0;
}
void proxy_ssh_provider_free(void) {
#if OPENSSL_VERSION_NUMBER >= 0x40000000L && !defined(HAVE_LIBRESSL) && \
PROFTPD_VERSION_NUMBER < 0x0001030A02
if (umac_provider != NULL) {
OSSL_PROVIDER_unload(umac_provider);
umac_provider = NULL;
}
#endif /* OpenSSL 4.x and later, ProFTPD before 1.3.10rc2 */
}
proftpd-mod_proxy-0.9.7/lib/proxy/ssh/redis.c 0000664 0000000 0000000 00000016750 15207633221 0021233 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_proxy SSH Redis implementation
* Copyright (c) 2022-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 "redis.h"
#include "proxy/ssh.h"
#include "proxy/ssh/crypto.h"
#include "proxy/ssh/redis.h"
#include